This code, along with the appropriate installed libraries, can be installed on the M5-Stick-C-Plus using the Arduino IDE. Partition scheme: Minimal SPIFFS.

The Paranormal Sensor Station is Open Source so that the community can debunk and debug it’s use with full scientific disclosure. This code is provided in good faith with the understanding that it is not to be distributed in a commercial product, in whole or in part, without the written permission of Two Faces.

The Code as a zip


// PSS by Two Faces
// beep fix v10_16
#include <M5StickCPlus.h>
#include "M5_ENV.h"
#include <Wire.h>
#include "ClosedCube_SHT31D.h"
#include "HX711.h"
#include "EEPROM.h"
#define EEPROM_SIZE 300
#include <WiFi.h>
#include <ESPAsyncWebServer.h>
#include <AsyncTCP.h>
#include <Update.h>
#include "logo.h"
char *baseSSID;
String ssid;
const char *host = "PSS";
AsyncWebServer server(80);
#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>
BLEServer *pServer = NULL;
BLECharacteristic *pCharacteristicData = NULL;
BLECharacteristic *pCharacteristicReceivedData = NULL;
bool deviceConnected = false;
#define LOADCELL_DOUT_PIN 25
#define LOADCELL_SCK_PIN 26
HX711 scale;
ClosedCube_SHT31D sht3xd;
QMP6988 qmp6988;
SHT31D sht;

// Rem Duo pins. use pins 32 (26) and 33 (36) for RemDuo on port 2 instead
#define pin26 26
#define pin36 36

volatile int pulseCountPin26 = 2000;
volatile int pulseCountPin36 = 2000;
int lastPulseCountPin26 = 2000;
int lastPulseCountPin36 = 2000;
int maxHzPin26 = 2000;
int maxHzPin36 = 2000;
int targetMaxHzPin26 = 2000;
int targetMaxHzPin36 = 2000;
const long interval = 2000;        // calbration duration to get mode ms
const long transitionTime = 1000;  // calibration correction time ms
long previousMillis = 0;
int historyPin26[3] = { 0 };
int historyPin36[3] = { 0 };
int ynHistory[3] = { 0 };
int historyIndex = 0;
int ynHistoryIndex = 0;
int currentPulseCountPin26;
int currentPulseCountPin36;
int pulsesPerSecondPin26;
int pulsesPerSecondPin36;
int percentagePin36;
int percentagePin26;
int pdn;
int ysn;
int ysnSkin = 30;  /// determines remduo alert percentage based on human touch = 100%
int calibDelay = 45;
int calibRem = 45;

float tcal = 4;  // calibrate thermometer by subtracting this number from the reading

bool state = false;
bool old_state = false;
byte oct = 0;
int id = 54321;
byte bright = 3;
byte nbright = 9;
byte cpu = 24;  // x10 MHz
bool btON = true;
byte alertHold = 10;  // time to display one alert, must do this many cycles to consider a new alert
int sleepr;
int maxSleep = 60;
unsigned long lastActivityTime;
bool batAlert = false;

float accX = 0.0F;
float accY = 0.0F;
float accZ = 0.0F;
float temp = 0.0F;
float humi = 0.0F;
float pres = 0.0F;
float aX;
float aY;
float aZ;
float at;
float ah;
float ap;
float oldX;
float oldY;
float oldZ;
float oldt;
float oldh;
float oldp;

bool tempOn = false;
bool temp2On = false;
bool scaleOn = false;
bool remOn = false;
bool press = false;

byte event[14];
byte lastEvent;
byte lastTimer;
bool pol;  //last polarity of reading (positive if true, otherwise negative)

float sens = .02;
float tsens = .025;
float ssens = .01;
int rsens = 1;

byte tick;

float weight;
float maxWeight;
float minWeight;
bool scalib = false;
float loadCellReadings[3] = { 0 };  // Array to hold the last three load cell readings
int currentReadingIndex = 0;        // Index to keep track of the current reading

void IRAM_ATTR handleInterruptPin26() {
  pulseCountPin26++;
}

void IRAM_ATTR handleInterruptPin36() {
  pulseCountPin36++;
}

int calculateMode(int values[], int length) {
  int number = values[0];
  int mode = number;
  int count = 1;
  int countMode = 1;
  for (int i = 1; i < length; i++) {
    if (values[i] == number) {
      count++;
    } else {
      if (count > countMode) {
        countMode = count;
        mode = number;
      }
      count = 1;
      number = values[i];
    }
  }
  return mode;
}

void reseter() {
  lastEvent = 0;
  for (int c = 0; c < 14; c++) {
    event[c] = 0;
  }
  tick = 0;

  checkPer();
  if (remOn) {
    calibRem = calibDelay;
  } else if (!tempOn || !temp2On) {
    delay(2000);
  }
  if (scaleOn) {
    maxWeight = 0;
    minWeight = 0;
    calibScale();
  }
  if (tempOn) {
    aX = accX * 3;
    aY = accY * 3;
    aZ = accZ * 3;
    at = temp * 3;
    ah = humi * 3;
    ap = pres * 3;
  }
}

class MyServerCallbacks : public BLEServerCallbacks {
  void onConnect(BLEServer *pServer) {
    deviceConnected = true;
  }

  void onDisconnect(BLEServer *pServer) {
    deviceConnected = false;
  }
};

class MyCallbacks : public BLECharacteristicCallbacks {
  void onWrite(BLECharacteristic *pCharacteristic) {
    std::string rxValue = pCharacteristic->getValue();  // Get the value that was written
    char letter2;
    int vt;
    int number;
    if (rxValue.length() > 0) {
      char letter1 = rxValue[0];
      if (rxValue.length() > 1) {
        letter2 = rxValue[1];  // This could be a number if only one letter was sent
        vt = rxValue[1] - '0';
      }
      if (rxValue.length() > 2) {
        number = atoi(rxValue.substr(2).c_str());
      }

      if ((letter1 == 'r' && letter2 == 'e') || (letter1 == 'r' && letter2 == 'd') || (letter1 == 'a' && letter2 == 'a') || (letter1 == 'a' && !letter2)) {
        btGo();
        wake();
      }
      if ((letter1 == 'b' && letter2 == 'e') || (letter1 == 'b' && letter2 == 'p') || (letter1 == 'b' && !letter2)) {
        for (int joy = 0; joy < 25; joy++) {
          digitalWrite(M5_LED, LOW);
          M5.Beep.tone(120 + (joy * 100), 10);
          delay(10);
          digitalWrite(M5_LED, HIGH);
        }
      }
      if ((letter1 == 'b' && letter2 == 'l') || (letter1 == 'f' && letter2 == 'l') || (letter1 == 'f' && !letter2)) {
        for (int fla = 0; fla < 20; fla++) {
          digitalWrite(M5_LED, LOW);
          delay(100);
          digitalWrite(M5_LED, HIGH);
          delay(100);
        }
      }
      if (letter1 == 'r' && letter2 == 'b') {
        ESP.restart();
      }
      if ((letter1 == 'z' && letter2 == 'z') || (letter1 == 'z' && !letter2)) {
        wake();
        reseter();
      }
      if (letter1 == 'f' && letter2 == 'r') {
        wipeEEPROM();
        ESP.restart();
      }
      if ((letter1 == 'a' && letter2 == 's') && (number < 6)) {
        float sval = number;
        sens = (5 * (sval + 3.000)) / 1000;
        EEPROM.put(9, sens);
        EEPROM.commit();
        Serial.println(String(letter1) + String(letter2) + String(sval) + "   " + String(sens, 3));
      }
      if (letter1 == 'a' && vt >= 0 && vt < 6) {
        float sval = vt;
        sens = (5 * (sval + 3.000)) / 1000;
        EEPROM.put(9, sens);
        EEPROM.commit();
        Serial.println(String(letter1) + String(letter2) + String(sval) + "   " + String(sens, 3));
      }
      if ((letter1 == 't' && letter2 == 's') && (number < 6 && number)) {
        float sval = number;
        tsens = (5 * (sval + 4.000)) / 1000;
        EEPROM.put(17, tsens);
        EEPROM.commit();
        Serial.println(String(letter1) + String(letter2) + String(sval) + "   " + String(tsens, 3));
      }
      if (letter1 == 't' && vt > 0 && vt < 6) {
        float sval = vt;
        tsens = (5 * (sval + 4.000)) / 1000;
        EEPROM.put(17, tsens);
        EEPROM.commit();
        Serial.println(String(letter1) + String(letter2) + String(sval) + "   " + String(tsens, 3));
      }
      if ((letter1 == 's' && letter2 == 's') && (number < 6 && number)) {
        float sval = number;
        ssens = sval / 100.000;
        EEPROM.put(13, ssens);
        EEPROM.commit();
        Serial.println(String(letter1) + String(letter2) + String(sval) + "   " + String(ssens, 3));
      }
      if (letter1 == 's' && vt > 0 && vt < 6) {
        float sval = vt;
        ssens = sval / 100.000;
        EEPROM.put(13, ssens);
        EEPROM.commit();
        Serial.println(String(letter1) + String(letter2) + String(sval) + "   " + String(ssens, 3));
      }
      if ((letter1 == 'r' && letter2 == 's') && (number < 6 && number)) {
        rsens = number;
        EEPROM.put(21, rsens);
        EEPROM.commit();
        Serial.println(String(letter1) + String(letter2) + String(rsens));
      }
      if (letter1 == 'r' && vt > 0 && vt < 6) {
        rsens = vt;
        EEPROM.put(21, rsens);
        EEPROM.commit();
        Serial.println(String(letter1) + String(letter2) + String(rsens));
      }
      if ((letter1 == 'v' && letter2 == 'o') && (number < 6)) {
        oct = number;
        EEPROM.writeByte(7, oct);
        EEPROM.commit();
        Serial.println(String(letter1) + String(letter2) + String(oct));
      }
      if (letter1 == 'v' && vt >= 0 && vt < 6) {
        oct = vt;
        EEPROM.writeByte(7, oct);
        EEPROM.commit();
        Serial.println(String(letter1) + String(letter2) + String(oct));
      }
      if ((letter1 == 'i' && letter2 == 'd') && number < 99999 && number && number > 0) {
        id = number;
        EEPROM.writeByte(2, lowByte(id));
        EEPROM.writeByte(3, highByte(id));
        EEPROM.commit();
        Serial.println(String(letter1) + String(letter2) + String(id));
      }
      if (((letter1 == 'c' && letter2 == 'p') || (letter1 == 'm' && letter2 == 'h')) && number < 25 && number > 7) {
        byte nnum = number;
        cpu = nnum;
        EEPROM.writeByte(6, cpu);
        EEPROM.commit();
        Serial.println(String(letter1) + String(letter2) + String(cpu));
        setCpuFrequencyMhz(cpu * 10);
      }
      if ((letter1 == 'w' && letter2 == 'i') || (letter1 == 'f' && letter2 == 'w') || (letter1 == 'w' && letter2 == 'f')) {
        startWifi();
      }
      if (letter1 == 'b' && letter2 == 'r' && number < 6) {
        bright = number;
        brightScale();
        EEPROM.writeByte(4, bright);
        EEPROM.writeByte(5, nbright);
        EEPROM.commit();
        Serial.println(String(letter1) + String(letter2) + String(bright) + " " + String(nbright));
        M5.Axp.ScreenBreath(nbright);
      }
      if (letter1 == 's' && letter2 == 'l' && number < 301) {
        sleepr = number;
        if (!number) wake();
        EEPROM.put(31, sleepr);
        EEPROM.commit();
        Serial.println(String(letter1) + String(letter2) + String(number));
      }
      if (letter1 == 't' && letter2 == 'c' && number < 2000) {
        tcal = number / 100.00;
        EEPROM.put(35, tcal);
        EEPROM.commit();
        Serial.println(String(letter1) + String(letter2) + String(tcal));
      }
    }
  }
};

void handleRoot(AsyncWebServerRequest *request) {
  String html = "<html><body>";
  html += "<h1>Two Faces PSS Firmware ver 1.16</h1>";
  html += "<form id='uploadForm' method='POST' action='/update' enctype='multipart/form-data'>";
  html += "<input type='file' name='update'><br><br>";
  html += "<input type='submit' id='uploadBtn' value='Upload Firmware' onclick='startUpload()'>";
  html += "</form>";
  html += "<script>";
  html += "function startUpload() {";
  html += "  var btn = document.getElementById('uploadBtn');";
  html += "  btn.value = 'Uploading...';";
  html += "}";
  html += "</script>";
  html += "</body></html>";
  request->send(200, "text/html", html);
}

void handleNotFound(AsyncWebServerRequest *request) {
  request->send(404);
}

void handleFileUpload(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) {
  static int totalSize = 0;

  if (!index) {
    Serial.println("Starting firmware update");
    if (!Update.begin(UPDATE_SIZE_UNKNOWN)) {
      Serial.println("Error: Firmware update could not begin");
    }
  }

  if (Update.write(data, len) != len) {
    Serial.println("Error: Writing firmware data failed");
  }

  totalSize += len;

  if (final) {
    if (Update.end(true)) {
      Serial.println("Firmware update completed");
      Serial.print("Total size: ");
      Serial.println(totalSize);
      request->send(200, "text/html", "<br><br>Firmware update <b>completed</b>. Your Two Faces PSS will restart in a few seconds.");
      delay(2000);
      ESP.restart();
    } else {
      Serial.println("Error: Firmware update failed");
      request->send(200, "text/html", "<br><br>Two Faces PSS Firmware <b>update failed</b>. Please try again.<br><i>Be sure to use the file ending in .bin<br>if it ends in .zip double-click it and move the .bin file out of the zip folder first.</i><br><br><a href='/'><button>Try Again</button></a>");
    }
  }
}

void startWifi() {
  baseSSID = "PSS_";
  ssid = String(baseSSID) + String(id);
  WiFi.softAP(ssid.c_str(), "");
  IPAddress IP = WiFi.softAPIP();
  Serial.print("AP IP address: ");
  Serial.println(IP);
  server.on("/", HTTP_GET, handleRoot);
  server.onNotFound(handleNotFound);
  server.onFileUpload(handleFileUpload);
  server.on(
    "/update", HTTP_POST, [](AsyncWebServerRequest *request) {
      request->send(200);
    },
    handleFileUpload);
  server.begin();
}

void wifiDraw() {
  M5.Lcd.fillScreen(BLACK);
  M5.Lcd.setTextSize(2);
  M5.Lcd.setTextColor(WHITE, BLACK);
  M5.Lcd.setCursor(0, 30);
  M5.Lcd.print(" Wifi\n");
  M5.Lcd.print(" Firmware\n");
  M5.Lcd.print(" Update\n\n");
  M5.Lcd.setCursor(5, 90);
  M5.Lcd.print("Find SSID:\n");
  M5.Lcd.setCursor(5, 110);
  M5.Lcd.print(ssid);
  M5.Lcd.setCursor(5, 140);
  M5.Lcd.print("Go to URL:\n");
  M5.Lcd.setCursor(0, 160);
  M5.Lcd.print("192.168.4.1\n");
}

double getBatteryLevel(void) {
  uint16_t vbatData = M5.Axp.GetVbatData();
  double vbat = vbatData * 1.1 / 1000;
  //  return 100.0 * ((vbat - 3.4) / (4.07 - 3.4));
  if (M5.Axp.GetBatCurrent() > 0) {
    vbat = vbat - .05;
  }
  double batteryPercentage;
  if (vbat <= 3.3) {
    batteryPercentage = 0;
  } else if (vbat >= 4.07) {
    batteryPercentage = 100;
  } else {
    double minVoltage = 3.3;
    double maxVoltage = 4.07;
    double minPercent = 0;
    double maxPercent = 100;

    double normalizedVoltage = (vbat - minVoltage) / (maxVoltage - minVoltage);
    double exponentialValue = pow(normalizedVoltage, 3);
    batteryPercentage = exponentialValue * (maxPercent - minPercent) + minPercent;
  }
  if (batteryPercentage < 10 && !batAlert) {
    batAlert = true;
    wake();
    if (btON && deviceConnected) {
      String data;
      data += "BAT" + String(id);
      pCharacteristicData->setValue(data.c_str());
      pCharacteristicData->notify();
    }
    digitalWrite(M5_LED, LOW);
    if (oct) {
      for (int sad = 0; sad < 50; sad++) {
        M5.Beep.tone(5120 - (sad * 100), 10);
        delay(10);
      }
    } else {
      delay(500);
    }
    digitalWrite(M5_LED, HIGH);
  }
  if (batteryPercentage > 25 && batAlert) {
    batAlert = false;
    wake();
  }
  return batteryPercentage;
}

void wake() {
  lastActivityTime = millis();  // Update the time of the last activity.
  if (batAlert) {
    M5.Axp.ScreenBreath(0);
  } else {
    M5.Axp.ScreenBreath(nbright);
  }
}

void wipeEEPROM() {
  for (int a = 0; a < EEPROM_SIZE; a++) {
    EEPROM.writeByte(a, 0);
  }
  EEPROM.commit();
}

void save() {
  EEPROM.writeByte(1, 1);
  EEPROM.writeByte(2, lowByte(id));
  EEPROM.writeByte(3, highByte(id));
  EEPROM.writeByte(4, bright);
  EEPROM.writeByte(5, nbright);
  EEPROM.writeByte(6, cpu);
  EEPROM.writeByte(7, oct);
  EEPROM.writeByte(8, btON);
  EEPROM.put(9, sens);
  EEPROM.put(13, ssens);
  EEPROM.put(17, tsens);
  EEPROM.put(21, rsens);
  EEPROM.put(31, sleepr);
  EEPROM.put(35, tcal);
  EEPROM.commit();
}

void load() {
  id = (EEPROM.read(3) << 8) | EEPROM.read(2);
  bright = EEPROM.readByte(4);
  nbright = EEPROM.readByte(5);
  cpu = EEPROM.readByte(6);
  oct = EEPROM.readByte(7);
  btON = EEPROM.readByte(8);
  EEPROM.get(9, sens);
  EEPROM.get(13, ssens);
  EEPROM.get(17, tsens);
  EEPROM.get(21, rsens);
  EEPROM.get(31, sleepr);
  EEPROM.get(35, tcal);
  if (nbright < 7 || nbright > 15) {
    nbright = 9;
    EEPROM.writeByte(5, nbright);
    EEPROM.commit();
  }
  M5.Axp.ScreenBreath(nbright);
  if (cpu < 8 || cpu > 24) {
    cpu = 8;
    EEPROM.writeByte(6, cpu);
    EEPROM.commit();
  }
  setCpuFrequencyMhz(cpu * 10);
}

void checkPer() {
  // check peripherals
  pinMode(pin26, INPUT_PULLUP);
  pinMode(pin36, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(pin26), handleInterruptPin26, FALLING);
  attachInterrupt(digitalPinToInterrupt(pin36), handleInterruptPin36, FALLING);
  delay(2000);                                             // Wait to accumulate some pulses
  if (pulseCountPin26 > 2001 && pulseCountPin36 > 2001) {  // check for rem duo
    remOn = true;
    if (cpu != 24) {
      cpu = 24;
      EEPROM.writeByte(6, cpu);
      EEPROM.commit();
      setCpuFrequencyMhz(cpu * 10);
    }
  } else {  // no rem duo
    detachInterrupt(digitalPinToInterrupt(pin26));
    detachInterrupt(digitalPinToInterrupt(pin36));
    pinMode(25, INPUT);      // return things to non-rem state
    if (!digitalRead(25)) {  // check if strain gauge load cell is connected
                             // no load cell, do env
      Wire.end();
      Wire.begin(0, 26);
      sht3xd.begin(0x44);
      sht = sht3xd.readTempAndHumidity(SHT3XD_REPEATABILITY_HIGH, SHT3XD_MODE_POLLING, 50);
      if (sht.error == SHT3XD_NO_ERROR) {  // check if env hat is connected
        qmp6988.init();
        pres = qmp6988.calcPressure();
        temp = sht.t;
        humi = sht.rh;
        tempOn = true;
      } else {
        tempOn = false;
      }
    } else {  // yes load cell
      pinMode(0, OUTPUT);
      digitalWrite(0, LOW);
      scale.begin(LOADCELL_DOUT_PIN, LOADCELL_SCK_PIN);
      scale.set_scale(16.4f);
      if (scale.get_units(1)) {
        scaleOn = true;
        splash();
      }
    }
  }
  if (!tempOn) {
    //check port b
    Wire.end();
    delay(100);
    Wire.begin(32, 33);
    sht3xd.begin(0x44);
    sht = sht3xd.readTempAndHumidity(SHT3XD_REPEATABILITY_HIGH, SHT3XD_MODE_POLLING, 50);
    if (sht.error == SHT3XD_NO_ERROR) {
      qmp6988.init();
      pres = qmp6988.calcPressure();
      temp = sht.t;
      humi = sht.rh;
      temp2On = true;
      sens = .015;  // turn off accel alerts
    } else {
      temp2On = false;
    }
  }
}  // end perif check

void setup() {
  M5.begin();

  Serial.begin(115200);

  M5.Axp.ScreenBreath(9);
  setCpuFrequencyMhz(240);

  M5.update();
  bool ba = M5.BtnA.isPressed();
  bool bb = M5.BtnB.isPressed();
  if (ba && bb) {
    wipeEEPROM();
    ESP.restart();
  }

  EEPROM.begin(EEPROM_SIZE);
  if (EEPROM.readByte(1) == 1) {
    load();
  } else {
    save();
  }

  M5.Lcd.setRotation(0);
  M5.Lcd.fillScreen(BLACK);
  M5.Lcd.setTextSize(2);

  M5.IMU.Init();
  pinMode(M5_LED, OUTPUT);
  digitalWrite(M5_LED, HIGH);

  if (btON) {
    String idString = String(id);
    String deviceName = "PSS_" + idString;
    BLEDevice::init(deviceName.c_str());
    pServer = BLEDevice::createServer();
    pServer->setCallbacks(new MyServerCallbacks());

    // Create BLE Service
    BLEService *pService = pServer->createService(BLEUUID("180A"));

    // Create BLE Characteristic
    pCharacteristicData = pService->createCharacteristic(
      BLEUUID("2A46"),  // 2A3D device info, 2A46 for Alert
      BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY);

    pService->addCharacteristic(pCharacteristicData);

    pCharacteristicReceivedData = pService->createCharacteristic(
      BLEUUID("2A3D"),                   // or Device Name 2A00
      BLECharacteristic::PROPERTY_WRITE  // Make it writeable from the client side
    );

    pService->addCharacteristic(pCharacteristicReceivedData);

    pCharacteristicReceivedData->setCallbacks(new MyCallbacks());  // Set the callbacks for this characteristic

    pCharacteristicData->addDescriptor(new BLE2902());
    pCharacteristicReceivedData->addDescriptor(new BLE2902());

    // Start the service
    pService->start();

    // Start advertising
    pServer->getAdvertising()->start();
    Serial.println("Waiting for connections...");
    BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
    pAdvertising->setScanResponse(true);
    //    pAdvertising->setMinPreferred(0xC80);  // Set the advertising interval to 2 seconds
    pAdvertising->setMinPreferred(0x06);  // functions that help with iPhone connections issue
    pAdvertising->setMinPreferred(0x12);
    pAdvertising->addServiceUUID(pService->getUUID());
    pAdvertising->start();
  }

  splash();

  checkPer();

  M5.IMU.getAccelData(&accX, &accY, &accZ);

  aX = accX * 3;
  aY = accY * 3;
  aZ = accZ * 3;
  at = temp * 3;
  ah = humi * 3;
  ap = pres * 3;

  delay(1000);
  M5.Lcd.fillScreen(BLACK);
}  // end setup

void splash() {
  M5.Lcd.fillScreen(RED);
  M5.Lcd.setCursor(0, 115);
  M5.Lcd.setTextSize(2);
  M5.Lcd.setTextColor(BLACK, RED);
  M5.Lcd.print(" Two Faces\n");
  M5.Lcd.print(" Paranormal\n");
  M5.Lcd.print(" Sensor\n");
  M5.Lcd.print(" Station\n");
  M5.Lcd.print(" ver 1.16\n\n");

  M5.Lcd.setTextSize(1);
  M5.Lcd.print(" Left Btn: Reset/Pwr.\n");
  M5.Lcd.print(" Right Btn: Beep/Menu.\n");
  M5.Lcd.print(" Front Btn: SensThresh");

  M5.Lcd.drawXBitmap(17, 5, logo, 100, 100, RED, BLACK);
}

char nth_digit(int val, int n) {
  char buffer[7];  // enough space for large negative int and null terminator
  sprintf(buffer, "%i", val);
  return buffer[0] == '-' ? buffer[n + 1] : buffer[n];
}

void brightScale() {
  switch (bright) {
    case 0:
      nbright = 7;
      break;
    case 1:
      nbright = 8;
      break;
    case 2:
      nbright = 9;
      break;
    case 3:
      nbright = 10;
      break;
    case 4:
      nbright = 12;
      break;
    case 5:
      nbright = 15;
      break;
    default:
      nbright = 8;
      break;
  }
}

void set() {  //////////// SETTINGS screen
  bool resetConfirm = false;
  bool redraw = true;
  byte param = 2;
  int idset;
  digitalWrite(M5_LED, HIGH);  // led on
  while (1) {
    M5.update();
    if (M5.BtnB.wasReleased()) {
      if (param == 0 && idset < 4) {
        idset++;
      } else {
        idset = 0;
        param++;
        if (param > 7) param = 0;
      }
      redraw = true;
    }
    if (M5.BtnA.wasReleased()) {
      switch (param) {
        case 0:
          id = id + pow(10, idset);
          if (id > 99999) id = id - 100000;
          redraw = true;
          break;
        case 1:
          if (btON) {
            btON = false;
          } else {
            btON = true;
          }
          redraw = true;
          break;
        case 2:
          bright++;
          if (bright > 5) bright = 0;
          redraw = true;
          brightScale();
          M5.Axp.ScreenBreath(nbright);
          break;
        case 3:
          cpu = cpu * 2;
          if (cpu == 32) cpu = 24;
          if (cpu > 24) cpu = 8;
          redraw = true;
          setCpuFrequencyMhz(cpu * 10);
          break;
        case 4:
          sleepr += 5;
          if (sleepr > maxSleep) sleepr = 0;
          redraw = true;
          break;
        case 5:
          startWifi();
          wifiDraw();
          while (1) {
            M5.update();
            if (M5.BtnA.wasReleased()) {
              save();
              ESP.restart();
              M5.Lcd.fillScreen(BLACK);
              return;
            }
          }
          break;
        case 6:
          if (resetConfirm) {
            wipeEEPROM();
            ESP.restart();
          } else {
            resetConfirm = true;
            redraw = true;
          }
          break;
        case 7:
          save();
          ESP.restart();
          return;
          break;
      }
    }
    if (redraw) {
      redraw = false;
      M5.Lcd.fillScreen(BLACK);
      M5.Lcd.setCursor(0, 0);
      M5.Lcd.setTextSize(2);
      M5.Lcd.setTextColor(BLACK, ORANGE);
      M5.Lcd.print(" SETTINGS  \n");
      if (param == 0) {
        M5.Lcd.setTextColor(WHITE, BLACK);
        M5.Lcd.print("   ");
        for (int dg = 0; dg < 5 - idset; dg++) {
          M5.Lcd.print(" ");
        }
        M5.Lcd.print("_");
      }
      M5.Lcd.print("\n");
      if (param == 0) {
        M5.Lcd.setTextColor(WHITE, BLACK);
        M5.Lcd.print(">");
      } else {
        M5.Lcd.setTextColor(ORANGE, BLACK);
        M5.Lcd.print(" ");
      }
      M5.Lcd.printf("Id=%5d\n", id);
      if (param == 1) {
        M5.Lcd.setTextColor(WHITE, BLACK);
        M5.Lcd.print(">");
      } else {
        M5.Lcd.setTextColor(ORANGE, BLACK);
        M5.Lcd.print(" ");
      }
      if (btON) {
        M5.Lcd.printf("Blue2th=ON\n");
      } else {
        M5.Lcd.printf("Blue2th=no\n");
      }
      if (param == 2) {
        M5.Lcd.setTextColor(WHITE, BLACK);
        M5.Lcd.print(">");
      } else {
        M5.Lcd.setTextColor(ORANGE, BLACK);
        M5.Lcd.print(" ");
      }
      M5.Lcd.printf("Bright =%2d\n", bright);
      if (param == 3) {
        M5.Lcd.setTextColor(WHITE, BLACK);
        M5.Lcd.print(">");
      } else {
        M5.Lcd.setTextColor(ORANGE, BLACK);
        M5.Lcd.print(" ");
      }
      M5.Lcd.printf("Mhz  = %2d0\n", cpu);
      if (param == 4) {
        M5.Lcd.setTextColor(WHITE, BLACK);
        M5.Lcd.print(">");
      } else {
        M5.Lcd.setTextColor(ORANGE, BLACK);
        M5.Lcd.print(" ");
      }
      if (sleepr) {
        M5.Lcd.printf("Sleep =%3d\n", sleepr);
      } else {
        M5.Lcd.print("Sleep = no");
      }
      if (param == 5) {
        M5.Lcd.setTextColor(WHITE, BLACK);
        M5.Lcd.print(">");
      } else {
        M5.Lcd.setTextColor(ORANGE, BLACK);
        M5.Lcd.print(" ");
      }
      M5.Lcd.printf("Firmware\n", cpu);
      if (param == 6) {
        M5.Lcd.setTextColor(WHITE, BLACK);
        M5.Lcd.print(">");
      } else {
        M5.Lcd.setTextColor(ORANGE, BLACK);
        M5.Lcd.print(" ");
      }
      if (resetConfirm) {
        M5.Lcd.print("R U Sure?\n");
      } else {
        M5.Lcd.print("Reset All\n");
      }
      if (param == 7) {
        M5.Lcd.setTextColor(WHITE, BLACK);
        M5.Lcd.print(">");
      } else {
        M5.Lcd.setTextColor(ORANGE, BLACK);
        M5.Lcd.print(" ");
      }
      M5.Lcd.print("EXIT/SAVE");

      M5.Lcd.setTextSize(1);
      M5.Lcd.setTextColor(RED, BLACK);
      M5.Lcd.print("\n\n\n Right btn: setting\n\n");
      M5.Lcd.print(" Front btn: value\n\n");
      M5.Lcd.print(" Lower Bright or Mhz\n");
      M5.Lcd.print(" to Save Battery");
      while (M5.BtnB.isPressed()) {
        M5.update();
      }
    }

  }  // end while
  M5.update();
  delay(300);
}  // end void

void checkButtons() {  //////////////////////////////  BUTTONS
  bool reset;

  M5.update();
  if (M5.BtnA.pressedFor(1000)) {
    press = true;
    scalib = false;
    wake();
    if (remOn || scaleOn) {
      sens += .005;
      if (sens > .04) sens = .015;
      M5.Lcd.setCursor(30, 30);
      M5.Lcd.setTextSize(100);
      M5.lcd.fillRect(0, 16, M5.lcd.width() - 3, 80, ORANGE);
      M5.Lcd.setTextColor(BLACK, ORANGE);
      M5.Lcd.printf("a%1.0f\n", sens * 1000 / 5 - 3);
      M5.Lcd.setTextSize(1);
      M5.Lcd.print(" acel sens thresh");
      EEPROM.put(9, sens);
      EEPROM.commit();
      Serial.println("Accel Sens:" + String(sens, 3));
    }
    if (tempOn || temp2On) {
      tsens += .005;
      if (tsens > .045) tsens = .025;
      M5.Lcd.setCursor(30, 30);
      M5.Lcd.setTextSize(100);
      M5.lcd.fillRect(0, 16, M5.lcd.width() - 3, 80, YELLOW);
      M5.Lcd.setTextColor(BLACK, YELLOW);
      M5.Lcd.printf("t%1.0f\n", tsens * 1000 / 5 - 4);
      M5.Lcd.setTextSize(1);
      M5.Lcd.print(" temp sens thresh");
      EEPROM.put(17, tsens);
      EEPROM.commit();
    }
    delay(300);
    while (M5.BtnA.isPressed()) {
      M5.update();
    }
  } else if (M5.BtnA.wasReleased()) {
    press = true;
    scalib = false;
    if ((sleepr) && (millis() - lastActivityTime >= sleepr * 1000)) {
      wake();
    } else {
      if (remOn) {
        rsens++;
        if (rsens > 5) rsens = 1;
        M5.Lcd.setCursor(30, 30);
        M5.Lcd.setTextSize(100);
        M5.lcd.fillRect(0, 16, M5.lcd.width() - 3, 80, M5.Lcd.color565(0, 255, 128));
        M5.Lcd.setTextColor(BLACK, M5.Lcd.color565(0, 255, 128));
        M5.Lcd.printf("r%1.0d\n", rsens);
        M5.Lcd.setTextSize(1);
        M5.Lcd.print(" rem duo thresh");
        EEPROM.put(21, rsens);
        EEPROM.commit();
      } else if (scaleOn) {
        ssens += .01;
        if (ssens > .07) ssens = .01;
        M5.Lcd.setCursor(30, 30);
        M5.Lcd.setTextSize(100);
        M5.lcd.fillRect(0, 16, M5.lcd.width() - 3, 80, M5.Lcd.color565(0, 255, 128));
        M5.Lcd.setTextColor(BLACK, M5.Lcd.color565(0, 255, 128));
        M5.Lcd.printf("s%1.0f\n", ssens * 100);
        M5.Lcd.setTextSize(1);
        M5.Lcd.print(" bar sens thresh");
        EEPROM.put(13, ssens);
        EEPROM.commit();
      } else {
        sens += .005;
        if (sens > .04) sens = .015;
        M5.Lcd.setCursor(30, 30);
        M5.Lcd.setTextSize(100);
        M5.lcd.fillRect(0, 16, M5.lcd.width() - 3, 80, ORANGE);
        M5.Lcd.setTextColor(BLACK, ORANGE);
        M5.Lcd.printf("a%1.0f\n", sens * 1000 / 5 - 3);
        M5.Lcd.setTextSize(1);
        M5.Lcd.print(" acel sens thresh");
        EEPROM.put(9, sens);
        EEPROM.commit();
        Serial.println("Accel Sens:" + String(sens, 3));
      }
    }
    delay(200);
  }
  if (M5.BtnB.pressedFor(1000)) {
    wake();
    set();
  } else if (M5.BtnB.wasReleased()) {
    oct++;
    if (oct > 5) oct = 0;
    EEPROM.writeByte(7, oct);
    EEPROM.commit();
    wake();
  }
  if (M5.Axp.GetBtnPress() == 0x02) {
    // esp_restart();
    wake();
    splash();
    reseter();
    M5.Lcd.fillScreen(BLACK);
  }
}



void doRem() {  ////////// REM DUO
  currentPulseCountPin26 = pulseCountPin26;
  currentPulseCountPin36 = pulseCountPin36;

  pulsesPerSecondPin26 = currentPulseCountPin26 - lastPulseCountPin26;
  pulsesPerSecondPin36 = currentPulseCountPin36 - lastPulseCountPin36;

  // Store the current pulses per second in the history
  historyPin26[historyIndex] = pulsesPerSecondPin26;
  historyPin36[historyIndex] = pulsesPerSecondPin36;
  historyIndex = (historyIndex + 1) % 3;

  // Calculate the mode for the last 3 seconds
  int modePin26 = calculateMode(historyPin26, 3);
  int modePin36 = calculateMode(historyPin36, 3);

  // Check if we should reset the max values
  if (millis() - previousMillis >= interval) {
    targetMaxHzPin26 = modePin26;
    targetMaxHzPin36 = modePin36;
    previousMillis = millis();
  }

  // Smoothly transition to the new max values over 1 second
  unsigned long elapsed = millis() - previousMillis;
  if (elapsed < transitionTime) {
    float alpha = (float)elapsed / transitionTime;
    maxHzPin26 = (1 - alpha) * maxHzPin26 + alpha * targetMaxHzPin26;
    maxHzPin36 = (1 - alpha) * maxHzPin36 + alpha * targetMaxHzPin36;
  }

  // Calculate the percentage of the current pulses per second relative to the max
  percentagePin36 = (pulsesPerSecondPin36 * 100) / maxHzPin36;
  percentagePin26 = (pulsesPerSecondPin26 * 100) / maxHzPin26;

  // new extreme values clean to tame feedback and speed up auto calib
  if (percentagePin36 > 100) percentagePin36 = 100;
  if (percentagePin26 > 100) percentagePin26 = 100;
  if (percentagePin36 < 0) percentagePin36 = 0;
  if (percentagePin26 < 0) percentagePin26 = 0;
  if (pulsesPerSecondPin36 > 9999) pulsesPerSecondPin36 = 5000;
  if (pulsesPerSecondPin26 > 9999) pulsesPerSecondPin26 = 5000;
  if (!pulsesPerSecondPin36) pulsesPerSecondPin36 = 1;
  if (!pulsesPerSecondPin26) pulsesPerSecondPin26 = 1;

  pdn = percentagePin26 - percentagePin36;

  if (pdn > 0) {
    ysn = (pdn * 100) / ysnSkin;
  } else if (pdn < 0) {
    ysn = (abs(pdn) * 100) / ysnSkin;
  }

  ysn = constrain(ysn, 0, 100);

  lastPulseCountPin26 = currentPulseCountPin26;
  lastPulseCountPin36 = currentPulseCountPin36;

  // Serial.println("Rem: y" + String(percentagePin36) + " n" + String(percentagePin26) + "   Y " + pulsesPerSecondPin36 + "   N " + pulsesPerSecondPin26);  ///// temp serial get vals to clean extremes at calib
}

float getMedian(float a, float b, float c) {
  if ((a < b && b < c) || (c < b && b < a)) return b;
  if ((b < a && a < c) || (c < a && a < b)) return a;
  return c;
}
void calibScale() {
  for (int i = 0; i < 3; i++) {
    loadCellReadings[i] = 0;
  }
  currentReadingIndex = 0;  // Reset the current reading index
  scale.tare();
}

void loop() {  ///////////////////  LOOP

  oldX = aX / 3;
  oldY = aY / 3;
  oldZ = aZ / 3;

  if (tempOn) {
    oldt = at / 3;
    oldh = ah / 3;
    oldp = ap / 3;
  }

  if (scaleOn) {
    // get weight median of 3
    float newReading = scale.get_units(1) / 1000;
    loadCellReadings[currentReadingIndex] = newReading;
    currentReadingIndex = (currentReadingIndex + 1) % 3;
    weight = getMedian(loadCellReadings[0], loadCellReadings[1], loadCellReadings[2]);
    if (weight > maxWeight) maxWeight = weight;
    if (weight < minWeight) minWeight = weight;
    if (minWeight < -99.0) minWeight = -99.0;
    if (maxWeight > 99.0) maxWeight = 99.0;
  }
  sht = sht3xd.readTempAndHumidity(SHT3XD_REPEATABILITY_HIGH, SHT3XD_MODE_POLLING, 50);
  if (sht.error == SHT3XD_NO_ERROR) {
    if (!tempOn) {
      qmp6988.init();
      tempOn = true;
    }
  } else {
    tempOn = false;
  }
  if (tempOn) {
    pres = qmp6988.calcPressure();
    temp = sht.t;
    humi = sht.rh;
  }

  if (remOn) {
    doRem();
    if (calibRem) {
      calibRem--;
    }
  }

  M5.IMU.getAccelData(&accX, &accY, &accZ);

  if (!lastTimer) {  //////////////////// ALERTS

    if ((sens > .015) && ((accX > oldX + sens) || (accX < oldX - sens))) {
      state = true;
      lastTimer = alertHold;
      event[1]++;
      if (event[1] > 99) event[1] = 0;
      lastEvent = 1;
      if (accX > oldX + sens) {
        pol = true;
      } else {
        pol = false;
      }
    } else if ((sens > .015) && ((accY > oldY + sens) || (accY < oldY - sens))) {
      state = true;
      lastTimer = alertHold;
      event[2]++;
      if (event[2] > 99) event[2] = 0;
      lastEvent = 2;
      if (accY > oldY + sens) {
        pol = true;
      } else {
        pol = false;
      }
    } else if ((sens > .015) && ((accZ > oldZ + sens) || (accZ < oldZ - sens))) {
      state = true;
      lastTimer = alertHold;
      event[3]++;
      if (event[3] > 99) event[3] = 0;
      lastEvent = 3;
      if (accZ > oldZ + sens) {
        pol = true;
      } else {
        pol = false;
      }
    } else if ((tempOn) && (temp > oldt + tsens * 2)) {
      state = true;
      lastTimer = alertHold;
      event[4]++;
      if (event[4] > 99) event[4] = 0;
      lastEvent = 4;
      pol = true;
    } else if ((tempOn) && (temp < oldt - tsens * 2)) {
      state = true;
      lastTimer = alertHold;
      event[7]++;
      if (event[7] > 99) event[7] = 0;
      lastEvent = 7;
      pol = false;
    } else if ((tempOn) && (humi > oldh + tsens * 15)) {
      state = true;
      lastTimer = alertHold;
      event[5]++;
      if (event[5] > 99) event[5] = 0;
      lastEvent = 5;
      pol = true;
    } else if ((tempOn) && (humi < oldh - tsens * 15)) {
      state = true;
      lastTimer = alertHold;
      event[8]++;
      if (event[8] > 99) event[8] = 0;
      lastEvent = 8;
      pol = false;
    } else if ((tempOn) && (pres > oldp + tsens * 200)) {
      state = true;
      lastTimer = alertHold;
      event[6]++;
      if (event[6] > 99) event[6] = 0;
      lastEvent = 6;
      pol = true;
    } else if ((tempOn) && (pres < oldp - tsens * 200)) {
      state = true;
      lastTimer = alertHold;
      event[9]++;
      if (event[9] > 99) event[9] = 0;
      lastEvent = 9;
      pol = false;
    } else if ((scalib) && (scaleOn) && (weight > ssens)) {
      state = true;
      lastTimer = alertHold;
      event[10]++;
      if (event[10] > 99) event[10] = 0;
      lastEvent = 10;
    } else if ((scalib) && (scaleOn) && (weight < ssens - (ssens * 2))) {
      state = true;
      lastTimer = alertHold;
      event[11]++;
      if (event[11] > 99) event[11] = 0;
      lastEvent = 11;
    } else if ((remOn) && (pdn > rsens) && (!calibRem)) {
      state = true;
      lastTimer = alertHold;
      event[12]++;
      if (event[12] > 99) event[12] = 0;
      lastEvent = 12;
      calibRem = alertHold * 2;
    } else if ((remOn) && (pdn < 0 - rsens) && (!calibRem)) {
      state = true;
      lastTimer = alertHold;
      event[13]++;
      if (event[13] > 99) event[13] = 0;
      lastEvent = 13;
      calibRem = alertHold * 2;
    } else {
      state = false;
    }
  }

  aX = aX - oldX + accX;
  aY = aY - oldY + accY;
  aZ = aZ - oldZ + accZ;
  if (tempOn || temp2On) {
    at = at - oldt + temp;
    ah = ah - oldh + humi;
    ap = ap - oldp + pres;
  }
  if (!old_state && state) {
    event[0]++;
    if (event[0] > 99) event[0] = 0;
    digitalWrite(M5_LED, LOW);  // led on
    if (oct) {
      switch (lastEvent) {
        case 1:
          M5.Beep.tone((131 * oct), 100);
          break;
        case 2:
          M5.Beep.tone((147 * oct), 100);
          break;
        case 3:
          M5.Beep.tone((164 * oct), 100);
          break;

        case 4:
          M5.Beep.tone((131 * oct + (lastEvent == 14)), 100);
          delay(100);
          M5.Beep.tone((147 * oct + (lastEvent == 14)), 100);
          break;
        case 5:
          M5.Beep.tone((147 * oct + (lastEvent == 15)), 100);
          delay(100);
          M5.Beep.tone((165 * oct + (lastEvent == 15)), 100);
          break;
        case 6:
          M5.Beep.tone((165 * oct + (lastEvent == 16)), 100);
          delay(100);
          M5.Beep.tone((196 * oct + (lastEvent == 16)), 100);
          break;
        case 7:
          M5.Beep.tone((147 * oct + (lastEvent == 17)), 100);
          delay(100);
          M5.Beep.tone((131 * oct + (lastEvent == 17)), 100);
          break;
        case 8:
          M5.Beep.tone((165 * oct + (lastEvent == 18)), 100);
          delay(100);
          M5.Beep.tone((147 * oct + (lastEvent == 18)), 100);
          break;
        case 9:
          M5.Beep.tone((196 * oct + (lastEvent == 19)), 100);
          delay(100);
          M5.Beep.tone((165 * oct + (lastEvent == 19)), 100);
          break;

        case 10:
          M5.Beep.tone((220 * oct), 100);
          delay(100);
          M5.Beep.tone((988 * oct), 100);
          break;
        case 11:
          M5.Beep.tone((220 * oct), 100);
          delay(100);
          M5.Beep.tone((783 * oct), 100);
          break;

        case 12:
          M5.Beep.tone((220 * oct), 100);
          delay(100);
          M5.Beep.tone((247 * oct), 100);
          break;
        case 13:
          M5.Beep.tone((220 * oct), 100);
          delay(100);
          M5.Beep.tone((196 * oct), 100);
          break;
      }
    }
  } else {
    digitalWrite(M5_LED, HIGH);  // led off
  }
  old_state = state;

  disp();

  checkButtons();

  if ((sleepr) && (millis() - lastActivityTime >= sleepr * 1000)) {
    M5.Axp.ScreenBreath(0);
  }
}
void tDisp() {
  M5.Lcd.setTextColor(YELLOW, BLACK);
  M5.Lcd.printf("\n t+");
  if (lastEvent == 4) M5.Lcd.setTextColor(WHITE, BLACK);
  M5.Lcd.printf("%2d", event[4]);
  M5.Lcd.setTextColor(YELLOW, BLACK);
  M5.Lcd.printf(" t-");
  if (lastEvent == 7) M5.Lcd.setTextColor(WHITE, BLACK);
  M5.Lcd.printf("%2d ", event[7]);
  M5.Lcd.setTextColor(CYAN, BLACK);
  M5.Lcd.printf("\n h+");
  if (lastEvent == 5) M5.Lcd.setTextColor(WHITE, BLACK);
  M5.Lcd.printf("%2d", event[5]);
  M5.Lcd.setTextColor(CYAN, BLACK);
  M5.Lcd.printf(" h-");
  if (lastEvent == 8) M5.Lcd.setTextColor(WHITE, BLACK);
  M5.Lcd.printf("%2d ", event[8]);
  M5.Lcd.setTextColor(MAGENTA, BLACK);
  M5.Lcd.printf("\n p+");
  if (lastEvent == 6) M5.Lcd.setTextColor(WHITE, BLACK);
  M5.Lcd.printf("%2d", event[6]);
  M5.Lcd.setTextColor(MAGENTA, BLACK);
  M5.Lcd.printf(" p-");
  if (lastEvent == 9) M5.Lcd.setTextColor(WHITE, BLACK);
  M5.Lcd.printf("%2d ", event[9]);
}

void disp() {  //////////////// DISPLAY

  M5.Lcd.setTextSize(2);
  M5.Lcd.setCursor(0, 0);
  M5.Lcd.setTextColor(BLACK, M5.Lcd.color565(192, 192, 128));
  M5.Lcd.print(id % 10);
  M5.Lcd.setTextColor(BLACK, M5.Lcd.color565(128, 128, 255));
  M5.Lcd.print("v");
  M5.Lcd.print(oct);
  if (scaleOn) {
    M5.Lcd.setTextColor(BLACK, M5.Lcd.color565(0, 255, 128));
    M5.Lcd.printf("s%1.0f", ssens * 100);
  } else if (remOn) {
    M5.Lcd.setTextColor(BLACK, M5.Lcd.color565(0, 255, 128));
    M5.Lcd.printf("r%1.0d", rsens);
  }
  if (tempOn || temp2On) {
    M5.Lcd.setTextColor(BLACK, YELLOW);
    M5.Lcd.printf("t%1.0f", tsens * 1000 / 5 - 4);
  }
  if (!scaleOn && !tempOn && !temp2On && !remOn) {
    M5.Lcd.setTextColor(BLACK, ORANGE);
    M5.Lcd.printf(" ");
  }
  if ( ! (scaleOn && tempOn) && ! (tempOn && remOn)) {
    M5.Lcd.setTextColor(BLACK, ORANGE);
    M5.Lcd.printf("a%1.0f", sens * 1000 / 5 - 3);
  }
  if (!scaleOn && !tempOn && !temp2On && !remOn) M5.Lcd.printf(" ");
  float charge = M5.Axp.GetBatCurrent();
  int bat;
  if (charge > 0) {  // 86.4
    M5.Lcd.setTextColor(BLACK, RED);
    bat = getBatteryLevel() - 1;
  } else {
    M5.Lcd.setTextColor(BLACK, WHITE);
    bat = getBatteryLevel();
  }
  if (bat == 100) {
    M5.Lcd.setTextColor(BLACK, GREEN);
  }
  M5.Lcd.printf("b%3d", bat);

  // Serial.println(String(charge) + " " + String(M5.Axp.GetVbatData()));

  M5.lcd.drawLine(0, 15, M5.lcd.width(), 15, BLACK);

  if (lastTimer) {                     ////// ALERTS       // large alert on screen that a parameter exceeding threshold event has occured
    lastTimer--;                       //  timer for how many screen refesh cycles to show the alert
    if (lastTimer == alertHold - 1) {  // draw on first timer cycle
      btGo();
      wake();
      M5.Lcd.setCursor(30, 30);
      M5.Lcd.setTextSize(100);
      switch (lastEvent) {
        case 0:
          lastTimer = 0;
          break;
        case 1:
          M5.lcd.fillRect(0, 16, M5.lcd.width() - 3, 80, RED);
          M5.Lcd.setTextColor(BLACK, RED);
          M5.Lcd.printf("X");
          break;
        case 2:
          M5.lcd.fillRect(0, 16, M5.lcd.width() - 3, 80, GREEN);
          M5.Lcd.setTextColor(BLACK, GREEN);
          M5.Lcd.printf("Y");
          break;
        case 3:
          M5.lcd.fillRect(0, 16, M5.lcd.width() - 3, 80, BLUE);
          M5.Lcd.setTextColor(BLACK, BLUE);
          M5.Lcd.printf("Z");
          break;
        case 4:
        case 7:
          M5.lcd.fillRect(0, 16, M5.lcd.width() - 3, 80, YELLOW);
          M5.Lcd.setTextColor(BLACK, YELLOW);
          M5.Lcd.printf("T");
          break;
        case 5:
        case 8:
          M5.lcd.fillRect(0, 16, M5.lcd.width() - 3, 80, CYAN);
          M5.Lcd.setTextColor(BLACK, CYAN);
          M5.Lcd.printf("H");
          break;
        case 6:
        case 9:
          M5.lcd.fillRect(0, 16, M5.lcd.width() - 3, 80, MAGENTA);
          M5.Lcd.setTextColor(BLACK, MAGENTA);
          M5.Lcd.printf("P");
          break;
        case 10:
          M5.lcd.fillRect(0, 16, M5.lcd.width() - 3, 80, TFT_GREENYELLOW);
          M5.Lcd.setTextColor(BLACK, TFT_GREENYELLOW);
          M5.Lcd.setTextSize(2);
          M5.Lcd.setCursor(30, 5);
          M5.Lcd.printf("\n   %.3f", weight);
          M5.Lcd.setTextSize(100);
          M5.Lcd.setCursor(5, 37);
          M5.Lcd.printf("YES");
          scalib = false;
          break;
        case 11:
          M5.lcd.fillRect(0, 16, M5.lcd.width() - 3, 80, ORANGE);
          M5.Lcd.setTextColor(BLACK, ORANGE);
          M5.Lcd.setTextSize(2);
          M5.Lcd.setCursor(30, 5);
          M5.Lcd.printf("\n   %.3f", abs(weight));
          M5.Lcd.setTextSize(100);
          M5.Lcd.setCursor(30, 37);
          M5.Lcd.printf("NO");
          scalib = false;
          break;
        case 12:
          M5.lcd.fillRect(0, 16, M5.lcd.width() - 3, 80, TFT_GREENYELLOW);
          M5.Lcd.setTextColor(BLACK, TFT_GREENYELLOW);
          M5.Lcd.setTextSize(2);
          M5.Lcd.setCursor(30, 5);
          M5.Lcd.printf("\n   %3d%%", ysn);
          M5.Lcd.setTextSize(100);
          M5.Lcd.setCursor(5, 37);
          M5.Lcd.printf("YES");
          break;
        case 13:
          M5.lcd.fillRect(0, 16, M5.lcd.width() - 3, 80, ORANGE);
          M5.Lcd.setTextColor(BLACK, ORANGE);
          M5.Lcd.setTextSize(2);
          M5.Lcd.setCursor(30, 5);
          M5.Lcd.printf("\n   %3d%%", ysn);
          M5.Lcd.setTextSize(100);
          M5.Lcd.setCursor(30, 37);
          M5.Lcd.printf("NO");
          break;
      }
      if ((lastEvent >= 1 && lastEvent < 10) || (lastEvent >= 14 && lastEvent < 20)) {
        if (pol) {
          M5.Lcd.printf("+\n");
        } else {
          M5.Lcd.printf("-\n");
        }
        M5.Lcd.setTextSize(1);
        if (lastEvent >= 1 && lastEvent < 4) {
          M5.Lcd.print(" accelerometer motion");
        } else if ((lastEvent == 4) || (lastEvent == 7) || (lastEvent == 4) || (lastEvent == 7)) {
          M5.Lcd.print(" temperature change");
        } else if ((lastEvent == 5) || (lastEvent == 8) || (lastEvent == 5) || (lastEvent == 8)) {
          M5.Lcd.print(" humidity change");
        } else if ((lastEvent == 6) || (lastEvent == 9) || (lastEvent == 6) || (lastEvent == 9)) {
          M5.Lcd.print(" barometric pressure");
        }
      }
    } else  // still alert hold, but not the first cycle
      if (((lastEvent == 10) || (lastEvent == 11)) && (!press)) {
        if (lastEvent == 10) M5.Lcd.setTextColor(BLACK, TFT_GREENYELLOW);
        if (lastEvent == 11) M5.Lcd.setTextColor(BLACK, ORANGE);
        M5.Lcd.setTextSize(2);
        M5.Lcd.setCursor(30, 5);
        M5.Lcd.printf("\n   %.3f", abs(weight));
      }
  } else {
    press = false;
    M5.Lcd.setTextColor(WHITE, BLACK);
    if (!tempOn) {
      M5.Lcd.printf("\n Event:");
      M5.Lcd.printf("%3d ", event[0]);
    } else {
      M5.Lcd.printf("e%2d", event[0]);
    }

    if (remOn) {
      if (calibRem) {
        M5.Lcd.setTextColor(BLACK, RED);
        M5.Lcd.print("\n calibrate ");
      } else {
        M5.Lcd.setTextColor(TFT_GREENYELLOW, BLACK);
        M5.Lcd.printf("\n Y:");
        if (lastEvent == 12) M5.Lcd.setTextColor(WHITE, BLACK);
        M5.Lcd.printf("%2d", event[12]);
        M5.Lcd.setTextColor(ORANGE, BLACK);
        M5.Lcd.printf(" N:");
        if (lastEvent == 13) M5.Lcd.setTextColor(WHITE, BLACK);
        M5.Lcd.printf("%2d ", event[13]);
      }
      M5.Lcd.setTextColor(CYAN, BLACK);
      M5.Lcd.printf(" %3d%%", percentagePin36);   // yes
      M5.Lcd.printf(" %3d%% ", percentagePin26);  // no
      M5.Lcd.setTextColor(MAGENTA, BLACK);
      M5.Lcd.printf("\n %4d", pulsesPerSecondPin36);
      M5.Lcd.printf(" %4d ", pulsesPerSecondPin26);
    } else if (scaleOn) {
      if (!scalib) {
        M5.Lcd.setTextColor(BLACK, RED);
        M5.Lcd.print("\n calibrate ");
      } else {
        M5.Lcd.setTextColor(M5.Lcd.color565(0, 255, 128), BLACK);
        M5.Lcd.print("\nnow");
        if (weight >= 0) M5.Lcd.print(" ");
        if ((weight >= 10) || (weight <= -10)) {
          M5.Lcd.printf("%.4f", weight);
        } else {
          M5.Lcd.printf("%.5f", weight);
        }
      }
      M5.Lcd.setTextColor(TFT_GREENYELLOW, BLACK);
      M5.Lcd.print("hi");
      if (maxWeight >= 0) M5.Lcd.print(" ");
      if ((maxWeight >= 10) || (maxWeight <= -10)) {
        M5.Lcd.printf("%.1f", maxWeight);
      } else {
        M5.Lcd.printf("%.2f", maxWeight);
      }
      M5.Lcd.print(" Y");
      if (lastEvent == 7) M5.Lcd.setTextColor(WHITE, BLACK);
      M5.Lcd.printf("%2d", event[10]);
      M5.Lcd.setTextColor(ORANGE, BLACK);
      M5.Lcd.print("lo");
      if (minWeight >= 0) M5.Lcd.print(" ");
      if ((minWeight >= 10) || (minWeight <= -10)) {
        M5.Lcd.printf("%.1f", minWeight);
      } else {
        M5.Lcd.printf("%.2f", minWeight);
      }
      M5.Lcd.print(" N");
      if (lastEvent == 8) M5.Lcd.setTextColor(WHITE, BLACK);
      M5.Lcd.printf("%2d", event[11]);
    } else if (tempOn || temp2On) {
      tDisp();
    } else {
      M5.Lcd.print("                                 ");
    }
    if ((scaleOn && temp2On) || (temp2On && remOn)) {
      M5.Lcd.setTextColor(YELLOW, BLACK);
      M5.Lcd.printf("\n");
      if (lastEvent == 4) M5.Lcd.setTextColor(WHITE, BLACK);
      M5.Lcd.printf("%1d", event[4]);
      M5.Lcd.setTextColor(YELLOW, BLACK);
      M5.Lcd.printf("-");
      if (lastEvent == 7) M5.Lcd.setTextColor(WHITE, BLACK);
      M5.Lcd.printf("%1d ", event[7]);
      M5.Lcd.setTextColor(CYAN, BLACK);
      if (lastEvent == 5) M5.Lcd.setTextColor(WHITE, BLACK);
      M5.Lcd.printf("%1d", event[5]);
      M5.Lcd.setTextColor(CYAN, BLACK);
      M5.Lcd.printf("-");
      if (lastEvent == 8) M5.Lcd.setTextColor(WHITE, BLACK);
      M5.Lcd.printf("%1d", event[8]);
      M5.Lcd.setTextColor(MAGENTA, BLACK);
      M5.Lcd.printf(" ");
      if (lastEvent == 6) M5.Lcd.setTextColor(WHITE, BLACK);
      M5.Lcd.printf("%1d", event[6]);
      M5.Lcd.setTextColor(MAGENTA, BLACK);
      M5.Lcd.printf("-");
      if (lastEvent == 9) M5.Lcd.setTextColor(WHITE, BLACK);
      M5.Lcd.printf("%1d", event[9]);
    } else {
      M5.Lcd.setTextColor(RED, BLACK);
      M5.Lcd.printf("\nx");
      if (lastEvent == 1) M5.Lcd.setTextColor(WHITE, BLACK);
      M5.Lcd.printf("%2d", event[1]);
      M5.Lcd.setTextColor(GREEN, BLACK);
      M5.Lcd.printf(" y");
      if (lastEvent == 2) M5.Lcd.setTextColor(WHITE, BLACK);
      M5.Lcd.printf("%2d", event[2]);
      M5.Lcd.setTextColor(BLUE, BLACK);
      M5.Lcd.printf(" z");
      if (lastEvent == 3) M5.Lcd.setTextColor(WHITE, BLACK);
      M5.Lcd.printf("%2d", event[3]);
    }

    if (tempOn || temp2On) {  // tiny temp
      M5.Lcd.setTextSize(1);
      M5.Lcd.setCursor(36, 16);
      M5.Lcd.setTextColor(YELLOW, BLACK);
      M5.Lcd.printf("t:");
      if ((lastEvent == 4) || (lastEvent == 7)) M5.Lcd.setTextColor(WHITE, BLACK);
      M5.Lcd.printf("%.2fC %.2fF ", temp - tcal, ((temp - tcal) * 1.8) + 32);
      M5.Lcd.setCursor(36, 24);
      M5.Lcd.setTextColor(CYAN, BLACK);
      M5.Lcd.printf("h:");
      if ((lastEvent == 5) || (lastEvent == 8)) M5.Lcd.setTextColor(WHITE, BLACK);
      M5.Lcd.printf("%.2f", humi);
      M5.Lcd.print("%");
      M5.Lcd.setTextColor(MAGENTA, BLACK);
      //pres = pres +10000; //test display
      if (pres < 100000) M5.Lcd.print(" ");
      M5.Lcd.printf("p:");
      if ((lastEvent == 6) || (lastEvent == 9)) M5.Lcd.setTextColor(WHITE, BLACK);
      M5.Lcd.printf("%5.0f", pres);
    }
    if ((scaleOn) && (!scalib)) {
      calibScale();
      scalib = true;
    }
  }

  //// HISTOGRAM

  int s = 20;
  int t = 120;

  M5.Lcd.drawLine(tick + 1, t - s - 4, tick + 1, M5.Lcd.height(), WHITE);
  M5.Lcd.drawLine(tick, t - s - 4, tick, M5.Lcd.height(), BLACK);
  t = t - 10;
  if ((remOn) || (scaleOn)) {
    if (tick % 4 == 0) {
      M5.Lcd.drawPixel(tick, t, TFT_LIGHTGREY);
      M5.Lcd.drawPixel(tick, t + (s * 2), TFT_LIGHTGREY);
    }
  }
  if (remOn) {
    if (calibRem) {
      M5.Lcd.drawLine(tick, t + s, tick, t + s - 30 + (percentagePin36 * 30) / 100, TFT_GREENYELLOW);
      M5.Lcd.drawLine(tick, t + s, tick, t + s + 30 - (percentagePin26 * 30) / 100, ORANGE);
    } else {
      M5.Lcd.drawLine(tick, t + s, tick, t + s - 30 + (percentagePin36 * 30) / 100, TFT_LIGHTGREY);
      M5.Lcd.drawLine(tick, t + s, tick, t + s + 30 - (percentagePin26 * 30) / 100, TFT_DARKGREY);
    }
    t = t + 70;
  } else if (scaleOn) {
    int sc = weight * 100;
    if (sc < 0) {
      M5.Lcd.drawLine(tick, t + s, tick, t + s + sc - (sc * 2), ORANGE);
    } else {
      M5.Lcd.drawLine(tick, t + s, tick, t + s + sc - (sc * 2), TFT_GREENYELLOW);
    }
    t = t + 70;
  }
  if (tempOn || temp2On) {
    M5.Lcd.drawLine(tick, t, tick, t + (oldt - temp) * 100, YELLOW);
    M5.Lcd.drawLine(tick, t + s, tick, t + s + (oldh - humi) * 10, CYAN);
    M5.Lcd.drawLine(tick, t + (s * 2), tick, t + (s * 2) + (oldp - pres), MAGENTA);
    t = t + 70;
  }
  if ( ! (scaleOn && tempOn) && ! (tempOn && remOn)) {
    M5.Lcd.drawLine(tick, t, tick, t + (accX - oldX) * 100, RED);
    M5.Lcd.drawLine(tick, t + s, tick, t + s + (oldY - accY) * 100, GREEN);
    M5.Lcd.drawLine(tick, t + (s * 2), tick, t + (s * 2) + (oldZ - accZ) * 100, BLUE);
  }
  //upper right blank area fix
  if (tick >= M5.lcd.width() - 3) M5.lcd.drawLine(tick, 0, tick, 96, BLACK);

  tick++;
  if (tick > 134) {
    tick = 0;
    if (scaleOn) scalib = false;
  }
}

void btGo() {
  if (btON && deviceConnected) {
    String data;
    switch (lastEvent) {
      case 1:
        data += "X";
        break;
      case 2:
        data += "Y";
        break;
      case 3:
        data += "Z";
        break;
      case 4:
      case 7:
        data += "T";
        break;
      case 5:
      case 8:
        data += "H";
        break;
      case 6:
      case 9:
        data += "P";
        break;
      case 10:
      case 12:
        data += "Yes";
        break;
      case 11:
      case 13:
        data += "No ";
        break;
    }
    if (lastEvent >= 1 && lastEvent < 10) {
      if (pol) {
        data += "+ ";
      } else {
        data += "- ";
      }
    }
    int sensi = sens * 1000 / 5 - 3;
    data += String(id) + ",e" + String(event[0]);
    if (tempOn) {
      int tsensi = tsens * 1000 / 5 - 4;
      int pr = pres;
      data += ", t+" + String(event[4]) + ",t-" + String(event[7]) + ",t:" + String(temp - tcal) + ", h+" + String(event[5]) + ",h-" + String(event[8]) + ",h:" + String(humi) + ", p+" + String(event[6]) + ",p-" + String(event[9]) + ",p:" + String(pr) + ",ts" + String(tsensi);
    }
    if (scaleOn) {
      int ssensi = ssens * 100;
      data += ", Now" + String(abs(weight)) + ",Y" + String(event[10]) + ",hi" + String(maxWeight) + ",N" + String(event[11]) + ",lo" + String(minWeight) + ",ss" + String(ssensi);
    }
    if (remOn) {
      data += ", Now" + String(ysn) + "%,Y" + String(event[12]) + "," + String(percentagePin36) + "%," + String(pulsesPerSecondPin36) + "Hz,N" + String(event[13]) + "," + String(percentagePin26) + "%," + String(pulsesPerSecondPin26) + "Hz,rs" + String(rsens);
    }

    data += ", x" + String(event[1]) + ",y" + String(event[2]) + ",z" + String(event[3]) + ",as" + String(sensi);
    data += ", br" + String(bright);
    data += ",mh" + String(cpu);
    data += ",sl" + String(sleepr);
    data += ",vo" + String(oct);

    int bat = getBatteryLevel();
    data += ",bat:" + String(bat);

    pCharacteristicData->setValue(data.c_str());
    pCharacteristicData->notify();

    Serial.println(data);
  }
}

// contents of logo.h
// logo image bitmap file (Arduino tab)

const unsigned char logo [] PROGMEM = {
	// 'two_faces_100b, 100x100px
	0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, 0x80, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0f, 0xff, 0xff, 0xff, 
	0xff, 0x7f, 0x00, 0x00, 0xe0, 0xff, 0xff, 0xff, 0xff, 0x0f, 0xff, 0xff, 0xff, 0xff, 0x07, 0x00, 
	0x00, 0x00, 0xfe, 0xff, 0xff, 0xff, 0x0f, 0xff, 0xff, 0xff, 0xff, 0x01, 0x00, 0x00, 0x00, 0xf0, 
	0xff, 0xff, 0xff, 0x0f, 0xff, 0xff, 0xff, 0x3f, 0x00, 0x00, 0x00, 0x00, 0xc0, 0xff, 0xff, 0xff, 
	0x0f, 0xff, 0xff, 0xff, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x0f, 0xff, 0xff, 
	0xff, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0xff, 0xff, 0x0f, 0xff, 0xff, 0xff, 0x00, 0x00, 
	0x00, 0x00, 0x00, 0x00, 0xf0, 0xff, 0xff, 0x0f, 0xff, 0xff, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, 
	0x00, 0xc0, 0xff, 0xff, 0x0f, 0xff, 0xff, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xff, 
	0xff, 0x0f, 0xff, 0xff, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x0f, 0xff, 
	0xff, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0xff, 0x0f, 0xff, 0xff, 0x01, 0x00, 
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0xff, 0x0f, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 
	0x00, 0x00, 0x00, 0xf0, 0xff, 0x0f, 0xff, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
	0xe0, 0xff, 0x0f, 0xff, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0xff, 0x0f, 
	0xff, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xff, 0x0f, 0xff, 0x1f, 0x00, 
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x0f, 0xff, 0x0f, 0x00, 0x00, 0x00, 0x00, 
	0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x0f, 0xff, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
	0x00, 0x00, 0xfe, 0x0f, 0xff, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 
	0x0f, 0xff, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x0f, 0xff, 0x01, 
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x0f, 0xff, 0x00, 0x00, 0x00, 0x00, 
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x0f, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
	0x00, 0x00, 0x00, 0xe0, 0x0f, 0x7f, 0x00, 0xe0, 0x07, 0xf8, 0xff, 0xff, 0x7f, 0x00, 0x00, 0x01, 
	0xe0, 0x0f, 0x3f, 0xf0, 0xff, 0x1f, 0xfc, 0xff, 0xff, 0xff, 0x00, 0xf0, 0x1f, 0xc0, 0x0f, 0x3f, 
	0xf0, 0xff, 0x1f, 0xf8, 0xff, 0xff, 0xff, 0x00, 0xfc, 0x7f, 0x80, 0x0f, 0x1f, 0xf0, 0xff, 0x0f, 
	0xf8, 0xff, 0xff, 0x7f, 0x00, 0xfe, 0xff, 0x80, 0x0f, 0x1f, 0x00, 0x38, 0x00, 0xf0, 0xf0, 0x3c, 
	0x78, 0x00, 0x1f, 0xf0, 0x81, 0x0f, 0x0f, 0x00, 0x38, 0x00, 0xf0, 0xe0, 0x3d, 0x3c, 0x00, 0x0f, 
	0xe0, 0x01, 0x0f, 0x0f, 0x00, 0x38, 0x00, 0xe0, 0xe1, 0x1f, 0x1e, 0x80, 0x07, 0xc0, 0x03, 0x0f, 
	0x0f, 0x00, 0x38, 0x00, 0xe0, 0xc1, 0x0f, 0x1e, 0x80, 0x03, 0xc0, 0x03, 0x0e, 0x07, 0x00, 0x38, 
	0x00, 0xc0, 0xc3, 0x0f, 0x0f, 0xc0, 0x83, 0x81, 0x03, 0x0e, 0x07, 0x00, 0x38, 0x00, 0x80, 0x83, 
	0x07, 0x0f, 0xc0, 0xc3, 0x83, 0x03, 0x0e, 0x03, 0x00, 0x38, 0x00, 0x80, 0x87, 0x87, 0x07, 0xc0, 
	0xc3, 0x87, 0x03, 0x0c, 0x03, 0x00, 0x38, 0x00, 0x00, 0xcf, 0x8f, 0x07, 0xc0, 0xc3, 0x83, 0x03, 
	0x0c, 0x03, 0x00, 0x38, 0x00, 0x00, 0xcf, 0xdf, 0x03, 0xc0, 0x03, 0x81, 0x03, 0x0c, 0x03, 0x00, 
	0x38, 0x00, 0x00, 0xfe, 0xff, 0x01, 0x80, 0x07, 0xc0, 0x03, 0x08, 0x01, 0x00, 0x38, 0x00, 0x00, 
	0xfe, 0xfc, 0x01, 0x80, 0x07, 0xc0, 0x03, 0x08, 0x01, 0x00, 0x38, 0x00, 0x00, 0xfc, 0xfc, 0x00, 
	0x00, 0x0f, 0xe0, 0x01, 0x08, 0x01, 0x00, 0x38, 0x00, 0x00, 0x78, 0xf8, 0x00, 0x00, 0x1f, 0xf8, 
	0x00, 0x08, 0x01, 0x00, 0x38, 0x00, 0x00, 0x78, 0x70, 0x00, 0x00, 0xfe, 0x7f, 0x00, 0x08, 0x01, 
	0x00, 0x38, 0x00, 0x00, 0x30, 0x30, 0x00, 0x00, 0xfc, 0x3f, 0x00, 0x00, 0x01, 0x00, 0x38, 0x00, 
	0x00, 0x10, 0x20, 0x00, 0x00, 0xf0, 0x1f, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
	0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xf0, 0x01, 0xfe, 
	0x03, 0x7e, 0x00, 0x00, 0x00, 0x80, 0xff, 0x00, 0x10, 0x00, 0xfc, 0x07, 0xff, 0x01, 0xff, 0x00, 
	0x00, 0x00, 0x80, 0x03, 0x00, 0x38, 0x00, 0x0e, 0x06, 0x07, 0x00, 0x7f, 0x00, 0x00, 0x01, 0x80, 
	0x03, 0x00, 0x78, 0x00, 0x07, 0x00, 0x07, 0x80, 0x1b, 0x00, 0x00, 0x01, 0x80, 0xff, 0x00, 0x7c, 
	0x00, 0x07, 0x00, 0xff, 0x83, 0x1f, 0x00, 0x00, 0x01, 0x80, 0x3f, 0x00, 0xee, 0x00, 0x43, 0x00, 
	0xff, 0x01, 0x3f, 0x00, 0x00, 0x01, 0x80, 0x03, 0x00, 0xc6, 0x00, 0xe3, 0x00, 0x07, 0x00, 0x7e, 
	0x00, 0x08, 0x01, 0x80, 0x7f, 0x00, 0x87, 0x01, 0xc3, 0x00, 0x1f, 0x00, 0xfc, 0x00, 0x08, 0x01, 
	0x80, 0xff, 0x00, 0x83, 0x03, 0x03, 0x00, 0xff, 0x03, 0xf8, 0x01, 0x08, 0x01, 0x80, 0x0f, 0x80, 
	0x03, 0x03, 0x07, 0x00, 0xff, 0x00, 0xd8, 0x01, 0x08, 0x03, 0x80, 0x01, 0x80, 0x03, 0x07, 0x0e, 
	0x00, 0x07, 0x00, 0xf9, 0x01, 0x08, 0x03, 0x80, 0x01, 0xc0, 0xff, 0x07, 0xfc, 0x07, 0x7f, 0x00, 
	0xff, 0x00, 0x0c, 0x03, 0x80, 0x01, 0xc0, 0xff, 0x07, 0xf8, 0x03, 0xfe, 0x03, 0x7e, 0x00, 0x0c, 
	0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x0c, 0x07, 0x00, 0x00, 
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x0c, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
	0x00, 0x00, 0x00, 0x0e, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
	0x0f, 0x0f, 0xc0, 0x81, 0xe0, 0x40, 0x90, 0x60, 0x70, 0x88, 0x41, 0x18, 0x00, 0x0f, 0x1f, 0xc0, 
	0xc3, 0xe0, 0x60, 0x90, 0xf0, 0x70, 0x88, 0x61, 0x18, 0x00, 0x0f, 0x1f, 0x40, 0xc2, 0xa0, 0x61, 
	0xb0, 0xd0, 0xd0, 0x88, 0x61, 0x18, 0x80, 0x0f, 0x3f, 0x40, 0xc2, 0xa0, 0x61, 0xb0, 0xd0, 0xd0, 
	0xd8, 0x61, 0x18, 0x80, 0x0f, 0x3f, 0x40, 0xc2, 0xa0, 0x61, 0xf0, 0xd0, 0xd0, 0xf8, 0x61, 0x18, 
	0xc0, 0x0f, 0x7f, 0xc0, 0xc3, 0xe1, 0xe0, 0xf0, 0xd0, 0x70, 0xf8, 0xe1, 0x18, 0xc0, 0x0f, 0x7f, 
	0xc0, 0x41, 0xe1, 0xa0, 0xf0, 0xd0, 0x70, 0xf8, 0xa1, 0x18, 0xe0, 0x0f, 0xff, 0x40, 0xe0, 0xa1, 
	0xf0, 0xd0, 0xd0, 0x50, 0xf8, 0xf1, 0x18, 0xf0, 0x0f, 0xff, 0x41, 0xe0, 0xa1, 0xf0, 0xd0, 0xd0, 
	0x50, 0xa8, 0xf1, 0x18, 0xf0, 0x0f, 0xff, 0x41, 0x20, 0xa1, 0x91, 0x90, 0xd0, 0xd0, 0xa8, 0x91, 
	0x18, 0xf8, 0x0f, 0xff, 0x43, 0x20, 0xa1, 0x91, 0x90, 0x70, 0xd0, 0x88, 0x91, 0xf8, 0xfc, 0x0f, 
	0xff, 0x07, 0x20, 0x20, 0x10, 0x10, 0x20, 0x10, 0x08, 0x10, 0x08, 0xfc, 0x0f, 0xff, 0x07, 0x00, 
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x0f, 0xff, 0x0f, 0x00, 0x00, 0x00, 0x00, 
	0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x0f, 0xff, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
	0x00, 0x80, 0xff, 0x0f, 0xff, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0xff, 
	0x0f, 0xff, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe0, 0xff, 0x0f, 0xff, 0xff, 
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0xff, 0x0f, 0xff, 0xff, 0x01, 0x00, 0x00, 
	0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0xff, 0x0f, 0xff, 0xff, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 
	0x00, 0x00, 0xfc, 0xff, 0x0f, 0xff, 0xff, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 
	0xff, 0x0f, 0xff, 0xff, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xff, 0xff, 0x0f, 0xff, 
	0xff, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0xff, 0xff, 0x0f, 0xff, 0xff, 0xff, 0x00, 
	0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0xff, 0xff, 0x0f, 0xff, 0xff, 0xff, 0x03, 0x00, 0x00, 0x00, 
	0x00, 0x00, 0xf8, 0xff, 0xff, 0x0f, 0xff, 0xff, 0xff, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 
	0xff, 0xff, 0x0f, 0xff, 0xff, 0xff, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x80, 0xff, 0xff, 0xff, 0x0f, 
	0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xf0, 0xff, 0xff, 0xff, 0x0f, 0xff, 0xff, 0xff, 
	0xff, 0x07, 0x00, 0x00, 0x00, 0xfe, 0xff, 0xff, 0xff, 0x0f, 0xff, 0xff, 0xff, 0xff, 0x3f, 0x00, 
	0x00, 0xc0, 0xff, 0xff, 0xff, 0xff, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0f, 0x00, 0xfe, 0xff, 
	0xff, 0xff, 0xff, 0x0f
};