diff --git a/battery.cpp b/battery.cpp
new file mode 100644
index 0000000..b7b79ac
--- /dev/null
+++ b/battery.cpp
@@ -0,0 +1,57 @@
+/*
+ Battery.cpp - Battery library
+ Copyright (c) 2014 Roberto Lo Giacco.
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation, either version 3 of the
+ License, or (at your option) any later version.
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+ */
+// https://github.com/rlogiacco/BatterySense
+
+#include "Battery.h"
+#include
+
+Battery::Battery(uint16_t minVoltage, uint16_t maxVoltage, uint8_t sensePin)
+{
+ this->sensePin = sensePin;
+ this->activationPin = 0xFF;
+ this->minVoltage = minVoltage;
+ this->maxVoltage = maxVoltage;
+}
+
+void Battery::begin(uint16_t refVoltage, float dividerRatio, mapFn_t mapFunction)
+{
+ this->refVoltage = refVoltage;
+ this->dividerRatio = dividerRatio;
+ pinMode(this->sensePin, INPUT);
+ if (this->activationPin < 0xFF)
+ pinMode(this->activationPin, OUTPUT);
+
+ this->mapFunction = mapFunction ? mapFunction : &linear;
+}
+
+void Battery::onDemand(uint8_t activationPin, uint8_t activationMode)
+{
+ if (activationPin < 0xFF) {
+ this->activationPin = activationPin;
+ this->activationMode = activationMode;
+ pinMode(this->activationPin, OUTPUT);
+ digitalWrite(activationPin, !activationMode);
+ }
+}
+
+uint8_t Battery::level(uint16_t voltage)
+{
+ if (voltage <= minVoltage)
+ return 0;
+ else if (voltage >= maxVoltage)
+ return 100;
+ else
+ return (*mapFunction)(voltage, minVoltage, maxVoltage);
+}
diff --git a/battery.h b/battery.h
new file mode 100644
index 0000000..a870ff9
--- /dev/null
+++ b/battery.h
@@ -0,0 +1,130 @@
+/*
+ Battery.h - Battery library
+ Copyright (c) 2014 Roberto Lo Giacco.
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation, either version 3 of the
+ License, or (at your option) any later version.
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+ */
+
+#ifndef BATTERY_H_
+#define BATTERY_H_
+
+#include
+
+typedef uint8_t(*mapFn_t)(uint16_t, uint16_t, uint16_t);
+
+class Battery {
+ public:
+ /**
+ * Creates an instance to monitor battery voltage and level.
+ * Initialization parameters depend on battery type and configuration.
+ *
+ * @param minVoltage is the voltage, expressed in millivolts, corresponding to an empty battery
+ * @param maxVoltage is the voltage, expressed in millivolts, corresponding to a full battery
+ * @param sensePin is the analog pin used for sensing the battery voltage
+ */
+ Battery(uint16_t minVoltage, uint16_t maxVoltage, uint8_t sensePin);
+
+ /**
+ * Initializes the library by optionally setting additional parameters.
+ * To obtain the best results use a calibrated reference using the VoltageReference library or equivalent.
+ *
+ * @param refVoltage is the board reference voltage, expressed in millivolts
+ * @param dividerRatio is the multiplier used to obtain the real battery voltage
+ * @param mapFunction is a pointer to the function used to map the battery voltage to the remaining capacity percentage (defaults to linear mapping)
+ */
+ void begin(uint16_t refVoltage, float dividerRatio, mapFn_t = 0);
+
+ /**
+ * Enables on-demand activation of the sensing circuit to limit battery consumption.
+ *
+ * @param activationPin is the pin which will be turned HIGH or LOW before starting the battery sensing
+ * @param activationMode is the optional value to set on the activationPin to enable battery sensing, defaults to LOW
+ * useful when using a resistor divider to save on battery consumption, but it can be changed to HIGH in case
+ * you are using a P-CH MOSFET or a PNP BJT
+ */
+ void onDemand(uint8_t activationPin, uint8_t activationMode = LOW);
+
+ /**
+ * Activation pin value disabling the on-demand feature.
+ */
+ static const uint8_t ON_DEMAND_DISABLE = 0xFF;
+
+ /**
+ * Returns the current battery level as a number between 0 and 100, with 0 indicating an empty battery and 100 a
+ * full battery.
+ */
+ uint8_t level();
+ uint8_t level(uint16_t voltage);
+
+ /**
+ * Returns the current battery voltage in millivolts.
+ */
+ uint16_t voltage();
+
+ private:
+ uint16_t refVoltage;
+ uint16_t minVoltage;
+ uint16_t maxVoltage;
+ float dividerRatio;
+ uint8_t sensePin;
+ uint8_t activationPin;
+ uint8_t activationMode;
+ mapFn_t mapFunction;
+};
+
+//
+// Plots of the functions below available at
+// https://www.desmos.com/calculator/x0esk5bsrk
+//
+
+/**
+ * Symmetric sigmoidal approximation
+ * https://www.desmos.com/calculator/7m9lu26vpy
+ *
+ * c - c / (1 + k*x/v)^3
+ */
+static inline uint8_t sigmoidal(uint16_t voltage, uint16_t minVoltage, uint16_t maxVoltage) {
+ // slow
+ // uint8_t result = 110 - (110 / (1 + pow(1.468 * (voltage - minVoltage)/(maxVoltage - minVoltage), 6)));
+
+ // steep
+ // uint8_t result = 102 - (102 / (1 + pow(1.621 * (voltage - minVoltage)/(maxVoltage - minVoltage), 8.1)));
+
+ // normal
+ uint8_t result = 105 - (105 / (1 + pow(1.724 * (voltage - minVoltage)/(maxVoltage - minVoltage), 5.5)));
+ return result >= 100 ? 100 : result;
+}
+
+/**
+ * Asymmetric sigmoidal approximation
+ * https://www.desmos.com/calculator/oyhpsu8jnw
+ *
+ * c - c / [1 + (k*x/v)^4.5]^3
+ */
+static inline uint8_t asigmoidal(uint16_t voltage, uint16_t minVoltage, uint16_t maxVoltage) {
+ uint8_t result = 101 - (101 / pow(1 + pow(1.33 * (voltage - minVoltage)/(maxVoltage - minVoltage) ,4.5), 3));
+ return result >= 100 ? 100 : result;
+}
+
+/**
+ * Linear mapping
+ * https://www.desmos.com/calculator/sowyhttjta
+ *
+ * x * 100 / v
+ */
+static inline uint8_t linear(uint16_t voltage, uint16_t minVoltage, uint16_t maxVoltage) {
+ return (unsigned long)(voltage - minVoltage) * 100 / (maxVoltage - minVoltage);
+}
+
+
+
+
+#endif // BATTERY_H_
diff --git a/display.cpp b/display.cpp
new file mode 100644
index 0000000..6259950
--- /dev/null
+++ b/display.cpp
@@ -0,0 +1,298 @@
+//============================================================================================
+// Display
+#include "display.hpp"
+
+String twoDigits(int digits)
+{
+ char str[16];
+ int res = snprintf(str, sizeof(str), "%02d", digits);
+ if (res >= sizeof(str) || res <= 0)
+ res = snprintf (str, sizeof(str), "00");
+
+ return String(str);
+}
+
+unsigned char batteryPicArray [] PROGMEM = {
+ 0xff,0x3f,0x01,0xe0,0x49,0x92,0x49,0x92,0x49,0x92,0x49,0x92,
+ 0x49,0x92,0x01,0xe0,0xff,0x3f
+};
+
+void Display::init()
+{
+ // U8G2_SSD1306_128X64_NONAME_F_HW_I2C _display1(U8G2_R0, /* reset=*/ U8X8_PIN_NONE, /* clock=*/ 22, /* data=*/ 21);
+ U8G2_SH1106_128X64_NONAME_F_HW_I2C _display1(U8G2_R0, U8X8_PIN_NONE, /* clock=*/ 22, /* data=*/ 21 );
+
+ _display = _display1;
+ _display.begin(/*Select=*/ GPIO_NUM_27, /*Right/Next=*/ GPIO_NUM_33, /*Left/Prev=*/ GPIO_NUM_14, /*Up=*/ U8X8_PIN_NONE, /*Down=*/ U8X8_PIN_NONE, /*Home/Cancel=*/ U8X8_PIN_NONE);
+ _display.setBusClock(400000);
+
+ // _display.setPowerSave(true);// kkkkkkK oatoff after test
+ tsLastDisplayUpdate = millis();
+ currentActivity = WATCH_ACTICITY;
+ bitmapInitialize();
+
+ // main_menu.init(_display);
+ setupMenu(&_display);
+}
+
+void Display::bitmapInitialize(){
+ batteryPic.set(batteryPicArray,16, 9);
+}
+
+void Display::showDigitalClock(int16_t x, int16_t y)
+{
+ String timenow = String(hour()) + ":" + twoDigits(minute());//+":"+twoDigits(second());
+ _display.setFont(u8g2_font_freedoomr25_tn);//(ArialMT_Plain_24);
+ _display.drawStr(x, y , timenow.c_str());
+}
+
+void Display::showTemperatureC(int16_t x, int16_t y, float therm, float pulse)
+{
+ String temp_str1 = String(therm) + "*C ";
+ String temp_str2 = String(pulse) + "bpm";
+
+ _display.setFont(u8g2_font_helvR08_te);
+ _display.drawStr(x, 62, temp_str1.c_str());
+ // _display.drawStr(x, 32, temp_str2.c_str());
+}
+
+void Display::showSteps(int16_t x, int16_t y, uint16_t steps)
+{
+ String steps_str = "steps: " + String (steps) ;
+
+ _display.setFont(u8g2_font_helvR08_te);
+ _display.drawUTF8(x, y, steps_str.c_str());
+}
+
+void Display::showBatteryLevel(uint8_t batLevel){
+// oled.display_pic(battery_pic.pic, battery_pic.width, battery_pic.height, true);
+ // int color = light_up? SSD1306_WHITE : SSD1306_BLACK;
+ // _display.setDrawColor(color);
+
+ _display.drawXBMP(105, 0, batteryPic.width, batteryPic.height, batteryPic.pic);
+ uint32_t _size = 14;
+ int w = (batLevel * _size) / 100;
+ _display.drawBox(106, 2, w, 5);
+
+ printf_w("-showBatteryLevel- %d %, %d\n", batLevel, w);
+
+ // oled.display_rect(107,2,last_battery_volt_level,5,lightup);
+}
+
+void Display::showWatchActivity(float therm, float pulse, uint16_t steps, uint8_t batLevel)
+{
+ showDigitalClock(0, 32);
+ showTemperatureC(90, 64, therm, pulse);
+ showSteps(0, 64, steps);
+ showBatteryLevel(batLevel);
+}
+
+const char pgmInfoHeader[] PROGMEM = "Sync watch time ";
+const char wifiPortalHeader[] PROGMEM = "Start Wifi Ap ";
+const char syncStatHeader[] PROGMEM = "Sync stat data";
+
+char dialogMessage[255];
+
+void Display::showSyncTimeMessage(CompletedHandlerFn completedHandler)
+{
+ snprintf(dialogMessage, sizeof(dialogMessage), "Try to get data\nfrom internet..");
+ showDialog(pgmInfoHeader, dialogMessage, completedHandler);
+ _display.clear();
+ renderer.exec();
+}
+
+void Display::showWifiApMessage(CompletedHandlerFn completedHandler)
+{
+ snprintf(dialogMessage,sizeof(dialogMessage), "Start WiFi portal\nAp name: %s\nPortal ip: 192.168.4.1", wifi_ap_name);
+ showDialog(pgmInfoHeader, dialogMessage, completedHandler );
+ _display.clear();
+ renderer.exec();
+}
+
+void Display::showWifiPortalMessage(CompletedHandlerFn completedHandler)
+{
+ snprintf(dialogMessage,sizeof(dialogMessage), "Ap name %s,\nPortal ip: 192.168.4.1", wifi_ap_name );
+ showDialog(wifiPortalHeader, dialogMessage, completedHandler );
+ _display.clear();
+ renderer.exec();
+}
+
+void Display::showSyncStatDataMessage(CompletedHandlerFn completedHandler)
+{
+ snprintf(dialogMessage,sizeof(dialogMessage), "Send statistics data\nto server..");
+ showDialog(syncStatHeader, dialogMessage, completedHandler);
+ _display.clear();
+ renderer.exec();
+}
+
+void Display::drawAxises(float _min, float _max, int _column_x, int _column_y, int step_x, int step_y, displayAsixType_t asixType)
+{
+ float _step = ((float)_max - (float) _min) / (float) countColumnY ;
+ float dig = (float) _max;
+ int i = 0;
+ int _hour = 0;
+ int count;
+
+ _display.drawLine(graphStartX, graphStartY, graphStartX, graphEndY);
+ _display.drawLine(graphEndX - 1, graphStartY, graphEndX-1, screenSizeY);
+
+ _display.setFont(u8g2_font_tom_thumb_4x6_mn);
+ _display.setDrawColor( 1 );
+
+ for (count = graphStartY + step_y/2 , i = 0; i < _column_y; count += step_y, dig -= (float)_step, i += 1) {
+ _display.drawLine(graphEndX, count, graphEndX - 2, count);
+ _display.drawLine(graphStartX, count, graphStartX + 2, count);
+ char buf[16];
+ _display.setCursor(graphEndX + 1, count + 3 );
+ if (SHOW_INT_TYPE == asixType)
+ snprintf(buf, sizeof(buf), "%d", (int) dig);
+ else
+ snprintf(buf, sizeof(buf), "%.1f", dig);
+
+ _display.print(buf);
+ // _display.drawStr(5, count - 3, dig);
+ }
+ // time axis
+ for (count = graphStartX + step_x/2, _hour = 0; _hour < _column_x; count += step_x, _hour += 1) {
+ _display.drawPixel(count, graphStartY + 1);
+ _display.drawPixel(count, graphEndY - 1);
+
+ _display.setCursor(count-2, graphEndY + 7 );
+ if (_hour % 2 )
+ _display.print(_hour+1);
+ }
+}
+
+void Display::drawCGraph(Graph *graph, boolean Redraw)
+{
+ if (!graph)
+ return;
+
+ double i;
+ double temp;
+ int _i = 0;
+ int count = 0;
+ int _column_x = graph->graphDataLen, _column_y = countColumnY;
+ int step_x = (graphEndX - graphStartX) / (_column_x);
+ int step_y = (graphEndY - graphStartY) / (_column_y);
+ float graph_v = graph->max() - graph->min();
+
+ printf_w("-- graph->graphDataLen %d, min %f, max %f\n\n", graph->graphDataLen, graph->min() , graph->max());
+
+ Serial.flush();
+
+ _display.drawBox(0, 0, 127 , 14);
+ _display.setFont(u8g2_font_t0_11b_tf);
+ _display.setDrawColor(0);
+ _display.drawStr(3, 13, graph->getTitle().c_str());
+
+ _display.setDrawColor(1);
+
+ for (count = graphStartX + step_x/2, _i = 0; _i < graph->graphDataLen; count += step_x, _i++) {
+ float len_line = ((graphEndY - 5 - graphStartY) * (graph->graphData[_i] - graph->min())) / graph_v;
+ float _pos_y_start = (graphEndY - len_line - 5);
+ float _pos_y_end = screenSizeY - 5;
+
+ _display.drawLine(count, _pos_y_start, count, _pos_y_end);
+ }
+
+ drawAxises(graph->min() , graph->max(), graph->graphDataLen, countColumnY, step_x, step_y, graph->getAsixType());
+}
+
+void Display::setCurrentActivity(displayActivity_t activity)
+{
+ currentActivity = activity;
+// printf_w("--- setCurrentActivity %d\n", activity);
+}
+
+void Display::update(float therm, float pulse, uint16_t steps, uint8_t batLevel, Graph *graph)
+{
+ // if (millis() - tsLastDisplayUpdate <= REPORTING_PERIOD_MS)
+ // return;
+
+ tsLastDisplayUpdate = millis();
+
+ if (WATCH_ACTICITY == currentActivity) {
+ _display.clearBuffer(); // clear the internal memory
+ showWatchActivity( therm, pulse, steps, batLevel);
+ _display.sendBuffer(); // transfer internal memory to the display
+ }
+ else
+ if (ICON_MENU_MAIN_ACTIVITY == currentActivity ||
+ GRAPH_STEPS_ACTIVITY == currentActivity ||
+ GRAPH_BODY_TEMP_ACTIVITY == currentActivity) {
+ //MENUDRAW_COMPLETE_REDRAW;
+
+ // redrawMode = MENUDRAW_COMPLETE_REDRAW;
+ taskManager.runLoop();
+ }
+}
+
+void Display::graphDraw(Graph *graph)
+{
+ _display.clearBuffer(); // clear the internal memory
+ drawCGraph(graph, true);
+ _display.sendBuffer(); // transfer internal memory to the display
+}
+
+void Display::setPowerSave(bool powerSave)
+{
+ _display.setPowerSave(powerSave);// kkkkkkK oatoff after test
+}
+
+//-----------
+
+
+void Graph::process()
+{
+ printf_w("Graph::process start\n\n");
+
+ max_v = 0;
+
+ for (int i = 0; i < graphDataLen; i++) {
+ max_v = graphData[i] > max_v ? graphData[i] : max_v;
+ }
+ min_v = max_v;
+ for (int i = 0; i < graphDataLen; i++) {
+ min_v = graphData[i] < min_v ? ((graphData[i] == 0)? min_v : graphData[i]) : min_v;
+ }
+ printf_w("Graph::process end\n");
+}
+
+void Graph::setDate(int _day, int _month, int _year, int _weekday)
+{
+ tmElements_t tme;
+ tme.Second = 0;
+ tme.Minute = 0;
+ tme.Hour = 0;
+ tme.Wday = _weekday; // day of week, sunday is day 1
+ tme.Day = _day;
+ tme.Month = _month; // Jan = 1 in tme, 0 in tm
+ tme.Year = (uint8_t) _year - 1970 ; // offset from 1970;
+
+ currentDate = makeTime(tme);
+
+ printf_w("--- setDate %d, _day %d, _month %d, _year %d \n", currentDate, _day, _month, _year);
+}
+
+void Graph::setData(struct hourStat_t *skim_log, skimrecord_idx_t idx, displayAsixType_t _asixType)
+{
+ if (skim_log->hoursInTotal <= 0) {
+ graphDataLen = 0;
+ return;
+ }
+ asixType = _asixType;
+ graphDataLen = 0;
+ memset(graphData, 0, sizeof(graphData));
+ printf_w("Graph::setData ---- hoursInTotal %d\n", skim_log->hoursInTotal);
+
+ for (int i = 0; i < skim_log->hoursInTotal; i++) {
+ // skim_log->byHours[i].print();
+ graphData[i] = skim_log->byHours[i].getField(idx);
+ printf_w("idx %d, i %d, data %f\n", idx, i, graphData[i]);
+ graphDataLen += 1;
+ }
+ printf_w("Graph::setData ---- graphDataLen %d\n", graphDataLen );
+
+ process();
+}
diff --git a/display.hpp b/display.hpp
new file mode 100644
index 0000000..dd072fd
--- /dev/null
+++ b/display.hpp
@@ -0,0 +1,120 @@
+#ifndef _DISPLAY_
+#define _DISPLAY_
+
+#include
+#include "tsh-watch_menu.h"
+#include
+#include "utils.h"
+#include "stat_records.hpp"
+
+static char *wifi_ap_name = "TshwatchAP";
+
+enum displayActivity_t {
+ WATCH_ACTICITY = 1 ,
+ ICON_MENU_MAIN_ACTIVITY,
+ SETTINGS_ACTIVITY,
+ GRAPH_BODY_TEMP_ACTIVITY,
+ GRAPH_STEPS_ACTIVITY,
+ CONNECT_WIFI_ACTIVITY,
+ START_WIFI_AP_ACTIVITY,
+};
+
+enum displayAsixType_t {
+ SHOW_INT_TYPE,
+ SHOW_FLOAT_TYPE,
+ SHOW_MAX_TYPE
+};
+
+class Graph {
+ float max_v;
+ float min_v;
+ String title;
+
+ time_t currentDate;
+ displayAsixType_t asixType;
+
+ public:
+
+ float graphData[24];
+ int graphDataLen;
+
+ void process();
+
+ time_t getDate() { return currentDate; };
+ void setDate(time_t _date) { currentDate = _date;};
+ void setDate( int _day, int _month, int _year, int _weekday);
+
+
+ void setData( struct hourStat_t *skim_log, skimrecord_idx_t idxm, displayAsixType_t _asixType);
+ void setTitle(String _title) { title = _title;};
+ String getTitle() { return title ;};
+
+ float max() {return max_v;};
+ float min() {return min_v;};
+ displayAsixType_t getAsixType() {return asixType;};
+
+ bool starting;
+ unsigned int encoderVal = 100;
+};
+
+class Bitmap{
+ public:
+ int height;
+ int width;
+ unsigned char *pic;
+ void set(unsigned char *pic, int width, int height){
+ this->pic = pic;
+ this->width = width;
+ this->height = height;
+ }
+};
+
+class Display {
+ private:
+ const int REPORTING_PERIOD_MS = 1000;
+ U8G2 _display;
+
+ int screenSizeX = 128;
+ int screenSizeY = 64;
+
+ int graphStartX = 0;
+ int graphStartY = 14;
+
+ int graphEndX = screenSizeX - 22;
+ int graphEndY = screenSizeY - 6;
+
+ int countColumnX = 24;
+ int countColumnY = 3;
+
+ displayActivity_t currentActivity;
+ Bitmap batteryPic;
+
+ uint32_t tsLastDisplayUpdate = 0;
+
+ void showBatteryLevel(uint8_t batLevel);
+ void showDigitalClock(int16_t x, int16_t y);
+ void showTemperatureC(int16_t x, int16_t y, float therm, float pulse);
+ void showSteps(int16_t x, int16_t y, uint16_t steps);
+
+ void showWatchActivity(float therm, float pulse, uint16_t steps, uint8_t batLevel);
+ public:
+ void init();
+ void bitmapInitialize();
+
+ void update(float therm, float pulse, uint16_t steps, uint8_t batLevel, Graph *graph);
+ void setPowerSave(bool powerSave);
+
+ displayActivity_t getCurrentActivity() { return currentActivity; };
+ void setCurrentActivity(displayActivity_t activity) ;//{ currentActivity = activity; };
+
+ void drawCGraph( Graph *graph, boolean Redraw);
+ void drawAxises(float _min, float _max, int _column_x, int _column_y, int step_x, int step_y, displayAsixType_t asixType) ;
+ void graphDraw(Graph *graph);
+
+ void showSyncTimeMessage(CompletedHandlerFn completedHandler) ;
+ void showWifiApMessage(CompletedHandlerFn completedHandler) ;
+ void showWifiPortalMessage(CompletedHandlerFn completedHandler) ;
+ void showSyncStatDataMessage(CompletedHandlerFn completedHandler);
+};
+
+#endif
diff --git a/get_libs.sh b/get_libs.sh
index c3a0cfb..dd6c80e 100755
--- a/get_libs.sh
+++ b/get_libs.sh
@@ -6,9 +6,9 @@ ARDUINO_LIB_DIR=~/Documents/Arduino/libraries/
#ARDUINO_LIB_DIR=~/Documents/tmp_test/
declare -a arr=(
- "git@github.com:pikot/MLX90615.git"
- "git@github.com:pikot/EmotiBit_BMI160.git"
- "git@github.com:Seeed-Studio/Accelerometer_And_Gyroscope_LSM6DS3.git"
+ "git@github.com:pikot/Arduino-MAX30100.git"
+ "git@github.com:arduino-libraries/NTPClient.git"
+ "git@github.com:pikot/WiFiManager.git"
)
function create_dir_ifnotexist {
diff --git a/hardware.cpp b/hardware.cpp
index 8b7a24f..f3d0d91 100644
--- a/hardware.cpp
+++ b/hardware.cpp
@@ -1,63 +1,49 @@
#include "hardware.hpp"
-#if defined(_USE_MLX90615_)
-#include
-#endif
-
-#include
-#include
-
RTC_DATA_ATTR uint8_t SensorState = SENSOR_AWAKE;
RTC_DATA_ATTR uint8_t ControlState = CONTROL_AWAKE;
-RTC_DATA_ATTR time_t next_scan_time = 0;
-RTC_DATA_ATTR time_t next_scan_time_stop = 0;
-RTC_DATA_ATTR StatRecord_t RTC_RECORDS[CACHE_RECORD_CNT];
-RTC_DATA_ATTR uint8_t StatRecord_cnt = 0;
+RTC_DATA_ATTR time_t nextScanTime = 0;
+
RTC_DATA_ATTR int8_t today = 0;
-String twoDigits(int digits) {
- if (digits < 10) {
- String i = '0'+String(digits);
- return i;
- }
- else {
- return String(digits);
- }
+
+RTC_DATA_ATTR float ina219_current = 0;
+RTC_DATA_ATTR uint32_t ina219_current_cnt = 0;
+
+//RTC_DATA_ATTR time_t wifi_last_connect = 0;
+//RTC_DATA_ATTR bool force_wifi_connection = true;
+RTC_DATA_ATTR uint8_t lastSkimlogUpdateHour = 0;
+
+void printPinStatus()
+{
+ for (int i = 0; i < 20; i++) {
+ int _State = digitalRead(i);
+
+ print_w("status: pin ");
+ print_w(i); print_w(" - "); print_w(_State); println_w("");
+ }
}
-void Hardware::serial_init(){
+void Hardware::serialInit()
+{
#ifdef _SERIAL_DEBUG_
Serial.begin(115200);
Serial.flush();
#endif
}
-void Hardware::time_init(){
- const uint8_t SPRINTF_BUFFER_SIZE = 32; // Buffer size for sprintf() //
- char inputBuffer[SPRINTF_BUFFER_SIZE]; // Buffer for sprintf()/sscanf() //
- while (!DS3231M.begin()) {
- println_w(F("Unable to find DS3231MM. Checking again in 3s."));
- delay(3000);
- }
- println_w(F("DS3231M initialized.")); // //
- //DS3231M.adjust(); // Set to library compile Date/Time //
- DateTime _now = DS3231M.now(); // get the current time //
- setTime(_now.unixtime());
+void Hardware::timeInit()
+{
+ sTime.init();
+ sTime.read();
- sprintf(inputBuffer,"%04d-%02d-%02d %02d:%02d:%02d", _now.year(), // Use sprintf() to pretty print //
- _now.month(), _now.day(), _now.hour(), _now.minute(), _now.second()); // date/time with leading zeros //
- println_w(inputBuffer); // Display the current date/time //
- //print_w(F("DS3231M chip temperature is ")); // //
- //print_w( DS3231M.temperature() / 100.0, 1); // Value is in 100ths of a degree //
- // println_w("\xC2\xB0""C");
- current_time = now();
+ currentTime = sTime.currentTime();
}
-bool Hardware::is_wake_by_deepsleep(esp_sleep_wakeup_cause_t wakeup_reason) {
-
+bool Hardware::isWakeByDeepsleep(esp_sleep_wakeup_cause_t wakeupReason)
+{
bool status = false;
- switch(wakeup_reason)
- {
+ switch (wakeupReason) {
case 1 :
case 2 :
case 3 :
@@ -69,398 +55,619 @@ bool Hardware::is_wake_by_deepsleep(esp_sleep_wakeup_cause_t wakeup_reason) {
}
return status;
}
-bool Hardware::is_new_day(){
- print_w("is_new = " );print_w(today);print_w(" " ); print_w(day()); print_w("\n" );
- if (today != day()){
+
+bool Hardware::isNewDay()
+{
+ if (today != day()) {
today = day();
return true;
}
return false;
}
-void Hardware::init() {
+
+
+void Hardware::init()
+{
setCpuFrequencyMhz(80);
- displaySleepTimer = millis();
+ esp_deep_sleep_disable_rom_logging();
- esp_sleep_wakeup_cause_t wakeup_reason = esp_sleep_get_wakeup_cause();
+ displaySleepTimer = millis();
- serial_init();
- print_fsm_state( __func__, __LINE__ );
+ wakeupReason = esp_sleep_get_wakeup_cause();
+ isAfterDeepsleep = isWakeByDeepsleep(wakeupReason);
- is_after_deepsleep = is_wake_by_deepsleep(wakeup_reason);
- if (false == is_after_deepsleep) {
- memset(&RTC_RECORDS, 0, sizeof(StatRecord_t));
- }
- print_all_stat(); //!!!!!!!!!!!!!!!!!!
- print_w("wakeup_reason "); println_w( esp_sleep_wake[wakeup_reason] );
- print_w("StatRecord_cnt "); println_w( StatRecord_cnt );
+ serialInit();
+ SPIFFS.begin(true); // need for logs or config
- if ( CONTROL_SLEEPING == ControlState && ESP_SLEEP_WAKEUP_EXT0 == wakeup_reason ){
- ControlState = CONTROL_AWAKE;
+ cfg.init(); // cfg -> settings
+ updateFromConfig(); // cfg -> hardware/// yep, pretty sheety and best case scenario is to have one place to sync directly
+
+ printFsmState( __func__, __LINE__ );
+ if (false == isAfterDeepsleep) {
+ memset(&RTC_RECORDS, 0, sizeof(statRecord_t));
+
+ setupUlp( wakeupReason );
+ runUlp();
}
- sTerm.init(); // MLX9061* demands magic with wire pins, so - it should run first, alltime :/
- time_init(); // timer should work alltime
- sCurrent.init(); // anyway i want to trace battery
- bool clearStep = ( !is_after_deepsleep || is_new_day() ) ;
- sGyro.init(clearStep); // Pedometer consume small energy, can wake alltime (convinient for steps observation)
- if ( SENSOR_SLEEPING == SensorState && next_scan_time <= current_time ) {
+ // print_all_stat(); //!!!!!!!!!!!!!!!!!!
+ printf_w("wakeupReason %s, statRecord_cnt %d\n", espSleepWake[wakeupReason], statRecord_cnt );
+
+ if (CONTROL_SLEEPING == ControlState && ESP_SLEEP_WAKEUP_EXT0 == wakeupReason)
+ ControlState = CONTROL_AWAKE;
+
+ timeInit(); // timer should work alltime
+
+ if (SENSOR_SLEEPING == SensorState && nextScanTime <= currentTime)
SensorState = SENSOR_AWAKE;
- }
- if ( SENSOR_AWAKE == SensorState ) {
- init_sensors();
- }
- if ( CONTROL_AWAKE == ControlState ) {
+ initSensors();
+ if (CONTROL_AWAKE == ControlState) {
display.init();
- control.init();
+ control.init();
}
-
- print_fsm_state( __func__, __LINE__);
+
+ printFsmState( __func__, __LINE__);
}
-void Hardware::init_sensors() {
+void Hardware::initSensors()
+{
+ sTerm.init();
+ sCurrent.init(&ina219_current);
sPulse.init();
+ sGyro.init(); // Pedometer consume small energy, can wake alltime (convinient for steps observation)
+
log_file.init();
+ printFsmState( __func__, __LINE__);
}
-void Hardware::print_fsm_state( const char *func_name, uint32_t line_number) {
- if (!debug_trace_fsm)
+/// https://www.analog.com/media/en/analog-dialogue/volume-44/number-2/articles/pedometer-design-3-axis-digital-acceler.pdf
+void Hardware::printFsmState( const char *funcName, uint32_t lineNumber)
+{
+ if (!debugTraceFsm)
return;
- print_w( func_name ); print_w(":"); print_w( line_number ); print_w("\t");
+ print_w( funcName ); print_w(":"); print_w( lineNumber ); print_w("\t");
print_w("SensorState: "); print_w( sensor_state_name[SensorState] ); print_w("\t");
print_w("ControlState: "); print_w( control_state_name[ControlState] ); print_w("\t");
println_w();
}
-void Hardware::print_stat(StatRecord_t *record) {
- print_w("Steps: "); print_w( record->Steps ); print_w("\t");
- print_w("Heart rate: ");print_w( record->HeartRate ); print_w("bpm / ");
- print_w("SpO2: "); print_w( record->SpO2); print_w("%\t");
- print_w("Ambient: "); print_w( record->AmbientTempC ); print_w("*C\t");
- print_w("Object: "); print_w( record->ObjectTempC ); print_w("*C\t");
- print_w("Vcc: "); print_w( record->Vcc ) ;
- println_w();
+void Hardware::printStat(statRecord_t *record)
+{
+ printf_w("Stat: Time '%d' Steps '%d', Heart rate '%f', SpO2: '%f', Ambient: '%f', Object: '%f', Vcc: '%f'\n",
+ record->Time,
+ record->Steps, record->HeartRate, record->SpO2,
+ record->AmbientTempC, record->ObjectTempC, record->Vcc
+ );
}
-void Hardware::print_all_stat() {
- println_w("print_all_stat");
- for (int i = 0; i < CACHE_RECORD_CNT; i++){
- print_w( i ); print_w( ". ");
- print_stat(&RTC_RECORDS[i]);
+
+void Hardware::printAllStat()
+{
+ println_w("printAllStat");
+ for (int i = 0; i < CACHE_RECORD_CNT; i++) {
+ print_w( i ); print_w( ". ");
+ printStat(&RTC_RECORDS[i]);
}
println_w();
}
-void Hardware::read_sensors(){
- sGyro.read_data();
- sTerm.read_data();
- sPulse.read_data();
- sCurrent.read_data();
+void Hardware::readSensors()
+{
+ sGyro.read();
+ sTerm.read();
+ sPulse.read();
+ sCurrent.read();
}
-void Hardware::update() {
- current_time = now();
-
- if ( SENSOR_SLEEPING == SensorState && next_scan_time <= current_time ) {
+void Hardware::updateSkimlog()
+{
+ printf_w("updateSkimlog hour %d, last_hour %d\n", hour(), lastSkimlogUpdateHour);
+
+ if (hour() == lastSkimlogUpdateHour)
+ return;
+
+ SkimData *skim_log = new SkimData( day(), month(), year() );
+ skim_log->process();
+ free(skim_log);
+ lastSkimlogUpdateHour = hour();
+}
+
+void Hardware::update()
+{
+ currentTime = now();
+
+ if (CONTROL_WIFI_INIT == ControlState)
+ wm.process();
+ /* --- return after test
+ if ( SENSOR_SLEEPING == SensorState && nextScanTime <= currentTime ) {
SensorState = SENSOR_AWAKE;
init_sensors();
}
- // print_fsm_state( __func__, __LINE__);
-
- if ( SENSOR_AWAKE == SensorState && (next_scan_time + pulse_threshold) <= current_time ) {
+ */
+
+ if (SENSOR_AWAKE == SensorState)
SensorState = SENSOR_WRITE_RESULT;
- }
- // print_fsm_state( __func__, __LINE__);
-
- if ( SENSOR_AWAKE == SensorState ) { // start collect and process data
- sPulse.update();
- sTerm.update();
- }
- // print_fsm_state( __func__, __LINE__);
+
+ printFsmState( __func__, __LINE__);
- if ( CONTROL_AWAKE == ControlState ){
- sGyro.read_data(); // ugly, read data only for prdometer debug
- StatRecord_t * r = get_last_sensor_data();
- display.update(r->ObjectTempC, r->HeartRate, sGyro.StepCount()); // return r->Step after debug
- }
- if ( SENSOR_WRITE_RESULT == SensorState ) {
- StatRecord_t current_rec;
- memset(¤t_rec, 0, sizeof(current_rec));
- current_sensor_data(¤t_rec);
+ if (CONTROL_AWAKE == ControlState) {
+ sGyro.read(); // ugly, read data only for prdometer
+ sCurrent.read();
- log_file.write_log( ¤t_rec );
- print_stat(¤t_rec);
- int next_wake = next_wake_time();
- next_scan_time = current_time + next_wake;
+ statRecord_t *r = getLastSensorsData();
+ display.update(r->ObjectTempC, r->HeartRate, sGyro.getStepCount(), sCurrent.getBatLevel(), &graph); // return r->Step after debug
+ }
+
+ if (SENSOR_WRITE_RESULT == SensorState) {
+ statRecord_t current_rec;
+ memset(¤t_rec, 0, sizeof(current_rec));
+ getCurrentSensorsData(¤t_rec);
+
+ log_file.writeLog( ¤t_rec ); ///!!!!!!!!!!!!!! return this string
+ /* --- return after test */
+
+ int next_wake = nextWakeTime();
+ nextScanTime = currentTime + next_wake;
SensorState = SENSOR_GOTOSLEEP;
+ updateSkimlog();
}
- // print_fsm_state( __func__, __LINE__);
}
-int Hardware::next_wake_time(){
+int Hardware::nextWakeTime()
+{
int rest = minute() % 1;
return (60 * rest) + 60 - second();
}
-void Hardware::WakeSensors() {
- powerSave = false;
- display.setPowerSave(powerSave); // off 4 test
- sPulse.wake();
- sCurrent.wake();
-}
-
-void Hardware::GoToSleep() {
- if ( CONTROL_GOTOSLEEP == ControlState ) {
- powerSave = true;
+void Hardware::goToSleep()
+{
+ if (CONTROL_GOTOSLEEP == ControlState) {
+ powerSave = true;
display.setPowerSave(powerSave);
ControlState = CONTROL_SLEEPING;
}
-
- if ( SENSOR_GOTOSLEEP == SensorState ) {
- sPulse.sleep();
- // term should go in sleep last, because 2 sleep need manipulation with scl and sda
+
+ if (SENSOR_GOTOSLEEP == SensorState) {
log_file.close();
+
SensorState = SENSOR_SLEEPING;
}
- if ( (CONTROL_SLEEPING == ControlState) && (SENSOR_SLEEPING == SensorState) ) {
- sCurrent.sleep();
- sTerm.sleep();
+ if ((CONTROL_SLEEPING == ControlState) && (SENSOR_SLEEPING == SensorState)) {
+ cfg.save();
+ control.initWakeup();
- control.init_wakeup();
-
- int next_wake = next_wake_time();
- print_w("next_wake "); println_w(next_wake);
+ int next_wake = nextWakeTime();
+ printf_w("next_wake %d\n", next_wake);
// esp_wifi_stop();
// esp_bt_controller_disable();
- esp_sleep_enable_timer_wakeup(next_wake * 1e6);
- esp_deep_sleep_start();
+ ESP_ERROR_CHECK(esp_sleep_enable_timer_wakeup(next_wake * 1e6));
+ // ESP_ERROR_CHECK( esp_sleep_enable_ulp_wakeup() );
+ esp_deep_sleep_start();
}
}
-StatRecord_t *Hardware::current_sensor_data(StatRecord_t *record) {
- read_sensors();
+statRecord_t *Hardware::getCurrentSensorsData(statRecord_t *record)
+{
+ readSensors();
- //StatRecord_t record;
- record->Time = current_time;
- record->Steps = sGyro.StepCount();
- record->HeartRate = sPulse.HeartRate();
- record->SpO2 = sPulse.SpO2();
- record->AmbientTempC = sTerm.AmbientC();
- record->ObjectTempC = sTerm.ObjectC();
- record->Vcc = sCurrent.Vcc();
+ //statRecord_t record;
+ record->Time = currentTime;
+ record->Steps = sGyro.getStepCount();
+ record->HeartRate = sPulse.getHeartRate();
+ record->SpO2 = sPulse.getSpO2();
+ record->AmbientTempC = sTerm.getAmbientC();
+ record->ObjectTempC = sTerm.getObjectC();
+ record->Vcc = sCurrent.getVcc();
return record;
}
-StatRecord_t *Hardware::get_last_sensor_data() {
+statRecord_t *Hardware::getLastSensorsData()
+{
uint8_t used_item = 0;
- if (0 == StatRecord_cnt)
+ if (0 == statRecord_cnt)
used_item = CACHE_RECORD_CNT - 1;
else
- used_item = StatRecord_cnt - 1;
+ used_item = statRecord_cnt - 1;
- if ( false == is_after_deepsleep )
+ if (false == isAfterDeepsleep)
used_item = 0;
- print_fsm_state( __func__, __LINE__);
- // print_w("used_item "); println_w( used_item ); //print_w(", ");
- print_stat( &RTC_RECORDS[ used_item ] );
+ printStat(&RTC_RECORDS[ used_item ]);
return &RTC_RECORDS[used_item];
}
-void Hardware::power_safe_logic() {
- print_fsm_state( __func__, __LINE__);
+struct tm * Hardware::changeDayFile(int32_t _day)
+{
+ time_t _temp_date = graph.getDate() + _day * 86400;
+
+ struct tm *tmstruct = localtime(&_temp_date);
+ int32_t y = (tmstruct->tm_year) + 1900;
+ int32_t m = (tmstruct->tm_mon) + 1;
+ int32_t d = tmstruct->tm_mday;
+ char fname[64];
+ int32_t fname_len = log_file.filenameDate(fname, sizeof(fname), y, m, d);
+ if (fname_len > 0) {
+ bool exist = SPIFFS.exists(fname);
+ if (exist) {
+ tmstruct->tm_hour = 0;
+ tmstruct->tm_min = 0;
+ tmstruct->tm_sec = 0;
+
+ tmElements_t tme = toTmElement(tmstruct);
+ time_t new_time = makeTime(tme);
+ graph.setDate( new_time) ;
+ return tmstruct;
+ }
+ }
+ return NULL;
+}
- if ( control.button_pressed() ) { // Call code button transitions from HIGH to LOW
- if (CONTROL_SLEEPING == ControlState) {
- display.init();
- }
- ControlState = CONTROL_AWAKE;
+struct tm * Hardware::loadNextDayFile()
+{
+ return changeDayFile(+1);
+}
- displaySleepTimer = millis();
- }
-/*
- print_w("millis ");print_w(millis()); print_w("\t");
- print_w("displaySleepTimer ");print_w(displaySleepTimer); print_w("\t");
- print_w("diff ");print_w((millis() - displaySleepTimer)); print_w("\t");
- print_w("displaySleepDelay ");print_w(displaySleepDelay); println_w("");
-*/
+struct tm * Hardware::loadPrevDayFile()
+{
+ return changeDayFile(-1);
+}
- if ( (CONTROL_AWAKE == ControlState) &&
- ((millis() - displaySleepTimer) > displaySleepDelay) ) {
- ControlState = CONTROL_GOTOSLEEP;
+void Hardware::runActivity(uint64_t _buttons)
+{
+ displayActivity_t _activity = display.getCurrentActivity();
+ if (WATCH_ACTICITY == _activity) {
+ bool pressed_menu = !(_buttons & (1ULL << control.pinRight())) ? true : false;
+ if (true == pressed_menu) {
+ cfg.setCurrenDayTimeToMenu();
+ display.setCurrentActivity(ICON_MENU_MAIN_ACTIVITY);
+ }
+ }
+ else
+ if (GRAPH_BODY_TEMP_ACTIVITY == _activity ||
+ GRAPH_STEPS_ACTIVITY == _activity) {
+ bool pressed_menu = !(_buttons & ( 1ULL << control.pinOk() ) ) ? true : false;
+ bool pressed_Left = !(_buttons & ( 1ULL << control.pinLeft() ) ) ? true : false;
+ bool pressed_Right= !(_buttons & ( 1ULL << control.pinRight() ) ) ? true : false;
+
+ if (true == pressed_menu) {
+ // renderer.giveBackDisplay();
+ display.setCurrentActivity(ICON_MENU_MAIN_ACTIVITY);
+
+ // menuMgr.setCurrentMenu(menuMgr.getParentAndReset());
+ returnToMenu();
+
+ }
+ else if (true == pressed_Left) {
+ struct tm *_day = loadNextDayFile();
+ if (_day != NULL) {
+ int32_t y = (_day->tm_year) + 1900;
+ int32_t m = (_day->tm_mon) + 1;
+ int32_t d = _day->tm_mday;
+
+ prepareGraphData(_activity, d, m, y);
+ }
+ }
+ else if (true == pressed_Right) {
+ struct tm *_day = loadPrevDayFile();
+ if (_day != NULL) {
+ int32_t y = (_day->tm_year) + 1900;
+ int32_t m = (_day->tm_mon) + 1;
+ int32_t d = _day->tm_mday;
+ prepareGraphData(_activity, d, m, y);
+ }
+ }
+ }
+ else
+ if(SETTINGS_ACTIVITY == _activity){
}
- GoToSleep();
}
-
-//============================================================================================
-// File System
-
-void FileSystem::init(){
- SPIFFS.begin(true);
+void Hardware::prepareGraphData(displayActivity_t act, int32_t _day, int32_t _month, int32_t _year)
+{
+ if (!IS_GRAPH_ACTIVITY(act))
+ return;
+
+ skimrecord_idx_t rec_idx;
+ char pattern_irl[32];
+ displayAsixType_t asixType;
- size_t totalBytes = 0, usedBytes = 0;
- totalBytes = SPIFFS.totalBytes();
- usedBytes = SPIFFS.usedBytes();
+ if (GRAPH_BODY_TEMP_ACTIVITY == act) {
+ rec_idx = SKIMREC_OBJT_IDX ;
+ snprintf(pattern_irl, sizeof(pattern_irl), "my body t\xB0: %d.%02d.%02d", _day, _month, _year % 100 );
+ asixType = SHOW_FLOAT_TYPE;
+ }
+ if (GRAPH_STEPS_ACTIVITY == act) {
+ rec_idx = SKIMREC_STEPS_IDX;
+ snprintf(pattern_irl, sizeof(pattern_irl), "my steps : %d.%02d.%02d", _day, _month, _year % 100 );
+ asixType = SHOW_INT_TYPE;
+ }
- String temp_str = "totalBytes = " + String(totalBytes) + ", usedBytes = " + String(usedBytes);
- println_w(temp_str);
+ graph.setTitle(String(pattern_irl));
- char fname[32];
- current_day_fname( fname, sizeof(fname), "/log", year(), month(), day() );
- // scan_log_dir("/log");
+ SkimData *skim_log = new SkimData(_day, _month, _year);
+ skim_log->process();
+ hourStat_t *stat = skim_log->getStat();
+
+ graph.setData( stat, rec_idx, asixType);
+ free(skim_log);
+}
+
+
+struct tm * Hardware::takeLogDayFile(unsigned int curValue)
+{
+ time_t _temp_date = now() - curValue * 86400; // can shit fappened in case new day
+
+ struct tm *tmstruct = localtime(&_temp_date);
+ int32_t y = (tmstruct->tm_year) + 1900;
+ int32_t m = (tmstruct->tm_mon) + 1;
+ int32_t d = tmstruct->tm_mday;
+ char fname[64];
+ int32_t fname_len = log_file.filenameDate(fname, sizeof(fname), y, m, d);
+ if (fname_len > 0) {
+ bool exist = SPIFFS.exists(fname);
+ if (exist) {
+ tmstruct->tm_hour = 0;
+ tmstruct->tm_min = 0;
+ tmstruct->tm_sec = 0;
+
+ tmElements_t tme = toTmElement(tmstruct);
+ time_t new_time = makeTime(tme);
+ graph.setDate(new_time) ;
+ return tmstruct;
+ }
+ }
+ return NULL;
+}
- char *modifier = "w";
- if (SPIFFS.exists(fname) )
- modifier = "a";
-
- if ( !_can_write )
+void Hardware::start_graph_logic(displayActivity_t _activity, unsigned int curValue)
+{
+ if (curValue == graph.encoderVal)
return;
- _file = SPIFFS.open(fname, modifier);
- if ( !_file ) {
- println_w("file open failed"); // "открыть файл не удалось"
+ graph.encoderVal = curValue;
+
+ struct tm *_day = takeLogDayFile(curValue);
+ if (_day != NULL) {
+ int32_t y = (_day->tm_year) + 1900;
+ int32_t m = (_day->tm_mon) + 1;
+ int32_t d = _day->tm_mday;
+ prepareGraphData(_activity, d, m, y );
}
- if ("w" == modifier)
- _file.print("V:1\n");
-
}
-char * FileSystem::current_day_fname(char *inputBuffer, int inputBuffer_size, char *dir, int16_t year, int8_t month, int8_t day) {
- snprintf(inputBuffer, inputBuffer_size,"%s/%04d-%02d-%02d.txt", dir, year, month, day);
+void Hardware::showGraph()
+{
+ display.graphDraw(&graph);
}
+
-#define MAX_FILES_CNT 6
-void FileSystem::cat_file(File f){
- while (f.available()) {
- write_w(f.read());
+void Hardware::showActivity(displayActivity_t act)
+{
+ if (GRAPH_BODY_TEMP_ACTIVITY == act || GRAPH_STEPS_ACTIVITY == act) {
+ prepareGraphData(act, day(), month(), year());
+ graph.setDate(day(), month(), year(), weekday()) ;
}
+
+ display.setCurrentActivity(act);
}
-void FileSystem::list_dir(fs::FS &fs, const char * dirname){
- File root = fs.open(dirname);
- if(!root){
- return;
- }
- if(!root.isDirectory()){
- return;
- }
+void Hardware::runPowerSafe()
+{
+ printFsmState(__func__, __LINE__);
+ printUlpStatus();
+ if (control.buttonPressed()) { // Call code button transitions from HIGH to LOW
+ if (CONTROL_SLEEPING == ControlState)
+ display.init();
+
+ uint64_t _buttons = control.buttons();
- File file = root.openNextFile();
- while(file){
- if(file.isDirectory()){
- continue;
- } else {
- print_w(" FILE: ");
- print_w(file.name());
- print_w("\tSIZE: ");
- println_w(file.size());
- cat_file(file);
-
- }
- file = root.openNextFile();
+ runActivity(_buttons);
+ ControlState = CONTROL_AWAKE;
+
+ displaySleepTimer = millis();
}
+
+ if ((CONTROL_AWAKE == ControlState) &&
+ ((millis() - displaySleepTimer) > displaySleepDelay))
+ ControlState = CONTROL_GOTOSLEEP;
+
+ // sleep(1);
+ goToSleep();
}
-void FileSystem::scan_log_dir(char* dir_name) {
- list_dir(SPIFFS, dir_name);
+//---- settings => config logic
+void Hardware::setTime(TimeStorage tm)
+{
+ sTime.updateTime(tm.hours, tm.minutes, tm.seconds);
}
-void FileSystem::save_records_to_file(){
- if ( !_can_write )
- return;
+void Hardware::setDate(DateStorage dt)
+{
+ sTime.updateDate(dt.day, dt.month, dt.year);
+}
- StatRecord_t *record = &RTC_RECORDS[0];
- for (int i = 0; i < StatRecord_cnt; i++, record++) {
- String temp_str = String(record->Time) + "\t" + String(record->Vcc) + String(record->Steps) + "\t" +
- String(record->HeartRate) + "\t" + String(record->AmbientTempC) + "\t" + String(record->ObjectTempC) + "\n" ;
- println_w(temp_str);
- _file.print(temp_str);
- }
+void Hardware::setTimezone(int tz, bool need_save)
+{
+ sTime.setTimeZone(enumIntTimeZone[tz]);
+ if (true == need_save) {
+ cfg.setTimezone(tz);
+ cfg.setSaveFlag(need_save);
+ }
}
-void FileSystem::write_log( StatRecord_t *record ){
-
- memcpy( (void*) &RTC_RECORDS[StatRecord_cnt], (void*) record, sizeof(StatRecord_t) );
-
- if (CACHE_RECORD_CNT == (StatRecord_cnt + 1)){
- save_records_to_file();
- StatRecord_cnt = 0;
- } else {
- StatRecord_cnt++;
+void Hardware::setTempSwitch(bool val, bool need_save)
+{
+ if (val)
+ sTerm.wake();
+ else
+ sTerm.sleep();
+ if (true == need_save) {
+ cfg.setTempSensorSwitch(val);
+ cfg.setSaveFlag(need_save);
}
}
-void FileSystem::close(){
- if ( !_can_write )
- return;
- _file.close();
+void Hardware::setPedoSwitch(bool val, bool need_save)
+{
+ if (val)
+ sGyro.wake();
+ else
+ sGyro.sleep();
+ if (true == need_save) {
+ cfg.setStepSensorSwitch(val);
+ cfg.setSaveFlag(need_save);
+ }
}
-//============================================================================================
-// Display
+void Hardware::updateFromConfig()
+{
+ setTempSwitch(cfg.getTempSensorSwitch(), false);
+ setPedoSwitch(cfg.getStepSensorSwitch(), false);
+ setTimezone(cfg.getTimezone(), false);
+}
-void Display::init() {
-// U8G2_SSD1306_128X32_UNIVISION_F_HW_I2C _display1(U8G2_R0, /* reset=*/ U8X8_PIN_NONE, /* clock=*/ 5, /* data=*/ 4);
- U8G2_SH1106_128X64_NONAME_F_HW_I2C _display1(U8G2_R0, U8X8_PIN_NONE );
- _display = _display1;
- _display.begin();
- _display.setBusClock( 400000 );
- //_display.setPowerSave(false);
- ts_last_display_update = millis();
+void onDialogFinished(ButtonType btnPressed, void* /*userdata*/)
+{
+ if (btnPressed == BTNTYPE_CLOSE) {
+// Serial.printf("\n\n\n---------------------------------- onDialogFinished BTNTYPE_CLOSE --------------------------------\n\n\n");
+ }
+ // Serial.printf("\n\n\n---------------------------------- onDialogFinished --------------------------------\n\n\n");
}
-void Display::show_digitalClock(int16_t x, int16_t y) {
- String timenow = String(hour()) + ":" + twoDigits(minute());//+":"+twoDigits(second());
- _display.setFont(u8g2_font_freedoomr25_tn);//(ArialMT_Plain_24);
- _display.drawStr( x, y , timenow.c_str() );
+void Hardware::syncTimeViaWifi()
+{
+ display.showSyncTimeMessage(onDialogFinished);
+
+ getTimeOverWifi();
}
-void Display::show_temperatureC(int16_t x, int16_t y, float therm, float pulse) {
- String temp_str1 = String (therm) + "*C ";
- String temp_str2 = String (pulse) + "bpm";
- _display.setFont(u8g2_font_helvR08_te);
- _display.drawStr(x, 14, temp_str1.c_str());
- _display.drawStr(x, 32, temp_str2.c_str());
+void Hardware::updateWebApiConfig()
+{
+ cfg.setServerUid(api_uid_server->getValue());
+ cfg.setServerToken(api_key_server->getValue());
+ cfg.setServerAddr(api_addr_server->getValue());
+
+ cfg.setSaveFlag(true);
}
-void Display::show_steps(int16_t x, int16_t y, uint16_t steps) {
- String steps_str = "steps: " + String (steps) ;
+void Hardware::showWifiPortal()
+{
+ display.showWifiPortalMessage(onDialogFinished);
+
+ WiFi.mode(WIFI_STA); // explicitly set mode, esp defaults to STA+AP
+ //reset settings - wipe credentials for testing
+ //wm.resetSettings();
+ // wm.setTimeout(120);
+ wm.setConfigPortalBlocking(false);
+
+ wm.setSaveConfigCallback(saveWifiConfigCallback);
+ wm.setBreakAfterConfig(true);
+
+ if (!api_uid_server) {
+ char * uid = cfg.getServerUid();
+ api_uid_server = new WiFiManagerParameter("API_uid", "API uid", uid, UID_SIZE);
+ }
+ if (!api_key_server) {
+ char * key = cfg.getServerToken();
+ api_key_server = new WiFiManagerParameter("API_key", "API key", key, TOKEN_SIZE);
+ }
+ if (!api_addr_server) {
+ char * addr = cfg.getServerAddr();
+ api_addr_server = new WiFiManagerParameter("STAT_srv", "Statistics server", addr, SERVER_ADDR_SIZE);
+ }
+
+ wm.addParameter(api_addr_server);
+ wm.addParameter(api_uid_server);
+ wm.addParameter(api_key_server);
- _display.setFont(u8g2_font_helvR08_te);
- _display.drawUTF8(x, y, steps_str.c_str());
+ if (!wm.startConfigPortal(wifi_ap_name, NULL)) {
+ ControlState = CONTROL_WIFI_INIT;
+ }
}
-void Display::update(float therm, float pulse, uint16_t steps){
- // if (millis() - ts_last_display_update <= REPORTING_PERIOD_MS)
- // return;
-// print_stat();
+void Hardware::getTimeOverWifi()
+{
+// wm.setDebugOutput(false);
- ts_last_display_update = millis();
+ WiFi.mode(WIFI_STA); // explicitly set mode, esp defaults to STA+AP
+ //reset settings - wipe credentials for testing
+ //wm.resetSettings();
+ // wm.setTimeout(120);
+ wm.setConfigPortalBlocking(false);
- _display.clearBuffer(); // clear the internal memory
- show_digitalClock( 0, 32);
- show_temperatureC( 90, 32, therm, pulse);
- show_steps(0, 50, steps);
- _display.sendBuffer(); // transfer internal memory to the display
+ if (wm.autoConnect(wifi_ap_name)) {
+ sTime.updateNtpTime();
+
+ display.setCurrentActivity(WATCH_ACTICITY);
+ ControlState = CONTROL_AWAKE;
+ displaySleepTimer = millis();
+ }
+ else {
+ display.showWifiApMessage(onDialogFinished);
+ ControlState = CONTROL_WIFI_INIT;
+ }
}
-void Display::setPowerSave(bool powerSave){
- _display.setPowerSave(powerSave);
-}
+void Hardware::syncStatViaWifi()
+{
+ display.showSyncStatDataMessage(onDialogFinished);
+
+ WiFi.mode(WIFI_STA); // explicitly set mode, esp defaults to STA+AP
+
+ wm.setConfigPortalBlocking(false);
+
+ if (wm.autoConnect(wifi_ap_name)){
+ String uid = String(cfg.getServerUid());
+ String token = String(cfg.getServerToken());
+ String addr = String(cfg.getServerAddr());
+
+ int32_t lastKnownData = getLastDate(addr, uid, token);
+ printf_w("lastKnownData -- %d\n", lastKnownData );
+ int32_t data_to_send[20];
+ int32_t data_to_send_len = getArrayLogfiles(lastKnownData, data_to_send, sizeof(data_to_send) );
+
+ for (int i =0; i < data_to_send_len ; i++) {
+ char pattern_url[64];
+ int32_t y, m, d;
+ int32_t date_to_send = data_to_send[i];
+
+ d = date_to_send % 100;
+ m = (date_to_send / 100) % 100;
+ y = date_to_send / 10000;
+
+ int32_t fname_len = log_file.filenameDate(pattern_url, sizeof(pattern_url), y, m, d);
+
+ if (fname_len > 0) {
+ sendStatFile(addr, uid, token, String(d) + "."+ String(m) +"." + String(y), String(pattern_url));
+ }
+ }
+
+ display.setCurrentActivity(WATCH_ACTICITY);
+ ControlState = CONTROL_AWAKE;
+ displaySleepTimer = millis();
+ }
+ else {
+ display.showWifiApMessage(onDialogFinished);
+ printf_w("Configportal running\n");
+ ControlState = CONTROL_WIFI_INIT;
+ }
+}
+//----
//============================================================================================
// Control
-void Control::init(){
+void Control::init()
+{
pinMode(LEFT_BUTTON,INPUT);
pinMode(RIGHT_BUTTON,INPUT);
pinMode(OK_BUTTON,INPUT);
@@ -484,15 +691,17 @@ void Control::init(){
*/
}
-void Control::print_button_state (const char *func_name, uint32_t line_number, int l_State, int r_State, int o_State ) {
- print_w("print_button_state -- ");
+void Control::printButtonState(const char *func_name, uint32_t line_number, int l_State, int r_State, int o_State)
+{
+ print_w("printButtonState -- ");
print_w( func_name ); print_w(":"); print_w( line_number ); print_w("\t");
print_w(LEFT_BUTTON); print_w(" - ");print_w(l_State); print_w(", ");
print_w(RIGHT_BUTTON); print_w(" - ");print_w(r_State); print_w(", ");
print_w(OK_BUTTON); print_w(" - ");print_w(o_State); println_w("");
}
-bool Control::button_pressed(){
+bool Control::buttonPressed()
+{
pinMode(LEFT_BUTTON,INPUT);
pinMode(RIGHT_BUTTON,INPUT);
pinMode(OK_BUTTON,INPUT);
@@ -507,22 +716,21 @@ bool Control::button_pressed(){
_Button_State |= 1ULL << RIGHT_BUTTON;
if (o_State)
_Button_State |= 1ULL << OK_BUTTON;
-
- if ( _Button_State != _Button_PrevState){
+
+ if (_Button_State != _Button_PrevState) {
_Button_PrevState = _Button_State;
- if ( !(_Button_State & (1ULL << LEFT_BUTTON) ) ||
- !(_Button_State & (1ULL << RIGHT_BUTTON) ) ||
- !(_Button_State & (1ULL << OK_BUTTON)) ) {
- return true;
+ if (!(_Button_State & (1ULL << LEFT_BUTTON)) ||
+ !(_Button_State & (1ULL << RIGHT_BUTTON)) ||
+ !(_Button_State & (1ULL << OK_BUTTON))) {
+ return true;
}
}
return false;
}
-
-void Control::init_wakeup(){
+void Control::initWakeup()
+{
esp_sleep_enable_ext0_wakeup(LEFT_BUTTON, 0);
-
/*
const uint64_t ext_wakeup_pin_1_mask = 1ULL << LEFT_BUTTON;
const uint64_t ext_wakeup_pin_2_mask = 1ULL << RIGHT_BUTTON;
@@ -531,5 +739,178 @@ void Control::init_wakeup(){
esp_sleep_enable_ext1_wakeup(ext_wakeup_pin_mask, ESP_EXT1_WAKEUP_ANY_HIGH);
*/
+}
+
+
+//============================================================================================
+// Config
+void Config::init()
+{
+ load();
+ setToMenu();
+}
+
+void Config::load()
+{
+ File file = SPIFFS.open(filename);
+ printf_w("%s, size %d\n", filename, file.size());
+/*
+ while (file.available()) {
+ write_w(file.read());
+ }
+ printf_w("\n");
+*/
+ // Allocate a temporary JsonDocument
+ // Don't forget to change the capacity to match your requirements.
+ // Use arduinojson.org/v6/assistant to compute the capacity.
+ StaticJsonDocument<512> doc;
+
+ DeserializationError error = deserializeJson(doc, file);
+ if (error) {
+ printf_w("Failed to read file, using default configuration. %p", file);
+ temp_sensor_switch = true;
+ step_sensor_switch = true;
+ timezone = 6;
+ auto_update_time_over_wifi = false;
+ file.close();
+ return;
+ }
+ // Copy values from the JsonDocument to the Config
+ temp_sensor_switch = doc["temp_switch"];
+ step_sensor_switch = doc["step_switch"];
+ timezone = doc["timezone"];
+ auto_update_time_over_wifi = doc["auto_update_time"];
+ strcpy(server_uid, doc["server_uid"]);
+ strcpy(server_token, doc["server_token"]);
+
+ if ( doc["server_addr"])
+ strcpy(server_addr, doc["server_addr"]);
+
+ file.close();
+
+ printf_w("Config load: server_uid %s, server_token %s\n", server_uid, server_token);
+ serializeJson(doc, Serial); // {"hello":"world"}
+ printf_w("\n");
+
+ updateUlpConfig();
+}
+
+void Config::updateUlpConfig()
+{
+ uint16_t switch_extern = 0;
+ if (temp_sensor_switch) {
+ switch_extern |= SENSOR_INA219;
+ }
+ if (step_sensor_switch) {
+ switch_extern |= SENSOR_INA219;
+ }
+
+ if (ulp_sensors_switch_extern != switch_extern) {
+ ulp_sensors_switch_extern = switch_extern;
+ }
+}
+
+void Config::save()
+{
+ // Delete existing file, otherwise the configuration is appended to the file
+ printf_w("Config save, need_save - '%s'\n", need_save? "y" : "n" );
+ if (!need_save)
+ return;
+
+ printf_w("Config save: '%s'\n", filename);
+ SPIFFS.remove(filename);
+ printf_w("Config save: remove '%s'\n", filename);
+
+ File file = SPIFFS.open(filename, "w");
+ if (!file) {
+ print_w("Failed to create file");
+ return;
+ }
+ printf_w("Config save: open ok '%s'\n", filename);
+
+ StaticJsonDocument<512> doc;
+ printf_w("Config save: StaticJsonDocument init ok '%s', temp_switch '%s', step_switch '%s', timezone '%d', auto_update_time '%s', uid %s, token %s\n",
+ filename,
+ temp_sensor_switch? "y":"n", step_sensor_switch?"y":"n", timezone, auto_update_time_over_wifi?"y":"n",
+ server_uid, server_token
+ );
+
+ doc["temp_switch"] = temp_sensor_switch;
+ doc["step_switch"] = step_sensor_switch;
+ doc["timezone"] = timezone;
+ doc["auto_update_time"] = auto_update_time_over_wifi;
+ doc["server_uid"] = server_uid;
+ doc["server_token"] = server_token;
+ doc["server_addr"] = server_addr;
+
+ printf_w("Config save: set value ok '%s'\n", filename);
+
+ if (serializeJson(doc, file) == 0) {
+ print_w("Failed to write to file");
+ }
+ printf_w("Config save: start close '%s'\n", filename);
+
+ serializeJson(doc, Serial); // {"hello":"world"}
+
+ file.close();
+}
+
+
+// set data to external values, uuuuglyyyy
+void Config::setToMenu()
+{
+// menuPulseMeter.setBoolean(newValue, true);
+ menuPedoMeter.setBoolean(step_sensor_switch, true);
+ menuTemperature.setBoolean(temp_sensor_switch, true);
+ menuTimeZone.setCurrentValue(timezone, true);
+ //menuAutoUpdate.setBoolean(auto_update_time_over_wifi, true);
+}
+
+
+void Config::setCurrenDayTimeToMenu()
+{
+ DateStorage _date( day(), month(), year() );
+ TimeStorage _time( hour(), minute(), second(), 0);
+
+ menuTime.setTime(_time);
+ menuDate.setDate(_date);
+}
+
+void Config::cast_from_menu()
+{
+// menuPulseMeter.setBoolean(newValue, true);
+ step_sensor_switch = menuPedoMeter.getBoolean();
+ temp_sensor_switch = menuTemperature.getBoolean();
+ timezone = menuTimeZone.getCurrentValue();
+ // auto_update_time_over_wifi = menuAutoUpdate.getBoolean();
+}
+
+void Config::setServerUid(const char *_uid)
+{
+ strcpy(server_uid, _uid);
+}
+
+void Config::setServerToken(const char *_token)
+{
+ strcpy(server_token, _token);
+}
+
+void Config::setServerAddr(const char *_addr)
+{
+ strcpy(server_addr, _addr);
+}
+
+char * Config::getServerUid()
+{
+ return server_uid;
+}
+
+char * Config::getServerToken()
+{
+ return server_token;
+}
+char * Config::getServerAddr()
+{
+ return server_addr;
}
diff --git a/hardware.hpp b/hardware.hpp
index 8b2c4fb..0ccf667 100644
--- a/hardware.hpp
+++ b/hardware.hpp
@@ -3,12 +3,17 @@
#include "sensors.hpp"
#include
-#include "FS.h"
-#include "SPIFFS.h"
-#include
-#include
-#define CACHE_RECORD_CNT 10
+//#include "icon_menu.h"
+#include
+
+#include // https://github.com/tzapu/WiFiManager
+#include "sensors.hpp"
+#include "stat_records.hpp"
+#include "stat_server.hpp"
+
+#include "display.hpp"
+#include "ulp_main.h"
enum sensor_state_t{
SENSOR_GOTOSLEEP = 0,
@@ -16,16 +21,18 @@ enum sensor_state_t{
SENSOR_AWAKE,
SENSOR_WRITE_RESULT
};
+
static const char * sensor_state_name[] = { "SENSOR_GOTOSLEEP", "SENSOR_SLEEPING", "SENSOR_AWAKE", "SENSOR_WRITE_RESULT" };
enum control_state_t{
CONTROL_GOTOSLEEP = 0,
CONTROL_SLEEPING,
CONTROL_AWAKE,
+ CONTROL_WIFI_INIT,
};
-static const char * control_state_name[] = {"CONTROL_GOTOSLEEP", "CONTROL_SLEEPING", "CONTROL_AWAKE"};
+static const char * control_state_name[] = {"CONTROL_GOTOSLEEP", "CONTROL_SLEEPING", "CONTROL_AWAKE", "CONTROL_WIFI_INIT"};
-static const char * esp_sleep_wake[] = {
+static const char * espSleepWake[] = {
"ESP_SLEEP_WAKEUP_UNDEFINED",
"ESP_SLEEP_WAKEUP_ALL",
"ESP_SLEEP_WAKEUP_EXT0",
@@ -36,118 +43,173 @@ static const char * esp_sleep_wake[] = {
"ESP_SLEEP_WAKEUP_GPIO",
"ESP_SLEEP_WAKEUP_UART"
};
-
-struct StatRecord_t{
- time_t Time;
- uint32_t Steps;
- float HeartRate;
- float SpO2;
- float AmbientTempC;
- float ObjectTempC;
- float Vcc;
-};
-class Display {
- private:
- const int REPORTING_PERIOD_MS = 1000;
- U8G2 _display;
-
- uint32_t ts_last_display_update = 0;
-
- void show_digitalClock(int16_t x, int16_t y);
- void show_temperatureC(int16_t x, int16_t y, float therm, float pulse);
- void show_steps(int16_t x, int16_t y, uint16_t steps);
+#define IS_GRAPH_ACTIVITY(act) ( GRAPH_BODY_TEMP_ACTIVITY == act || GRAPH_STEPS_ACTIVITY == act )
+
+class Control {
+ private:
+ uint64_t _Button_State = 0;
+ uint64_t _Button_PrevState = 0;
+ gpio_num_t LEFT_BUTTON = GPIO_NUM_14;
+ gpio_num_t RIGHT_BUTTON = GPIO_NUM_33; // in schema 33 return after test
+ gpio_num_t OK_BUTTON = GPIO_NUM_27;
+
+ void printButtonState (const char *func_name, uint32_t line_number, int l_State, int r_State, int o_State );
public:
+
void init();
- void update(float therm, float pulse, uint16_t);
- void setPowerSave(bool powerSave);
+ bool buttonPressed();
+ void initWakeup();
+
+ gpio_num_t pinOk( ) { return OK_BUTTON;};
+ gpio_num_t pinLeft( ) { return LEFT_BUTTON;};
+ gpio_num_t pinRight( ) { return RIGHT_BUTTON;};
+
+ uint64_t buttons() { return _Button_State;};
};
-class FileSystem {
+#define UID_SIZE 32
+#define TOKEN_SIZE 64
+#define SERVER_ADDR_SIZE 64
+
+class Config {
private:
- bool _can_write = true; //false;
+ bool temp_sensor_switch;
+ bool step_sensor_switch;
+ int8_t timezone;
+ bool auto_update_time_over_wifi;
+
+ char server_uid[UID_SIZE];
+ char server_token[TOKEN_SIZE];
+ char server_addr[SERVER_ADDR_SIZE];
- File _file;
-
- char *current_day_fname(char *inputBuffer, int inputBuffer_size, char *dir, int16_t year, int8_t month, int8_t day);
- void save_records_to_file();
- void list_dir(fs::FS &fs, const char * dirname);
+ char *filename = "/config.txt";
+ bool need_save = false;
+ void updateUlpConfig();
public:
void init();
- void cat_file(File f);
- void write_log(StatRecord_t *record);
- void scan_log_dir(char* dir_name);
+ void load();
+ void save();
- void close();
-};
+ void setSaveFlag(bool _save) { need_save = _save;}
-class Control {
- private:
- uint64_t _Button_State = 0;
- uint64_t _Button_PrevState = 0;
-
- gpio_num_t LEFT_BUTTON = GPIO_NUM_14;
- gpio_num_t RIGHT_BUTTON = GPIO_NUM_33;
- gpio_num_t OK_BUTTON = GPIO_NUM_27;
+ void setToMenu();
+ void cast_from_menu();
+ void setCurrenDayTimeToMenu();
- void print_button_state (const char *func_name, uint32_t line_number, int l_State, int r_State, int o_State );
- public:
- void init();
- bool button_pressed();
- void init_wakeup();
+
+ void setTempSensorSwitch(bool val) { temp_sensor_switch = val;}
+ void setStepSensorSwitch(bool val) { step_sensor_switch = val;}
+ void setServerUid(const char* u) ;
+ void setServerToken(const char * t);
+ void setServerAddr(const char * a);
+ void setTimezone(int8_t val) { timezone = val;}
+
+ bool getTempSensorSwitch(){ return temp_sensor_switch;}
+ bool getStepSensorSwitch(){ return step_sensor_switch;}
+ char *getServerUid();
+ char *getServerToken();
+ char *getServerAddr();
+ int getTimezone() { return timezone;}
};
-
+
+void saveWifiConfigCallback();
+
class Hardware {
private:
+ bool use_wifi = false;
+
+ //int32_t wifi_stage;
+ Config cfg;
+
Gyroscope sGyro;
ThermoMeter sTerm;
PulseMeter sPulse;
CurrentMeter sCurrent;
-
- DS3231M_Class DS3231M;
+
+ TimeMeter sTime;
Display display;
Control control;
FileSystem log_file;
+ WiFiManager wm;
- time_t current_time;
-
-
- bool is_after_deepsleep = false;
+ WiFiManagerParameter *api_uid_server = NULL;
+ WiFiManagerParameter *api_key_server = NULL;
+ WiFiManagerParameter *api_addr_server = NULL;
+
+ time_t currentTime;
+
+ bool isAfterDeepsleep = false;
+ esp_sleep_wakeup_cause_t wakeupReason;
uint8_t pulse_threshold = 3;
- uint32_t displaySleepDelay = 3000;
+ uint32_t displaySleepDelay = 4000; //
uint32_t displaySleepTimer = 0;
+ Graph graph;
- bool debug_trace_fsm = false;
+ bool debugTraceFsm = false;
bool powerSave = false;
- void init_sensors();
- void time_init();
- void display_init();
- void filesystem_init();
- void serial_init();
+ void initSensors();
+ void timeInit();
+ void serialInit();
+
+ int nextWakeTime();
+ void goToSleep() ;
- int next_wake_time();
- void WakeSensors() ;
- void GoToSleep() ;
+ void readSensors();
+ statRecord_t *getCurrentSensorsData(statRecord_t *record);
+ statRecord_t *getLastSensorsData ();
- void read_sensors();
- StatRecord_t *current_sensor_data(StatRecord_t *record);
- StatRecord_t *get_last_sensor_data ();
+ void printAllStat();
+ void printStat(statRecord_t *record);
+ void printFsmState(const char *func_name, uint32_t line_number);
- void print_all_stat();
- void print_stat(StatRecord_t *record);
- void print_fsm_state(const char *func_name, uint32_t line_number);
+ bool isWakeByDeepsleep(esp_sleep_wakeup_cause_t wakeupReason);
+ bool isNewDay();
- bool is_wake_by_deepsleep(esp_sleep_wakeup_cause_t wakeup_reason);
- bool is_new_day();
+ void runActivity(uint64_t buttons);
+ void updateFromConfig();
+
+ void prepareGraphData(displayActivity_t act, int32_t _day, int32_t _month, int32_t _year );
+
+ struct tm * changeDayFile(int32_t _day);
+ struct tm * loadNextDayFile();
+ struct tm * loadPrevDayFile();
+
+ void getTimeOverWifi();
+
+ void updateSkimlog();
+ struct tm * takeLogDayFile(unsigned int curValue);
public:
void init();
void update();
- void power_safe_logic();
+ void runPowerSafe();
+
+// menu logic
+ void setTime( TimeStorage tm);
+ void setDate( DateStorage dt);
+ void setTimezone( int tz , bool need_save);
+
+ void setTempSwitch(bool val, bool need_save);
+ void setPedoSwitch(bool val, bool need_save);
+
+ void showActivity(displayActivity_t act);
+
+ void updateWebApiConfig();
+ void showWifiPortal();
+ void syncTimeViaWifi();
+ void syncStatViaWifi();
+
+ void start_graph_logic(displayActivity_t _activity, unsigned int curValue);
+
+ void setGraphStarting(bool val) { graph.starting = val; };
+ bool getGraphStarting() { return graph.starting; };
+ void showGraph();
};
+
#endif
diff --git a/i2c-util.s b/i2c-util.s
new file mode 100644
index 0000000..129cf99
--- /dev/null
+++ b/i2c-util.s
@@ -0,0 +1,240 @@
+/*
+ * I2C ULP utility routines
+ */
+
+#include "soc/rtc_cntl_reg.h"
+#include "soc/rtc_io_reg.h"
+#include "soc/soc_ulp.h"
+
+#include "stack.s"
+
+
+.bss
+ .global cnt_read
+cnt_read:
+ .long 0
+
+ .global icounter
+icounter:
+ .long 0
+
+ .global resul_pointer
+resul_pointer:
+ .long 0
+
+
+
+odd_sign:
+ .long 0
+
+.text
+
+write_intro:
+ psr
+ jump i2c_start_cond
+
+ ld r2,r3,20 // Address
+ lsh r2,r2,1
+ psr
+ jump i2c_write_byte
+ jumpr popfail,1,ge
+
+ ld r2,r3,16 // Register
+ psr
+ jump i2c_write_byte
+ jumpr popfail,1,ge
+ ret
+
+
+.global write8
+write8:
+ psr
+ jump write_intro
+
+write_b:
+ ld r2,r3,8 // data byte
+ psr
+ jump i2c_write_byte
+ jumpr fail,1,ge
+
+ psr
+ jump i2c_stop_cond
+
+ move r2, 0 // Ok
+ ret
+
+
+.global write16
+write16:
+ psr
+ jump write_intro
+
+ ld r2,r3,8 // data byte 1
+ rsh r2,r2,8
+ psr
+ jump i2c_write_byte
+ jumpr fail,1,ge
+
+ jump write_b
+
+
+read_intro:
+ psr
+ jump i2c_start_cond
+
+ ld r2,r3,16 // Address
+ lsh r2,r2,1
+ psr
+ jump i2c_write_byte
+ jumpr popfail,1,ge
+
+ ld r2,r3,12 // Register
+ psr
+ jump i2c_write_byte
+ jumpr popfail,1,ge
+
+ psr
+ jump i2c_start_cond
+
+ ld r2,r3,16
+ lsh r2,r2,1
+ or r2,r2,1 // Address Read
+ psr
+ jump i2c_write_byte
+ jumpr popfail,1,ge
+
+ ret
+popfail:
+ pop r1 // pop caller return address
+ move r2,1
+ ret
+
+.global read8
+read8:
+ psr
+ jump read_intro
+
+ move r2,1 // last byte
+ psr
+ jump i2c_read_byte
+ push r0
+
+ psr
+ jump i2c_stop_cond
+
+ pop r0
+
+ move r2,0 // OK
+ ret
+fail:
+ move r2,1
+ ret
+
+.global read16
+read16:
+ psr
+ jump read_intro
+
+ move r2,0
+ psr
+ jump i2c_read_byte
+ push r0
+
+ move r2,1 // last byte
+ psr
+ jump i2c_read_byte
+ push r0
+
+ psr
+ jump i2c_stop_cond
+
+ pop r0
+ pop r2 // first byte
+ lsh r2,r2,8
+ or r2,r2,r0
+ move r0,r2
+
+ move r2,0 // OK
+ ret
+
+ .global readMULTY // ugly, memory non-efficient, it store each uint8 to uint32 memory cell =)
+readMULTY:
+ move r1, cnt_read
+ st r0, r1, 0
+
+ move r1, resul_pointer
+ st r2, r1, 0
+
+ psr
+ jump read_intro
+
+ store_mem cnt_read icounter
+next_byteN:
+ dec icounter // icounter -> r0
+ jumpr last_byte, 0, eq
+ move r2,0
+ jump end_byte_select
+last_byte:
+ move r2,1 // last byte
+end_byte_select:
+
+ psr
+ jump i2c_read_byte
+ push r0
+
+ move r2, icounter
+ ld r0, r2, 0
+ jumpr next_byteN, 0, gt
+
+ psr
+ jump i2c_stop_cond
+
+ store_mem cnt_read icounter
+ move r2, resul_pointer
+ ld r1, r2, 0
+
+next_elem:
+ pop r0
+
+ st r0, r1, 0 // Save data in memory
+ sub r1, r1,1 // move pointer to next
+ dec icounter // icounter -> r0
+ jumpr next_elem, 0, gt
+ move r2,0
+ ret
+
+
+ .global writeMULTY // i
+writeMULTY:
+ ld r0,r3,8 // data byte 1
+ move r1, cnt_read
+ st r0, r1, 0
+
+ move r1, resul_pointer
+ st r2, r1, 0
+
+ psr
+ jump write_intro
+
+ store_mem cnt_read icounter
+write_next_byteN:
+ dec icounter // icounter -> r0
+
+ move r1, resul_pointer
+ ld r1,r1,0 // load pointer from memory
+ ld r2,r1,0 // data byte
+ psr
+ jump i2c_write_byte
+ jumpr fail,1,ge
+
+ inc resul_pointer // move pointer to next
+
+ move r2, icounter
+ ld r0, r2, 0
+ jumpr write_next_byteN, 0, gt
+
+ psr
+ jump i2c_stop_cond
+
+ move r2, 0 // Ok
+ ret
diff --git a/i2c.s b/i2c.s
new file mode 100644
index 0000000..ba7fc2b
--- /dev/null
+++ b/i2c.s
@@ -0,0 +1,175 @@
+/* ULP Example: Read temperautre in deep sleep
+
+ This example code is in the Public Domain (or CC0 licensed, at your option.)
+
+ Unless required by applicable law or agreed to in writing, this
+ software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+ CONDITIONS OF ANY KIND, either express or implied.
+
+ This file contains assembly code which runs on the ULP.
+
+*/
+
+/* ULP assembly files are passed through C preprocessor first, so include directives
+ and C macros may be used in these files
+ */
+
+#include "soc/rtc_cntl_reg.h"
+#include "soc/rtc_io_reg.h"
+#include "soc/soc_ulp.h"
+#include "stack.s"
+
+// RTC_GPIO_6 == GPIO_25
+// RTC_GPIO_7 == GPIO_26
+// RTC_GPIO_9 == GPIO_32
+// RTC_GPIO_8 == GPIO_33
+
+//const gpio_num_t GPIO_SCL = GPIO_NUM_26;
+//const gpio_num_t GPIO_SDA = GPIO_NUM_25;
+
+//.set SCL_PIN, 7
+//.set SDA_PIN, 6
+
+.bss
+i2c_started:
+ .long 0
+i2c_didInit:
+ .long 0
+
+
+.text
+.global i2c_start_cond
+.global i2c_stop_cond
+.global i2c_write_bit
+.global i2c_read_bit
+.global i2c_write_byte
+.global i2c_read_byte
+
+.macro I2C_delay
+ wait 5 // if number equ 10 then clock gap is minimal 4.7us // was 50
+.endm
+
+.macro read_SCL
+ READ_RTC_REG(RTC_GPIO_IN_REG, RTC_GPIO_IN_NEXT_S + SCL_PIN, 1)
+.endm
+
+.macro read_SDA
+ READ_RTC_REG(RTC_GPIO_IN_REG, RTC_GPIO_IN_NEXT_S + SDA_PIN, 1)
+.endm
+
+.macro set_SCL
+ WRITE_RTC_REG(RTC_GPIO_ENABLE_W1TC_REG, RTC_GPIO_ENABLE_W1TC_S + SCL_PIN, 1, 1)
+.endm
+
+.macro clear_SCL
+ WRITE_RTC_REG(RTC_GPIO_ENABLE_W1TS_REG, RTC_GPIO_ENABLE_W1TS_S + SCL_PIN, 1, 1)
+.endm
+
+.macro set_SDA
+ WRITE_RTC_REG(RTC_GPIO_ENABLE_W1TC_REG, RTC_GPIO_ENABLE_W1TC_S + SDA_PIN, 1, 1)
+.endm
+
+.macro clear_SDA
+ WRITE_RTC_REG(RTC_GPIO_ENABLE_W1TS_REG, RTC_GPIO_ENABLE_W1TS_S + SDA_PIN, 1, 1)
+.endm
+
+
+i2c_start_cond:
+ move r1,i2c_didInit
+ ld r0,r1,0
+ jumpr didInit,1,ge
+ move r0,1
+ st r0,r1,0
+ WRITE_RTC_REG(RTC_GPIO_OUT_REG, RTC_GPIO_OUT_DATA_S + SCL_PIN, 1, 0)
+ WRITE_RTC_REG(RTC_GPIO_OUT_REG, RTC_GPIO_OUT_DATA_S + SDA_PIN, 1, 0)
+didInit:
+ move r2,i2c_started
+ ld r0,r2,0
+ jumpr not_started,1,lt // if started, do a restart condition
+ set_SDA // set SDA to 1
+ I2C_delay
+ set_SCL
+clock_stretch: // TODO: Add timeout
+ read_SCL
+ jumpr clock_stretch,1,lt
+ I2C_delay // Repeated start setup time, minimum 4.7us
+not_started:
+ clear_SDA // SCL is high, set SDA from 1 to 0.
+ I2C_delay
+ clear_SCL
+ move r0,1
+ st r0,r2,0
+ ret
+
+i2c_stop_cond:
+ clear_SDA // set SDA to 0
+ I2C_delay
+ set_SCL
+clock_stretch_stop:
+ read_SCL
+ jumpr clock_stretch_stop,1,lt
+ I2C_delay // Stop bit setup time, minimum 4us
+ set_SDA // SCL is high, set SDA from 0 to 1
+ I2C_delay
+ move r2,i2c_started
+ move r0,0
+ st r0,r2,0
+ ret
+
+i2c_write_bit: // Write a bit to I2C bus
+ jumpr bit0,1,lt
+ set_SDA
+ jump bit1
+bit0:
+ clear_SDA
+bit1:
+ I2C_delay // SDA change propagation delay
+ set_SCL
+ I2C_delay
+clock_stretch_write:
+ read_SCL
+ jumpr clock_stretch_write,1,lt
+ clear_SCL
+ ret
+
+i2c_read_bit: // Read a bit from I2C bus
+ set_SDA // Let the slave drive data
+ I2C_delay
+ set_SCL
+clock_stretch_read:
+ read_SCL
+ jumpr clock_stretch_read,1,lt
+ I2C_delay
+ read_SDA // SCL is high, read out bit
+ clear_SCL
+ ret // bit in r0
+
+i2c_write_byte: // Return 0 if ack by the slave.
+ stage_rst
+next_bit:
+ and r0,r2,0x80
+ psr
+ jump i2c_write_bit
+ lsh r2,r2,1
+ stage_inc 1
+ jumps next_bit,8,lt
+ psr
+ jump i2c_read_bit
+ ret
+
+i2c_read_byte: // Read a byte from I2C bus
+ push r2
+ move r2,0
+ stage_rst
+next_bit_read:
+ psr
+ jump i2c_read_bit
+ lsh r2,r2,1
+ or r2,r2,r0
+ stage_inc 1
+ jumps next_bit_read,8,lt
+ pop r0
+ psr
+ jump i2c_write_bit
+ move r0,r2
+ ret
diff --git a/i2c_ds3231.s b/i2c_ds3231.s
new file mode 100644
index 0000000..bca4ee9
--- /dev/null
+++ b/i2c_ds3231.s
@@ -0,0 +1,137 @@
+#include "soc/rtc_cntl_reg.h"
+#include "soc/rtc_io_reg.h"
+#include "soc/soc_ulp.h"
+#include "stack.s"
+
+
+.set DS3231_ADDR, 0x68
+.set DS3231_REG_TIME, 0x00
+
+.bss
+
+ .global ds3231_error
+ds3231_error:
+ .long 0
+ .global ds3231_second
+ds3231_second:
+ .long 0
+ .global ds3231_minute
+ds3231_minute:
+ .long 0
+ .global ds3231_hour
+ds3231_hour:
+ .long 0
+ .global ds3231_dayOfWeek
+ds3231_dayOfWeek:
+ .long 0
+ .global ds3231_day
+ds3231_day:
+ .long 0
+ .global ds3231_month
+ds3231_month:
+ .long 0
+ .global ds3231_year
+ds3231_year:
+ .long 0
+
+
+ .global ds3231_set_second
+ds3231_set_second:
+ .long 0
+ .global ds3231_set_minute
+ds3231_set_minute:
+ .long 0
+ .global ds3231_set_hour
+ds3231_set_hour:
+ .long 0
+ .global ds3231_set_dayOfWeek
+ds3231_set_dayOfWeek:
+ .long 0
+ .global ds3231_set_day
+ds3231_set_day:
+ .long 0
+ .global ds3231_set_month
+ds3231_set_month:
+ .long 0
+ .global ds3231_set_year
+ds3231_set_year:
+ .long 0
+
+
+
+.text
+
+/*
+ *
+-----
+21:58:01.084 -> 0x5 0x68 0x0 0x336 0x42 0x14 0x21 0x2
+21:58:01.084 -> 0x2 0x1 0x0 0x3f6 0x4b0 0x0 0x0 0x0
+21:58:01.084 -> 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0
+21:58:01.084 -> 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0
+21:58:01.084 ->
+21:58:01.084 -> wakeupReason ESP_SLEEP_WAKEUP_UNDEFINED, statRecord_cnt 0
+21:58:01.084 -> ds3231 data: year 2000, month 1, day 2, dayOfWeek 2, hour 21, minute 14, second 42
+
+
+21:58:04.036 -> 0x5 0x68 0x0 0x336 0x3dc 0x37e 0x4a5 0x2
+21:58:04.036 -> 0x2 0x1 0x0 0x3f6 0x4b0 0x0 0x0 0x0
+21:58:04.036 -> 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0
+21:58:04.036 -> 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0
+
+21:58:20.111 -> 0x5 0x68 0x0 0x336 0x0 0x0 0x0 0x0
+21:58:20.111 -> 0x0 0x0 0x0 0x3f6 0x4b0 0x0 0x0 0x0
+21:58:20.111 -> 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0
+21:58:20.111 -> 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0
+
+*/
+
+ .global ds3231_getDateTime
+ds3231_getDateTime:
+ store ds3231_error 255
+
+ move r1, DS3231_ADDR
+ push r1
+ move r1, DS3231_REG_TIME
+ push r1
+ move r0, 7
+ move r2, ds3231_year
+ psr
+ jump readMULTY
+ add r3,r3,2 // remove call parameters from stack
+
+ move r0,r2 // test for error
+ jumpr ds3231_fail_read,1,ge
+ store ds3231_error 0
+ ret
+
+
+ .global ds3231_setDateTime
+ds3231_setDateTime:
+ store ds3231_error 255
+
+ move r1, DS3231_ADDR
+ push r1
+ move r1, DS3231_REG_TIME
+ push r1
+ move r1, 7
+ push r1
+
+ move r2, ds3231_set_second
+ psr
+ jump writeMULTY
+ add r3,r3,3 // remove call parameters from stack
+
+ move r0,r2 // test for error
+ jumpr ds3231_fail_read,1,ge
+ store ds3231_error 0
+ ret
+
+
+ .global ds3231_fail_read
+ds3231_fail_read:
+ store ds3231_error SENSOR_STATUS_READ_ERROR
+ ret
+
+
+
+
diff --git a/i2c_ina219.s b/i2c_ina219.s
new file mode 100644
index 0000000..5d7a23a
--- /dev/null
+++ b/i2c_ina219.s
@@ -0,0 +1,296 @@
+#include "soc/rtc_cntl_reg.h"
+#include "soc/rtc_io_reg.h"
+#include "soc/soc_ulp.h"
+#include "stack.s"
+
+.set INA219_ADDR, 0x40
+
+.set INA219_REG_CONFIG, 0x00
+.set INA219_REG_SHUNTVOLTAGE, 0x01
+.set INA219_REG_BUSVOLTAGE, 0x02
+.set INA219_REG_POWER, 0x03
+.set INA219_REG_CURRENT, 0x04
+.set INA219_REG_CALIBRATION, 0x05
+
+.set INA219_CONFIG_GAIN_8_320MV, 0x1800 // Gain 8, 320mV Range
+.set INA219_CONFIG_BADCRES_12BIT, 0x0180 // 12-bit bus res = 0..4097
+.set INA219_CONFIG_SADCRES_12BIT_1S_532US, 0x0018 // 1 x 12-bit shunt sample
+.set INA219_CONFIG_MODE_SANDBVOLT_CONTINUOUS, 0x07 //< shunt and bus voltage continuous
+.set INA219_CONFIG_BVOLTAGERANGE_16V, 0x0000 // 0-16V Range
+.set INA219_CONFIG_BVOLTAGERANGE_32V, 0x2000 // 0-32V Range
+//.set INA219_CONFIG_MODE_POWERDOWN, 0x00
+.set INA219_CONFIG_MODE_POWERDOWN_MASK, 0xFFF8 // we don't have abiliti to call Logical NOT operation
+
+.bss
+
+ .global ina219_error
+ina219_error:
+ .long 0
+
+ .global ina219_sleepstatus
+ina219_sleepstatus:
+ .long 0
+
+ .global ina219_config
+ina219_config:
+ .long 0
+
+ .global ina219_aggr_current
+ina219_aggr_current:
+ .long 0
+
+ .global ina219_current
+ina219_current:
+ .long 0
+
+ .global ina219_voltage
+ina219_voltage:
+ .long 0
+
+ .global ina219_calValue
+ina219_calValue:
+ .long 0
+
+ .global ina219_currentDivider_mA
+ina219_currentDivider_mA:
+ .long 0
+
+ .global ina219_current_table
+ina219_current_table:
+ .long 0
+ // .skip 240, 0
+ // 60 * 4(bites)
+
+ .global ina219_current_pointer
+ina219_current_pointer:
+ .long 0
+
+.text
+ .global ina219_readdata
+ina219_readdata:
+ // store ina219_error 0
+
+ psr
+ jump Start_INA219 // wake up on init state
+
+ psr
+ jump getBusVoltage_raw
+
+ psr
+ jump getCurrent_raw
+
+ psr
+ jump ina219_powerDown
+/*
+ move r2, ina219_current_pointer
+ ld r0, r2, 0
+ jumpr plus_pointer, 59, lt
+ move r0, 0x0
+ jump pointer_magic
+plus_pointer:
+ add r0, r0, 1
+pointer_magic:
+ move r2, ina219_current_pointer
+ st r0, r2, 0
+
+ move r2, ina219_current_table
+ add r1, r2, r0 // move pointer to num from ina219_current_poiter
+*/
+ move r2, ina219_current // Read current
+ ld r0, r2, 0
+ // st r0, r1, 0 // Save counter in minute table
+
+ move r2, ina219_aggr_current // Read counter
+ ld r1, r2, 0
+ add r0, r0, r1 // Increment
+ st r0, r2, 0 // Save counter in memory
+
+ ret
+
+ .global Start_INA219
+Start_INA219:
+ store ina219_error 0
+
+///setCalibration_32V_2A_INA219:
+ store ina219_calValue 4096
+ store ina219_currentDivider_mA 10
+ psr
+ jump setCalibration
+
+ // move r2, 70 // Wait
+ // psr
+ // jump waitMs
+ move r1,INA219_ADDR
+ push r1
+ move r1,INA219_REG_CONFIG
+ push r1
+ move r1, 0
+ or r1,r1,INA219_CONFIG_BVOLTAGERANGE_32V
+ or r1,r1,INA219_CONFIG_GAIN_8_320MV
+ or r1,r1,INA219_CONFIG_BADCRES_12BIT
+ or r1,r1,INA219_CONFIG_SADCRES_12BIT_1S_532US
+ or r1,r1,INA219_CONFIG_MODE_SANDBVOLT_CONTINUOUS
+ // move r0, ina219_config
+ //st r1, r0, 0x0
+ // ld r1,ina219_config,0x0
+ push r1
+ psr
+ jump write16
+ add r3,r3,3 // remove call parameters from stack
+ move r0,r2 // test for error in r2
+ jumpr fail_config,1,ge
+
+ // psr
+ //jump read_config
+ ret
+
+ .global setCalibration
+setCalibration:
+ move r1,INA219_ADDR
+ push r1
+ move r1,INA219_REG_CALIBRATION
+ push r1
+ move r2, ina219_calValue
+ ld r1,r2,0
+ push r1
+ psr
+ jump write16
+ add r3,r3,3 // remove call parameters from stack
+ move r0,r2 // test for error in r2
+ jumpr fail_config,1,ge
+ ret
+
+fail_config:
+ store ina219_error SENSOR_STATUS_CFG_ERROR
+ ret
+
+ .global ina219_powerDown
+ina219_powerDown:
+ psr
+ jump read_config
+
+ move r1,INA219_ADDR
+ push r1
+ move r1,INA219_REG_CONFIG
+ push r1
+ move r2,ina219_config
+ ld r1,r2,0
+ and r1,r1,INA219_CONFIG_MODE_POWERDOWN_MASK
+ push r1
+ psr
+ jump write16
+ add r3,r3,3 // remove call parameters from stack
+
+ move r0,r2 // test for error in r2
+ jumpr fail_config,1,ge
+ ret
+
+ .global powerUp
+powerUp:
+ psr
+ // jump read_config
+ move r1,INA219_ADDR
+ push r1
+ move r1,INA219_REG_CONFIG
+ push r1
+ move r2, ina219_config
+ ld r1,r2,0
+ or r1,r1,INA219_CONFIG_MODE_SANDBVOLT_CONTINUOUS
+ push r1
+ psr
+ jump write16
+ add r3,r3,3 // remove call parameters from stack
+ move r0,r2 // test for error in r2
+ jumpr fail_config,1,ge
+
+ store ina219_error SENSOR_WSTATUS_AWAKE
+ ret
+
+ .global read_config
+read_config:
+ move r1,INA219_ADDR
+ push r1
+ move r1,INA219_REG_CONFIG
+ push r1
+ psr
+ jump read16
+ add r3,r3,2 // remove call parameters from stack
+ move r1,r0 // save result
+ move r0,r2 // test for error
+ jumpr fail_read,1,ge
+
+ move r2, ina219_config // store result
+ st r1, r2, 0
+ ret
+
+ .global getBusVoltage_raw
+getBusVoltage_raw:
+ move r1,INA219_ADDR
+ push r1
+ move r1,INA219_REG_BUSVOLTAGE
+ push r1
+ psr
+ jump read16
+ add r3,r3,2 // remove call parameters from stack
+
+ move r1,r0 // save result
+ move r0,r2 // test for error
+ jumpr fail_read,1,ge
+
+ rsh r2, r1, 0x03
+ move r1,ina219_voltage // store result
+ st r2,r1,0
+ ret
+
+ .global getCurrent_raw
+getCurrent_raw:
+ psr
+ jump setCalibration
+ move r2, 70 // Wait
+ psr
+ jump waitMs
+
+ move r1, INA219_ADDR
+ push r1
+ move r1, INA219_REG_CURRENT
+ push r1
+ psr
+ jump read16
+ add r3, r3, 2 // remove call parameters from stack
+
+ move r1, r0 // save result
+
+ move r0,r2 // test for error
+ jumpr fail_read,1,ge
+
+ move r2, ina219_current// store result
+ st r1,r2,0
+ ret
+
+ .global fail_read
+fail_read:
+ store ina219_error SENSOR_STATUS_READ_ERROR
+ ret
+
+/*
+ .global getShuntVoltage_raw
+getShuntVoltage_raw:
+ move r1,INA219_ADDR
+ push r1
+ move r1,INA219_REG_SHUNTVOLTAGE
+ push r1
+ psr
+ jump read16
+ add r3,r3,2 // remove call parameters from stack
+ move r1,r0 // save result
+ move r0,r2 // test for error
+ jumpr fail_rc,1,ge
+ move r2,ina219_shuntvoltage// store result
+ st r1,r2,0
+ ret
+ */
+
+
+
+
+
diff --git a/i2c_lsm6ds3.s b/i2c_lsm6ds3.s
new file mode 100644
index 0000000..e26edf8
--- /dev/null
+++ b/i2c_lsm6ds3.s
@@ -0,0 +1,245 @@
+#include "soc/rtc_cntl_reg.h"
+#include "soc/rtc_io_reg.h"
+#include "soc/soc_ulp.h"
+#include "stack.s"
+
+.set LSM6DS3_ADDR, 0x6A
+
+.set LSM6DS3_ACC_GYRO_WHO_AM_I_REG, 0X0F
+
+.set LSM6DS3_ACC_GYRO_WHO_AM_I, 0X69
+.set LSM6DS3_C_ACC_GYRO_WHO_AM_I, 0x6A
+
+// * Register : CTRL1_XL, * Address : 0X10
+.set LSM6DS3_ACC_GYRO_FS_XL_2g, 0x00
+.set LSM6DS3_ACC_GYRO_FS_XL_4g, 0x08
+
+.set LSM6DS3_ACC_GYRO_ODR_XL_POWER_DOWN, 0x00
+//.set LSM6DS3_ACC_GYRO_ODR_XL_12Hz, 0x10
+.set LSM6DS3_ACC_GYRO_ODR_XL_26Hz, 0x20
+//.set LSM6DS3_ACC_GYRO_ODR_G_12Hz, 0x10
+//.set LSM6DS3_ACC_GYRO_PEDO_RST_STEP_ENABLED, 0x02
+
+// * Register : CTRL2_G, * Address : 0X11
+.set LSM6DS3_ACC_GYRO_ODR_G_POWER_DOWN, 0x00
+
+.set LSM6DS3_ACC_GYRO_XL_HM_MODE, 0x10
+// * Register : CTRL7_G, * Address : 0X16
+.set LSM6DS3_ACC_GYRO_G_HM_MODE, 0x80
+
+/************** Device Register *******************/
+.set LSM6DS3_ACC_GYRO_INT1_CTRL, 0X0D
+.set LSM6DS3_ACC_GYRO_CTRL1_XL, 0X10
+.set LSM6DS3_ACC_GYRO_CTRL2_G, 0X11
+.set LSM6DS3_ACC_GYRO_CTRL6_C, 0X15
+.set LSM6DS3_ACC_GYRO_CTRL7_G, 0X16
+
+.set LSM6DS3_ACC_GYRO_CTRL10_C, 0X19
+.set LSM6DS3_ACC_GYRO_TAP_CFG1, 0X58
+.set LSM6DS3_ACC_GYRO_STEP_COUNTER_L,0X4B
+.set LSM6DS3_ACC_GYRO_STEP_COUNTER_H,0X4C
+/********/
+.set IMU_HW_ERROR, 0X1
+.set IMU_CFG_ERROR, 0X2
+.set IMU_PED_ERROR, 0X4
+.set IMU_LOWPOWER_ERROR, 0X8
+.set IMU_BAD_RET_ERROR, 0X16
+
+
+.bss
+ .global lsm6ds3_error
+lsm6ds3_error:
+ .long 0
+
+ .global lsm6ds3_error_cnt
+lsm6ds3_error_cnt:
+ .long 0
+
+ .global lsm6ds3_driver_sign
+lsm6ds3_driver_sign:
+ .long 0
+
+//64K steps ought to be enough for anyone =)
+ .global lsm6ds3_step_count
+lsm6ds3_step_count:
+ .long 0
+
+ .global lsm6ds3_inited
+lsm6ds3_inited:
+ .long 0
+
+ .global lsm6ds3_reseted_steps
+lsm6ds3_reseted_steps:
+ .long 0
+
+.text
+ .global lsm6ds3_config_pedometer
+lsm6ds3_config_pedometer:
+ store lsm6ds3_error 0
+
+ if_val_eq ds3231_hour 0 skip_set_step //ammm, try to reset counter on new day logic
+ store lsm6ds3_reseted_steps 0x00 //yep, each time reset this value if not 1th hour of day, not beautiful but reliable
+skip_set_step:
+ store lsm6ds3_driver_sign 0
+ read_i2c LSM6DS3_ADDR LSM6DS3_ACC_GYRO_WHO_AM_I_REG fail_lsm6ds3
+ move r0, lsm6ds3_driver_sign
+ st r1, r0, 0
+
+ move r0, r1 // test for LSM6DS3_ACC_GYRO_WHO_AM_I
+ jumpr ok_lsm6ds3,LSM6DS3_ACC_GYRO_WHO_AM_I,eq
+ jumpr ok_lsm6ds3,LSM6DS3_C_ACC_GYRO_WHO_AM_I,eq
+ store lsm6ds3_error IMU_BAD_RET_ERROR
+ ret
+fail_lsm6ds3:
+ store lsm6ds3_error IMU_HW_ERROR
+ inc lsm6ds3_error_cnt
+ ret
+ok_lsm6ds3:
+ move r2, ds3231_hour // some logic for reset counter (can't use if_val_eq
+ ld r0, r2, 0x00 //
+ jumpr skip_reset_steps, 0, gt
+
+ move r2, lsm6ds3_reseted_steps
+ ld r0, r2, 0x00
+ jumpr skip_reset_steps, 0x01, eq
+ move r0, 0x01
+ st r0, r2, 0x00
+ psr
+ jump drop_step_count
+
+skip_reset_steps:
+
+ if_val_noteq lsm6ds3_inited 0x1 set_lsm6ds3_lowpower
+ jump lsm6ds3_read_step_counter // ugly but otherwise "error: relocation out of range" happened
+set_lsm6ds3_lowpower:
+ // set low-power mode
+
+ update_i2c_register LSM6DS3_ADDR LSM6DS3_ACC_GYRO_CTRL6_C fail_lsm6ds3_lowpower 0xef LSM6DS3_ACC_GYRO_XL_HM_MODE
+ update_i2c_register LSM6DS3_ADDR LSM6DS3_ACC_GYRO_CTRL7_G fail_lsm6ds3_lowpower 0x7f LSM6DS3_ACC_GYRO_G_HM_MODE
+
+ jump ok_lsm6ds3_lowpower
+fail_lsm6ds3_lowpower:
+ store lsm6ds3_error IMU_LOWPOWER_ERROR
+ ret
+ok_lsm6ds3_lowpower:
+
+ //update_i2c_register LSM6DS3_ADDR LSM6DS3_ACC_GYRO_CTRL1_XL fail_lsm6ds3_config 0x0f 0x20
+ // update_i2c_register LSM6DS3_ADDR LSM6DS3_ACC_GYRO_CTRL2_G fail_lsm6ds3_config 0x0f 0x00
+ // ret
+ // Step 1: Configure ODR-26Hz and FS-2g
+ move r1,LSM6DS3_ADDR
+ push r1
+ move r1,LSM6DS3_ACC_GYRO_CTRL1_XL
+ push r1
+ move r1, 0
+ or r1,r1,LSM6DS3_ACC_GYRO_FS_XL_2g
+ or r1,r1,LSM6DS3_ACC_GYRO_ODR_XL_26Hz
+ push r1
+ psr
+ jump write8
+ add r3, r3, 3 // remove call parameters from stack
+ move r0, r2 // test for error in r2
+ jumpr fail_lsm6ds3_config,1,ge
+
+ // // test to look at power consumption
+ // write_i2c LSM6DS3_ADDR LSM6DS3_ACC_GYRO_CTRL2_G LSM6DS3_ACC_GYRO_ODR_G_POWER_DOWN fail_lsm6ds3_config
+
+
+ // Step 2: Set bit Zen_G, Yen_G, Xen_G, FUNC_EN, PEDO_RST_STEP(1 or 0)
+ move r1, LSM6DS3_ADDR
+ push r1
+ move r1, LSM6DS3_ACC_GYRO_CTRL10_C
+ push r1
+ move r1, 0x3C // Read current
+ push r1
+ psr
+ jump write8
+ add r3,r3,3 // remove call parameters from stack
+ move r0, r2 // test for error in r2
+ jumpr fail_lsm6ds3_config,1,ge
+
+ // Step 3: Enable pedometer algorithm
+ move r1,LSM6DS3_ADDR
+ push r1
+ move r1,LSM6DS3_ACC_GYRO_TAP_CFG1
+ push r1
+ move r1,0x40
+ push r1
+ psr
+ jump write8
+ add r3,r3,3 // remove call parameters from stack
+ move r0,r2 // test for error in r2
+ jumpr fail_lsm6ds3_config,1,gt
+
+ //Step 4: Step Detector interrupt driven to INT1 pin, set bit INT1_FIFO_OVR
+ move r1,LSM6DS3_ADDR
+ push r1
+ move r1,LSM6DS3_ACC_GYRO_INT1_CTRL
+ push r1
+ move r1,0x00
+ push r1
+ psr
+ jump write8
+ add r3,r3,3 // remove call parameters from stack
+ move r0,r2 // test for error in r2
+ jumpr fail_lsm6ds3_config,1,ge
+ store lsm6ds3_inited 0x1
+
+ jump lsm6ds3_read_step_counter
+
+fail_lsm6ds3_config:
+ store lsm6ds3_error IMU_CFG_ERROR
+ ret
+
+ .global lsm6ds3_read_step_counter
+lsm6ds3_read_step_counter:
+ move r1,LSM6DS3_ADDR
+ push r1
+ move r1,LSM6DS3_ACC_GYRO_STEP_COUNTER_L // i dont wan't to read twice registers via i2c, let it be bit magic instead
+ push r1
+ psr
+ jump read16
+ add r3,r3,2 // remove call parameters from stack
+
+ move r1,r0 // save result
+ move r0,r2 // test for error
+ jumpr fail_lsm6ds3_read_counter,1,ge
+ move r0, r1
+
+ and r1,r1,0XFF00 // bit swap
+ rsh r1,r1,8
+ lsh r0,r0,8
+ or r0,r0,r1
+
+ move r1, lsm6ds3_step_count
+ st r0, r1, 0 // Save counter in memory
+ ret
+
+
+fail_lsm6ds3_read_counter:
+ store lsm6ds3_error IMU_PED_ERROR
+ ret
+
+ .global lsm6ds3_poweroff
+lsm6ds3_poweroff:
+ update_i2c_register LSM6DS3_ADDR LSM6DS3_ACC_GYRO_CTRL1_XL fail_lsm6ds3_config 0x0f 0x00
+ update_i2c_register LSM6DS3_ADDR LSM6DS3_ACC_GYRO_CTRL2_G fail_lsm6ds3_config 0x0f 0x00
+ret
+
+ .global drop_step_count
+drop_step_count:
+ write_i2c LSM6DS3_ADDR LSM6DS3_ACC_GYRO_CTRL10_C 0x3E fail_lsm6ds3_config
+ret
+ // PEDO_THS_REG -- Pedometer minimum threshold and internal full-scale configuration register (r/w).
+
+ /*
+- only accelerometer active and gyroscope in power-down
+- only gyroscope active and accelerometer in power-down
+- both accelerometer and gyroscope sensors active with independent ODR
+
+
+
+*/
+
+
+
diff --git a/i2c_max30100.s b/i2c_max30100.s
new file mode 100644
index 0000000..e62ec85
--- /dev/null
+++ b/i2c_max30100.s
@@ -0,0 +1,91 @@
+#include "soc/rtc_cntl_reg.h"
+#include "soc/rtc_io_reg.h"
+#include "soc/soc_ulp.h"
+#include "stack.s"
+
+.set MAX30100_ADDR, 0x57
+
+.set EXPECTED_PART_ID, 0x11
+
+.set MAX30100_SAMPRATE_50HZ, 0x00
+//.set MAX30100_SAMPRATE_100HZ, 0x01
+.set MAX30100_MC_SHDN, 0x80 // 1 << 7
+
+// LED Configuration
+.set MAX30100_LED_CURR_7_6MA, 0x02
+.set MAX30100_LED_CURR_27_1MA, 0x08
+.set MAX30100_LED_CURR_50MA, 0x0f
+
+.set MAX30100_SPC_PW_200US_13BITS, 0x00
+.set MAX30100_SPC_PW_1600US_16BITS, 0x03
+.set MAX30100_SPC_SPO2_HI_RES_EN, 0x40
+
+.set MAX30100_MODE_HRONLY, 0x02
+.set MAX30100_MODE_SPO2_HR, 0x03
+
+.set MAX30100_FIFO_DEPTH, 0x10 // link with MAX30100_FIFO_DEPTH_MINUS_ONE (don't want to call sub)
+.set MAX30100_FIFO_DEPTH_MINUS_ONE, 0xf // link with MAX30100_FIFO_DEPTH (don't want to call sub)
+
+.set MAX30100_REG_FIFO_WRITE_POINTER, 0x02
+.set MAX30100_REG_FIFO_OVERFLOW_COUNTER, 0x03
+.set MAX30100_REG_FIFO_READ_POINTER, 0x04
+.set MAX30100_REG_FIFO_DATA, 0x05
+.set MAX30100_REG_MODE_CONFIGURATION, 0x06
+.set MAX30100_REG_SPO2_CONFIGURATION, 0x07
+.set MAX30100_REG_LED_CONFIGURATION, 0x09
+.set MAX30100_REG_PART_ID, 0xff
+
+// https://github.com/oxullo/Arduino-MAX30100/blob/master/src/MAX30100.cpp
+//https://github.com/viggi1000/RakshBluemix/blob/3b64cc5819357a9f7e7492c5afc7fd98b4f5671e/max30100_photon/firmware/MAX30100.cpp
+
+.set ARRAY_SIZE, 10 // in uint32 or = 800 byte, same set in max30100_raw_data size
+
+.bss
+ .global max30100_error
+max30100_error:
+ .long 0
+ .global max30100_toRead
+max30100_toRead:
+ .long 0
+ .global max30100_r2
+max30100_r2:
+ .long 0
+ .global max30100_r3
+max30100_r3:
+ .long 0
+ .global max30100_flags
+max30100_flags:
+ .long 0
+ .global max30100_data_pointer //
+max30100_data_pointer:
+ .long 0
+ .global max30100_raw_data
+max30100_raw_data:
+ .skip 5// need set 800
+ .global max30100_raw_dataEnd
+max30100_raw_dataEnd:
+ .long 0
+
+// DUMMY, only for switch off sensor if it soldered
+.text
+ .global max30100_init
+max30100_init:
+ ret // tmp off
+
+
+ /*
+ .global max30100_resume
+max30100_resume:
+ update_i2c_register MAX30100_ADDR MAX30100_REG_MODE_CONFIGURATION fail_max30100_read 0x7f 0
+ ret
+*/
+ .global max30100_shutdown
+max30100_shutdown:
+ update_i2c_register MAX30100_ADDR MAX30100_REG_MODE_CONFIGURATION fail_max30100_read 0x7f MAX30100_MC_SHDN
+ ret
+
+fail_max30100_read:
+ store max30100_error SENSOR_STATUS_HW_ERROR
+ ret
+
+
diff --git a/i2c_mlx90615.s b/i2c_mlx90615.s
new file mode 100644
index 0000000..468d169
--- /dev/null
+++ b/i2c_mlx90615.s
@@ -0,0 +1,196 @@
+#include "soc/rtc_cntl_reg.h"
+#include "soc/rtc_io_reg.h"
+#include "soc/soc_ulp.h"
+#include "stack.s"
+//#include "i2c.s"
+
+
+
+.set MLX90615_ADDR, 0x5B
+
+.set MLX90615_AMBIENT_TEMPERATURE, 0x26
+.set MLX90615_OBJECT_TEMPERATURE, 0x27
+.set MLX90615_SLEEP, 0xC6
+
+
+.bss
+ .global mlx90615_error
+mlx90615_error:
+ .long 0
+ .global mlx90615_obj_temperature
+mlx90615_obj_temperature:
+ .long 0
+ .global mlx90615_amb_temperature
+mlx90615_amb_temperature:
+ .long 0
+
+ .global mlx90615_b1
+mlx90615_b1:
+ .long 0
+ .global mlx90615_b2
+mlx90615_b2:
+ .long 0
+ .global mlx90615_pec
+mlx90615_pec:
+ .long 0
+.text
+
+
+ .global mlx90615_readdata
+mlx90615_readdata:
+ // psr
+ // jump mlx90615_wake
+
+ move r2, 300 // Valid data will be available typically 0.3 seconds after the device has woken up.
+ psr
+ jump waitMs
+
+ psr
+ jump mlx90615_getAmbientTemp
+
+ psr
+ jump mlx90615_getObjectTemp
+
+ psr
+ jump mlx90615_sleep
+ret
+
+ .global mlx90615_getObjectTemp
+mlx90615_getObjectTemp:
+
+ move r1,MLX90615_ADDR
+ push r1
+ move r1,MLX90615_OBJECT_TEMPERATURE
+ push r1
+ move r0, 3
+ move r2, mlx90615_pec
+ psr
+ jump readMULTY
+ add r3,r3,2 // remove call parameters from stack
+
+ move r1, mlx90615_b1 // save result
+ ld r0, r1, 0
+ move r1, mlx90615_b2 // save result
+ ld r2, r1, 0
+
+ lsh r2,r2,8
+ or r2,r2,r0
+
+ move r0, mlx90615_obj_temperature
+ st r2, r0, 0
+ ret
+
+ .global mlx90615_getAmbientTemp
+mlx90615_getAmbientTemp:
+ move r1,MLX90615_ADDR
+ push r1
+ move r1,MLX90615_AMBIENT_TEMPERATURE
+ push r1
+ move r0, 3
+ move r2, mlx90615_pec
+ psr
+ jump readMULTY
+ add r3,r3,2 // remove call parameters from stack
+
+ move r1, mlx90615_b1 // save result
+ ld r0, r1, 0
+ move r1, mlx90615_b2 // save result
+ ld r2, r1, 0
+
+ lsh r2,r2,8
+ or r2,r2,r0
+
+ move r0, mlx90615_amb_temperature
+ st r2, r0, 0
+ ret
+
+fail_mlx90615:
+ store mlx90615_error 1
+ ret
+
+ .global mlx90615_sleep
+mlx90615_sleep:
+// SA=0x5B, Command=0xC6, PEC=0x6D (SA_W=0xB6 Command=0xC6 PEC=0x6D)// hardcode for sleep command, adress fixed
+ move r1, MLX90615_ADDR // SA_W=0xB6
+ push r1
+ move r1, 0xC6 // Command=0xC6
+ push r1
+ move r1, 0x6D // PEC=0x6D
+ push r1
+ psr
+ jump write8
+
+ add r3,r3,3 // remove call parameters from stack
+ move r0,r2 // test for error in r2
+ jumpr fail_mlx90615,1,ge
+ ret
+
+ .global mlx90615_wake
+mlx90615_wake:
+ WRITE_RTC_REG(RTC_GPIO_ENABLE_W1TS_REG, RTC_GPIO_ENABLE_W1TS_S + SCL_PIN, 1, 1) // SET 0 to scl
+
+ move r2, 55 // wait 10ms to wake up, Valid data will be available typically 0.3 seconds after the device has woken up.
+ psr
+ jump waitMs
+ ret
+
+ /*
+ wake_stretch_read: // read 1 from scl -- this is good sign that i2c waked
+ READ_RTC_REG(RTC_GPIO_IN_REG, RTC_GPIO_IN_NEXT_S + SCL_PIN, 1)
+ jumpr wake_stretch_read,1,lt
+ ret
+ */
+// https://github.com/joaobarroca93/MLX90615/blob/9f5d3a79b2e3032abb9d0bd01e18334830f1d5dc/MLX90615.cpp
+
+/*
+ float getTemperature(int Temperature_kind, bool fahrenheit = false) {
+ float celsius;
+ uint16_t tempData;
+
+ readReg(Temperature_kind, &tempData);
+
+ double tempFactor = 0.02; // 0.02 degrees per LSB (measurement resolution of the MLX90614)
+
+ // This masks off the error bit of the high byte
+ celsius = ((float)tempData * tempFactor) - 0.01;
+
+ celsius = (float)(celsius - 273.15);
+
+ return fahrenheit ? (celsius * 1.8) + 32.0 : celsius;
+ }
+
+ boolean MLX90615::sleep(void)
+{
+ CRC8 crc(MLX90615_CRC8POLY);
+ // Build the CRC-8 of all bytes to be sent.
+ crc.crc8(_addr << 1);
+ _crc8 = crc.crc8(MLX90615_SLEEP_MODE);
+ Serial.print("PEC (Sleep Mode) = ");
+ Serial.println(_crc8);
+ // Send the slave address then the command.
+ Wire.beginTransmission(_addr);
+ Wire.write(MLX90615_SLEEP_MODE);
+ // Then write the crc and set the r/w error status bits.
+ Wire.write(_crc8);
+ _rwError |= (1 << Wire.endTransmission(true)) >> 1;
+ // Now we need to keep SCL high
+ pinMode(A5, OUTPUT);
+ pinMode(A4, OUTPUT);
+ digitalWrite(A5, HIGH);
+ digitalWrite(A4, LOW);
+ // Clear r/w errors if using broadcast address.
+ if(_addr == MLX90615_BROADCASTADDR) _rwError &= MLX90615_NORWERROR;
+ return _sleep = true;
+}
+
+boolean MLX90615::wakeUp(void)
+{
+ pinMode(A5, OUTPUT);
+ digitalWrite(A5, LOW);
+ delay(10);
+ Wire.begin();
+ delay(400);
+ MLX90615::begin();
+ return _ready;
+}
+ */
diff --git a/main.s b/main.s
new file mode 100644
index 0000000..b21b5ec
--- /dev/null
+++ b/main.s
@@ -0,0 +1,237 @@
+#include "soc/rtc_cntl_reg.h"
+#include "soc/rtc_io_reg.h"
+#include "soc/soc_ulp.h"
+#include "stack.s"
+
+.set SENSOR_INA219, 0x01
+.set SENSOR_LSM6DS3, 0x02
+.set SENSOR_MLX90615, 0x04
+.set SENSOR_MAX30100, 0x08
+
+.bss
+/*
+ */
+ .global sample_counter
+sample_counter:
+ .long 0
+/**/
+ .global result
+result:
+ .long 0
+
+ .global sensors_switch_extern
+sensors_switch_extern:
+ .long 0
+
+ .global sensors_switch
+sensors_switch:
+ .long 0
+
+ .global sensors_working
+sensors_working:
+ .long 0
+
+ .global refresh_sensors
+refresh_sensors:
+ .long 0
+
+ .global ina219_read_cnt
+ina219_read_cnt:
+ .long 0
+ .global lsm6ds3_read_cnt
+lsm6ds3_read_cnt:
+ .long 0
+ .global mlx90615_read_cnt
+mlx90615_read_cnt:
+ .long 0
+ .global max30100_read_cnt
+max30100_read_cnt:
+ .long 0
+
+
+ .global ina219_skip_cnt
+ina219_skip_cnt:
+ .long 0
+ .global lsm6ds3_skip_cnt
+lsm6ds3_skip_cnt:
+ .long 0
+ .global mlx90615_skip_cnt
+mlx90615_skip_cnt:
+ .long 0
+ .global max30100_skip_cnt
+max30100_skip_cnt:
+ .long 0
+
+ .global ds3231_update_flag
+ds3231_update_flag:
+ .long 0
+
+ .global stack
+stack:
+ .skip 80
+ .global stackEnd
+stackEnd:
+ .long 0
+
+
+.text
+ .global entry
+entry:
+ move r3, stackEnd
+
+ store_mem sensors_switch_extern sensors_switch
+
+ psr
+ jump ds3231_getDateTime // anyway at start, try to get current time
+
+ move r2, ds3231_update_flag // update time logic if needed
+ ld r0,r2,0x0
+ jumpr skip_time_update, 0, eq
+
+ move r0, ds3231_set_year // skip update if year == 2000
+ jumpr skip_time_update, 0, eq
+ psr
+ jump ds3231_setDateTime
+
+ store ds3231_update_flag 0
+ store ds3231_set_day 0
+
+skip_time_update:
+
+ move r2, ds3231_second
+ ld r0,r2,0
+ jumpr skip_wake_mlx90615, 89, lt // this is raw format from ds3231 > 59
+
+ if_noflag sensors_switch SENSOR_MLX90615 skip_wake_mlx90615
+ // if_noflag sensors_working SENSOR_MLX90615 skip_wake_mlx90615
+ // need some time to start after wake, so send wake first, do some work with other sensor and only than read
+ psr
+ jump mlx90615_wake
+skip_wake_mlx90615:
+
+ // this is a long long logic, i tried separate logic but it costs high-memory
+ if_flag sensors_switch SENSOR_INA219 start_read_ina219
+ if_noflag sensors_working SENSOR_INA219 skip_read_ina219
+ psr
+ jump ina219_powerDown
+ del_flag sensors_working SENSOR_INA219
+ inc ina219_skip_cnt
+ jump skip_read_ina219
+
+start_read_ina219:
+ psr
+ jump ina219_readdata
+ set_flag sensors_working SENSOR_INA219
+ inc ina219_read_cnt
+skip_read_ina219:
+
+ /// test this logic in real life !!!!!!!!!!!!!!!!
+
+ move r2, ds3231_second
+ ld r0,r2,0x0
+ and r0, r0, 0x1
+ jumpr skip_read_lsm6ds3, 0x0, eq //
+
+ if_flag sensors_switch SENSOR_LSM6DS3 start_read_lsm6ds3
+ if_noflag sensors_working SENSOR_LSM6DS3 skip_read_lsm6ds3
+ psr
+ jump lsm6ds3_poweroff
+ del_flag sensors_working SENSOR_LSM6DS3
+ inc lsm6ds3_skip_cnt
+ jump skip_read_lsm6ds3
+
+start_read_lsm6ds3:
+ move r2, 70 // mb problem
+ psr
+ jump waitMs
+
+ psr
+ jump lsm6ds3_config_pedometer
+ set_flag sensors_working SENSOR_LSM6DS3
+ inc lsm6ds3_read_cnt
+skip_read_lsm6ds3:
+
+ move r2, ds3231_second
+ ld r0,r2,0
+ jumpr inc_sample, 89, lt // this is raw format from ds3231 > 59
+
+ if_flag sensors_switch SENSOR_MLX90615 start_read_mlx90615
+ if_noflag sensors_working SENSOR_MLX90615 skip_read_mlx90615
+ psr
+ jump mlx90615_sleep
+ del_flag sensors_working SENSOR_MLX90615
+ inc mlx90615_skip_cnt
+ jump skip_read_mlx90615
+
+start_read_mlx90615:
+ psr
+ jump mlx90615_readdata
+ set_flag sensors_working SENSOR_MLX90615
+ inc mlx90615_read_cnt
+
+skip_read_mlx90615:
+
+ if_flag sensors_switch SENSOR_MAX30100 start_read_max30100
+ if_noflag sensors_working SENSOR_MAX30100 skip_read_max30100
+ psr
+ jump max30100_shutdown
+ del_flag sensors_working SENSOR_MAX30100
+ inc max30100_skip_cnt
+ jump skip_read_max30100
+
+start_read_max30100:
+ psr
+ jump max30100_init
+ set_flag sensors_working SENSOR_MAX30100
+ inc max30100_read_cnt
+// psr
+ // jump max30100_resume
+
+ /*
+ move r2, 1 // Read sample counter
+ psr
+ jump waitMs
+
+ psr
+ jump max30100_readFifoData
+
+
+ // psr
+ // jump max30100_shutdown
+ move r2, 5000 // Read sample counter
+ psr
+ */
+skip_read_max30100:
+
+
+inc_sample:
+ inc sample_counter // Read sample counter
+
+ .global exit
+exit:
+ halt
+
+ .global wake_up
+wake_up:
+ /* Check if the system can be woken up */
+ READ_RTC_REG(RTC_CNTL_DIAG0_REG, 19, 1)
+ and r0, r0, 1
+ jump exit, eq
+ /* Wake up the SoC, end program */
+ wake
+ WRITE_RTC_FIELD(RTC_CNTL_STATE0_REG, RTC_CNTL_ULP_CP_SLP_TIMER_EN, 0)
+ halt
+
+// Wait for r2 milliseconds
+ .global waitMs
+waitMs:
+ wait 8000
+ sub r2,r2,1
+ jump doneWaitMs,eq
+ jump waitMs
+doneWaitMs:
+ ret
+
+
+
+
diff --git a/sensors.cpp b/sensors.cpp
index 69b5659..bd0cdfa 100644
--- a/sensors.cpp
+++ b/sensors.cpp
@@ -1,137 +1,54 @@
#include "sensors.hpp"
#include "esp_adc_cal.h"
+#include "ulp_main.h"
//============================================================================================
-#ifdef _USE_BMI160_
-GyroscopeBMI160::GyroscopeBMI160() {
- steps = 0;
+void Sensor::init_direct(){
}
-void GyroscopeBMI160::init(bool clearStep) {
- inited = false;
-KK
- gyro.begin(BMI160GenClass::I2C_MODE, Wire, i2c_addr, 2, !clearStep); // !clearStep -- after deep sleep other init logic
- bool sce = gyro.getStepCountEnabled();
- uint8_t sdm = gyro.getStepDetectionMode();
-
- print_w("StepCountEnabled "); println_w( sce ? "true" : "false" );
- print_w("StepDetectionMode "); println_w( sdm );
-
- if ( !sce ) {
- println_w( "init gyro step counter" ) ;
- gyro.setStepCountEnabled(true);
- sce = gyro.getStepCountEnabled();
-
- print_w("StepCountEnable #2 ");
- println_w(sce ? "true" : "false" );
- }
- else {
- println_w( "gyro step inited" );
- }
- uint16_t sc = gyro.getStepCount();
- print_w("StepCount "); println_w(sc );
+void Sensor::init(){
inited = true;
- return;// gyro.getStepCount();
-}
-
-
-void GyroscopeBMI160::read_data() {
- if (false == inited)
- return;
- steps = gyro.getStepCount();
-}
-
-uint16_t GyroscopeBMI160::StepCount(){
- return steps;
-}
-
-void GyroscopeBMI160::wake(){
-}
-
-void GyroscopeBMI160::sleep(){
+ return;
}
-#endif
//============================================================================================
-#ifdef _USE_LSM6DS3_
GyroscopeLSM6DS3::GyroscopeLSM6DS3() {
steps = 0;
}
+
GyroscopeLSM6DS3::~GyroscopeLSM6DS3(){
- free(gyro);
}
-void GyroscopeLSM6DS3::init(bool clearStep) {
- inited = false;
- gyro = new LSM6DS3Core( I2C_MODE, i2c_addr );
-
- if( gyro->beginCore() != 0 )
- {
- Serial.print("Error at beginCore().\n");
- return;
- }
-
- //Error accumulation variable
- uint8_t errorAccumulator = 0;
-
- uint8_t dataToWrite = 0; //Temporary variable
-
- //Setup the accelerometer******************************
- dataToWrite = 0; //Start Fresh!
- // dataToWrite |= LSM6DS3_ACC_GYRO_BW_XL_200Hz;
- dataToWrite |= LSM6DS3_ACC_GYRO_FS_XL_2g;
- dataToWrite |= LSM6DS3_ACC_GYRO_ODR_XL_26Hz;
-
- // //Now, write the patched together data
- errorAccumulator += gyro->writeRegister(LSM6DS3_ACC_GYRO_CTRL1_XL, dataToWrite);
-
- // //Set the ODR bit
- // errorAccumulator += gyro->readRegister(&dataToWrite, LSM6DS3_ACC_GYRO_CTRL4_C);
- // dataToWrite &= ~((uint8_t)LSM6DS3_ACC_GYRO_BW_SCAL_ODR_ENABLED);
-
- // Enable embedded functions -- ALSO clears the pdeo step count
- int flag10 = 0x3C; // all except reset pedometer
- if ( clearStep )
- flag10 |= LSM6DS3_ACC_GYRO_PEDO_RST_STEP_ENABLED; // up reset flag
-
- errorAccumulator += gyro->writeRegister(LSM6DS3_ACC_GYRO_CTRL10_C, flag10);
- // Enable pedometer algorithm
- errorAccumulator += gyro->writeRegister(LSM6DS3_ACC_GYRO_TAP_CFG1, 0x40);
- // Step Detector interrupt driven to INT1 pin
- errorAccumulator += gyro->writeRegister( LSM6DS3_ACC_GYRO_INT1_CTRL, 0x10 );
-
- /*
- uint16_t sc = gyro.getStepCount();
- print_w("StepCount "); println_w(sc );
-*/
- inited = true;
- return;
+void GyroscopeLSM6DS3::init() {
+ Sensor::init();
}
+void GyroscopeLSM6DS3::read() {
+ // test for errcode from ulp and set value from rtc memory
+ printf_w("LSM6DS3 driver signature %d, error_cnt %d\n", ulp_lsm6ds3_driver_sign, ulp_lsm6ds3_error_cnt);
-void GyroscopeLSM6DS3::read_data() {
- if (false == inited)
+ if (ulp_lsm6ds3_error)
return;
-
- uint8_t readDataByte = 0;
- uint16_t stepsTaken = 0;
- //Read the 16bit value by two 8bit operations
- gyro->readRegister(&readDataByte, LSM6DS3_ACC_GYRO_STEP_COUNTER_H);
- stepsTaken = ((uint16_t)readDataByte) << 8;
- gyro->readRegister(&readDataByte, LSM6DS3_ACC_GYRO_STEP_COUNTER_L);
- stepsTaken |= readDataByte;
- steps = stepsTaken;
+ printUlpData();
+
+ steps = ulp_lsm6ds3_step_count; //
}
-uint16_t GyroscopeLSM6DS3::StepCount(){
+uint16_t GyroscopeLSM6DS3::getStepCount(){
return steps;
}
void GyroscopeLSM6DS3::wake(){
+ ulp_sensors_switch_extern |= SENSOR_LSM6DS3;
}
void GyroscopeLSM6DS3::sleep(){
+ ulp_sensors_switch_extern &= ~SENSOR_LSM6DS3;
}
-#endif
+
+void GyroscopeLSM6DS3::printUlpData(){
+ printf_w("LSM6DS3 status: step_count %d\n", ulp_lsm6ds3_step_count);
+}
+
//============================================================================================
ThermoMeter::ThermoMeter() {
AmbientTempC = 0;
@@ -139,51 +56,51 @@ ThermoMeter::ThermoMeter() {
}
void ThermoMeter::init(){
-#if defined(_USE_MLX90614_)
- therm.begin(); // Initialize the MLX90614
- therm.setUnit(TEMP_C);
- therm.wake();
-#elif defined(_USE_MLX90615_)
- therm.wakeUp();
-#endif
- inited = true;
-}
-
-void ThermoMeter::update(){
-#if defined(_USE_MLX90614_)
- therm.read();
-#endif
+ Sensor::init();
}
void ThermoMeter::wake(){
- therm.wakeUp();
+ ulp_sensors_switch_extern |= SENSOR_MLX90615;
}
void ThermoMeter::sleep(){
-#if defined(_USE_MLX90614_)
- therm.sleep();
-#elif defined(_USE_MLX90615_)
- therm.sleep(); // MLX9061* demands magic action with wire before go to sleep so it should be last wire sleep call
-#endif
+ ulp_sensors_switch_extern &= ~SENSOR_MLX90615;
}
-void ThermoMeter::read_data(){
-#if defined(_USE_MLX90614_)
- AmbientTempC = therm.ambient();
- ObjectTempC = therm.object();
-#elif defined(_USE_MLX90615_)
- AmbientTempC = therm.readTemp(MLX90615::MLX90615_SRCA, MLX90615::MLX90615_TC);
- ObjectTempC = therm.readTemp();
-#endif
+float ThermoMeter::raw_temp_to_C(uint16_t _t) {
+ if (0 == _t)
+ return 0;
+ return ( (float)_t * 0.02 ) - 273.15;
}
-float ThermoMeter::AmbientC(){
+void ThermoMeter::read() {
+ // test for errcode from ulp and set value from rtc memory
+ if (ulp_mlx90615_error)
+ return;
+
+ printUlpData();
+ AmbientTempC = raw_temp_to_C(ulp_mlx90615_amb_temperature);
+ ObjectTempC = raw_temp_to_C(ulp_mlx90615_obj_temperature);
+}
+
+float ThermoMeter::getAmbientC(){
return AmbientTempC;
}
-float ThermoMeter::ObjectC() {
+float ThermoMeter::getObjectC() {
return ObjectTempC;
}
+void ThermoMeter::printUlpData(){
+ printf_w("mlx90615 data: "
+ "obj_temperature %f, amb_temperature %f,ulp_mlx90615_obj_temperature %d, amb_temperature raw %d"
+ "\n",
+ raw_temp_to_C(ulp_mlx90615_obj_temperature), raw_temp_to_C(ulp_mlx90615_amb_temperature), ulp_mlx90615_obj_temperature, ulp_mlx90615_amb_temperature
+ );
+
+// printf_w("mlx90615 data: b1 %d, b2 %d, pec %d, last val %d\n",
+ // ulp_mlx90615_b1, ulp_mlx90615_b2, ulp_mlx90615_pec, (ulp_mlx90615_b2 << 8) | ulp_mlx90615_b1
+ // );
+}
//============================================================================================
PulseMeter::PulseMeter(){
heartRate = 0;
@@ -191,92 +108,302 @@ PulseMeter::PulseMeter(){
}
void PulseMeter::init(){
- pulse.begin();// PULSEOXIMETER_DEBUGGINGMODE_PULSEDETECT
- pulse.setIRLedCurrent(MAX30100_LED_CURR_7_6MA);
- inited = true;
- // pulse.setIRLedCurrent(MAX30100_LED_CURR_7_6MA);
-}
-
-void PulseMeter::update(){
- pulse.update();
+ Sensor::init();
}
void PulseMeter::wake(){
- pulse.resume();
+ ulp_sensors_switch_extern |= SENSOR_MAX30100;
}
void PulseMeter::sleep(){
- pulse.shutdown();
+ ulp_sensors_switch_extern &= ~SENSOR_MAX30100;
}
-void PulseMeter::read_data() {
- heartRate = pulse.getHeartRate();
- spO2 = pulse.getSpO2();
+void PulseMeter::read() {
+ // test for errcode from ulp and set value from rtc memory
+ heartRate = 0;
+ spO2 = 0;
}
-float PulseMeter::HeartRate(){
+float PulseMeter::getHeartRate(){
return heartRate;
}
-float PulseMeter::SpO2(){
+float PulseMeter::getSpO2(){
return spO2;
}
//============================================================================================
+
+// https://www.jackogrady.me/battery-management-system/state-of-charge
CurrentMeter::CurrentMeter() {
vcc = 0;
+ current_since_reboot = 0;
+ //aggr_current = 0;
+ //aggr_current_prev = 0;
}
-void CurrentMeter::init(){
- ina219.begin();
- inited = true;
+void CurrentMeter::init(float *current){
+ Sensor::init();
+
+ battery = new Battery(3000, 4200, A0);
+ battery->begin(180, 1.0, &sigmoidal);
+
+ aggr_current = current;
+}
+
+
+float CurrentMeter::calc_aggr_accum(float mA) {
+ float aggr = (float) mA/(float)ulp_ina219_currentDivider_mA;
+ return tm * aggr / tb;
}
-void CurrentMeter::read_data(){
- vcc = ina219.getBusVoltage_V();
+void CurrentMeter::read() {
+ if (ulp_ina219_error )
+ return;
+
+ // test for errcode from ulp and set value from rtc memory
+ float _current = calc_aggr_accum( (float) ulp_ina219_aggr_current);
+ ulp_ina219_aggr_current = 0;
+
+ *aggr_current += _current;
+ /*
+ printf_w("current list\n");
+ for (uint32_t i = 0; i< 60; i++){
+ printf_w("%d.\t %fma\n", i, (float) ( (int16_t)ulp_ina219_current_table[i]) / (float)ulp_ina219_currentDivider_mA);
+ }
+*/
+ vcc = (float) (ulp_ina219_voltage * 4 ) * 0.001;
+ current = (float) ulp_ina219_current/(float) ulp_ina219_currentDivider_mA;
+ batLevel = battery->level( vcc *1000 );
+
+ printf_w("ina219 :2: --- c %f, p %f, bat level %d\n", _current, *aggr_current, battery->level( vcc *1000 ) );
+
+ printUlpData();
}
void CurrentMeter::wake(){
- ina219.powerSave(false);
+ ulp_sensors_switch_extern |= SENSOR_INA219;
}
void CurrentMeter::sleep(){
- ina219.powerSave(true);
+ ulp_sensors_switch_extern &= ~SENSOR_INA219;
}
-float CurrentMeter::Vcc(){
+float CurrentMeter::getVcc(){
return vcc;
}
-int cmp_volt(const uint32_t *a, const uint32_t *b)
+uint8_t CurrentMeter::getBatLevel(){
+ return batLevel;
+}
+
+void CurrentMeter::printUlpData(){
+ printf_w("ina219 data: config %x, current %fmA, current_aggr_raw %u, current_aggr %fmA, current_aggr_sr %fmA, "
+ "voltage %fV, "
+ "currentDivider_mA = %d, calValue = %u"
+ "\n",
+ (uint16_t)ulp_ina219_config,
+ (float) ulp_ina219_current/(float)ulp_ina219_currentDivider_mA ,
+ ulp_ina219_aggr_current,
+ *aggr_current, current_since_reboot,
+ (float)(ulp_ina219_voltage *4)*0.001,
+ (uint16_t) ulp_ina219_currentDivider_mA,
+ (uint16_t) ulp_ina219_calValue
+
+ );
+}
+//============================================================================================
+TimeMeter::TimeMeter(){
+
+}
+
+void TimeMeter::init(){
+ Sensor::init();
+}
+
+time_t TimeMeter::currentTime(){
+ return now();
+}
+
+uint8_t TimeMeter::bcd2dec(uint8_t bcd)
{
- if (*a < *b) {
- return -1;
- } else if (*a == *b) {
+ return ((bcd / 16) * 10) + (bcd % 16);
+}
+
+
+uint8_t TimeMeter::dec2bcd(uint8_t n)
+{
+ uint16_t a = n;
+ byte b = (a*103) >> 10;
+ return n + b*6;
+}
+
+bool TimeMeter::read_data_to_tm(tmElements_t &tm) {
+ if (ulp_ds3231_error )
+ return false;
+
+ printUlpData();
+
+ uint32_t yr = bcd2dec(ulp_ds3231_year);
+ if( yr > 99)
+ yr = yr - 1970;
+ else
+ yr += 30;
+
+ tm.Year = yr;
+ tm.Month = bcd2dec(ulp_ds3231_month);
+ tm.Day = bcd2dec(ulp_ds3231_day);
+ tm.Hour = bcd2dec(ulp_ds3231_hour);
+ tm.Minute = bcd2dec(ulp_ds3231_minute);
+ tm.Second = bcd2dec(ulp_ds3231_second);
+ return true;
+}
+
+void TimeMeter::read() {
+ tmElements_t tm;
+ if ( true == read_data_to_tm(tm) )
+ setTime(makeTime(tm));
+}
+
+void TimeMeter::printUlpData(){
+
+ printf_w("ds3231 data: "
+ "year %d, month %d, day %d, dayOfWeek %d, "
+ "hour %d (raw hour %d), minute %d, second %d (year raw %d)"
+ ", reseted_steps %d\n",
+ bcd2dec(ulp_ds3231_year) + 2000, bcd2dec(ulp_ds3231_month), bcd2dec(ulp_ds3231_day), bcd2dec(ulp_ds3231_dayOfWeek),
+ bcd2dec(ulp_ds3231_hour), ulp_ds3231_hour, bcd2dec(ulp_ds3231_minute), bcd2dec(ulp_ds3231_second), ulp_ds3231_year,
+ ulp_lsm6ds3_reseted_steps);
+ /*
+ printf_w("ds3231 SET DATA: %02d:%02d:%02d %02d.%02d.%02d (%d dow) update_flag %d\n",
+ bcd2dec(ulp_ds3231_set_hour), bcd2dec(ulp_ds3231_set_minute), bcd2dec(ulp_ds3231_set_second),
+ bcd2dec(ulp_ds3231_set_day), bcd2dec(ulp_ds3231_set_month), bcd2dec(ulp_ds3231_set_year) + 2000,
+ bcd2dec(ulp_ds3231_set_dayOfWeek) , (uint16_t) ulp_ds3231_update_flag);
+*/
+}
+
+void TimeMeter::updateUlpTime(tmElements_t &tm){
+ ulp_ds3231_set_second = dec2bcd(tm.Second);
+ ulp_ds3231_set_minute = dec2bcd(tm.Minute);
+ ulp_ds3231_set_hour = dec2bcd(tm.Hour);
+ ulp_ds3231_set_dayOfWeek = dec2bcd(tm.Wday);
+ ulp_ds3231_set_day = dec2bcd(tm.Day);
+ ulp_ds3231_set_month = dec2bcd(tm.Month);
+ ulp_ds3231_set_year = dec2bcd(tm.Year - 30);
+ printf_w("year %d\n", ulp_ds3231_set_year);
+ ulp_ds3231_update_flag = 1;
+}
+
+RTC_DATA_ATTR uint32_t lastNtpUpdate = 0;
+
+time_t TimeMeter::updateNtpTime(){
+ ulp_ds3231_update_flag = 0;
+ if ( (lastNtpUpdate) && ( ((now() - lastNtpUpdate) ) < ntpUpdateInterval ) ) {
+ printf_w("updateNtpTime -- %d : %d\n", lastNtpUpdate, ntpUpdateInterval);
+ return 0;
+ }
+ printf_w("updateNtpTime: lastNtpUpdate %d, ntpUpdateInterval %d, status %d/%d\n",
+ lastNtpUpdate, ntpUpdateInterval, WiFi.status(), WL_CONNECTED);
+
+ if (WiFi.status() != WL_CONNECTED)
+ return 0;
+
+ Udp.begin(localPort);
+ // printf_w("Local port: %d", Udp.localPort() );
+ // printf_w("waiting for sync\n");
+ // setSyncProvider( TimeMeter::getNtpTime );
+ // setSyncInterval(300);
+ time_t net_time = getNtpTime();
+ printf_w("updateNtpTime: net_time %d\n", net_time);
+ if (0 == net_time)
return 0;
- } else {
- return 1;
+
+ tmElements_t tm;
+ breakTime(net_time, tm);
+
+ updateUlpTime(tm);
+ lastNtpUpdate = now();
+}
+
+time_t TimeMeter::getNtpTime()
+{
+ if (WiFi.status() != WL_CONNECTED)
+ return 0;
+
+ IPAddress ntpServerIP; // NTP server's ip address
+
+ while (Udp.parsePacket() > 0) ; // discard any previously received packets
+ printf_w("Transmit NTP Request\n");
+ // get a random server from the pool
+ WiFi.hostByName(ntpServerName, ntpServerIP);
+
+ printf_w("%s : %s\n", ntpServerName, ntpServerIP);
+ sendNtpPacket(ntpServerIP);
+ uint32_t beginWait = millis();
+ while (millis() - beginWait < 1500) {
+ int size = Udp.parsePacket();
+ if (size >= NTP_PACKET_SIZE) {
+ printf_w("Receive NTP Response\n");
+ Udp.read(packetBuffer, NTP_PACKET_SIZE); // read packet into the buffer
+ unsigned long secsSince1900;
+ // convert four bytes starting at location 40 to a long integer
+ secsSince1900 = (unsigned long)packetBuffer[40] << 24;
+ secsSince1900 |= (unsigned long)packetBuffer[41] << 16;
+ secsSince1900 |= (unsigned long)packetBuffer[42] << 8;
+ secsSince1900 |= (unsigned long)packetBuffer[43];
+ return secsSince1900 - 2208988800UL + timeZone * SECS_PER_HOUR;
+ }
}
+ printf_w("No NTP Response :-(\n");
+ return 0; // return 0 if unable to get the time
}
-uint32_t CurrentMeter::get_battery_voltage(void)
+void TimeMeter::sendNtpPacket(IPAddress &address)
{
- uint32_t ad_volt_list[BATTERY_ADC_SAMPLE];
- esp_adc_cal_characteristics_t characteristics;
-
- adc1_config_width(ADC_WIDTH_BIT_12);
- adc1_config_channel_atten(BATTERY_ADC_CH, ADC_ATTEN_11db);
- esp_adc_cal_characterize(ADC_UNIT_1, ADC_ATTEN_DB_11, ADC_WIDTH_BIT_12, ADC_VREF,
- &characteristics);
-/*
- for (uint32_t i = 0; i < BATTERY_ADC_SAMPLE; i++) {
- ESP_ERROR_CHECK(esp_adc_cal_get_voltage(BATTERY_ADC_CH,
- &characteristics, ad_volt_list + i));
+ // set all bytes in the buffer to 0
+ memset(packetBuffer, 0, NTP_PACKET_SIZE);
+ // Initialize values needed to form NTP request
+ // (see URL above for details on the packets)
+ packetBuffer[0] = 0b11100011; // LI, Version, Mode
+ packetBuffer[1] = 0; // Stratum, or type of clock
+ packetBuffer[2] = 6; // Polling Interval
+ packetBuffer[3] = 0xEC; // Peer Clock Precision
+ // 8 bytes of zero for Root Delay & Root Dispersion
+ packetBuffer[12] = 49;
+ packetBuffer[13] = 0x4E;
+ packetBuffer[14] = 49;
+ packetBuffer[15] = 52;
+ // all NTP fields have been given values, now
+ // you can send a packet requesting a timestamp:
+ Udp.beginPacket(address, 123); //NTP requests are to port 123
+ Udp.write(packetBuffer, NTP_PACKET_SIZE);
+ Udp.endPacket();
+}
+
+
+void TimeMeter::updateTime(int h, int m, int s){
+ tmElements_t tm;
+ if ( true == read_data_to_tm(tm) ) {
+ tm.Hour = h;
+ tm.Minute = m;
+ tm.Second = s;
+ updateUlpTime(tm);
+ setTime(makeTime(tm));
}
-*/
- qsort(ad_volt_list, BATTERY_ADC_SAMPLE, sizeof(uint32_t),
- (int (*)(const void *, const void *))cmp_volt);
+}
+
+void TimeMeter::updateDate(int d, int m, int y){
+ tmElements_t tm;
+ if ( true == read_data_to_tm(tm) ) {
+
+ tm.Year = y;
+ tm.Month = m;
+ tm.Day = d;
- // mean value
- return ad_volt_list[BATTERY_ADC_SAMPLE >> 1] * BATTERY_ADC_DIV;
+ updateUlpTime(tm);
+ setTime(makeTime(tm));
+ }
}
+
+//============================================================================================
diff --git a/sensors.hpp b/sensors.hpp
index 5cd4978..91c2faa 100644
--- a/sensors.hpp
+++ b/sensors.hpp
@@ -8,105 +8,80 @@
//define _USE_BMI160_ 1
#include "utils.h"
-#include "MAX30100_PulseOximeter.h"
-#include
+#include
+#include
+#include
-#ifdef _USE_LSM6DS3_
-#include
-#endif
-#ifdef _USE_BMI160_
-#include
-#endif
-
-#if defined(_USE_MLX90614_)
-#include
-#endif
-#if defined(_USE_MLX90615_)
-#include
-#endif
+#include "battery.h"
class Sensor {
private:
int i2c_addr;
- public:
- bool inited = false;
+ // virtual void read_data_direct();
+
+ protected:
+ bool inited = false;
+ virtual void init_direct();
+
+ public:
void init();
- void init(bool is_after_deepsleep);
- void update();
void sleep();
void wake();
-
- void read_data();
-};
-#ifdef _USE_BMI160_
-class GyroscopeBMI160 : public Sensor {
- private:
- const int i2c_addr = 0x69;
- BMI160GenClass gyro;
+ void on();
+ void off();
- int32_t steps;
- public:
- GyroscopeBMI160();
- void init(bool clearStep);
- void update();
- void sleep();
- void wake();
+ void read();
- void read_data();
- uint16_t StepCount();
};
-typedef GyroscopeBMI160 Gyroscope;
-#endif
-#ifdef _USE_LSM6DS3_
+
class GyroscopeLSM6DS3 : public Sensor {
private:
const int i2c_addr = 0x6A;
- LSM6DS3Core *gyro;
int32_t steps;
+
+ void printUlpData();
+
public:
GyroscopeLSM6DS3();
~GyroscopeLSM6DS3();
- void init(bool clearStep);
- void update();
+ void init();
void sleep();
void wake();
- void read_data();
- uint16_t StepCount();
+ void read();
+ uint16_t getStepCount();
};
typedef GyroscopeLSM6DS3 Gyroscope;
-#endif
+
class ThermoMeter: public Sensor {
private:
-#if defined(_USE_MLX90614_)
- IRTherm therm;
-#elif defined(_USE_MLX90615_)
- MLX90615 therm;
-#endif
float AmbientTempC;
float ObjectTempC;
+
+ float raw_temp_to_C(uint16_t _t);
+ void printUlpData();
+
public:
ThermoMeter();
void init();
- void update();
void sleep();
void wake();
- void read_data();
- float AmbientC();
- float ObjectC();
+ void read();
+ float getAmbientC();
+ float getObjectC();
};
class PulseMeter: public Sensor {
private:
- PulseOximeter pulse;
+// PulseOximeter pulse;
float heartRate;
float spO2;
@@ -115,35 +90,90 @@ class PulseMeter: public Sensor {
PulseMeter();
void init();
- void update();
void sleep();
void wake();
- void read_data();
- float HeartRate();
- float SpO2();
+ void read();
+ float getHeartRate();
+ float getSpO2();
};
+/*
#define BATTERY_ADC_CH ADC1_CHANNEL_4 // GPIO 32
#define BATTERY_ADC_SAMPLE 33
#define BATTERY_ADC_DIV 1
#define ADC_VREF 1128 // ADC calibration data
-
+*/
class CurrentMeter: public Sensor {
private:
- Adafruit_INA219 ina219;
-
- float vcc;
+ Battery *battery;
+ float vcc;
+ float current;
+ uint8_t batLevel;
+ float current_since_reboot;
+ float *aggr_current;
+ float aggr_current_prev;
+
+ int32_t tm = 1;
+ int32_t tb = 60;
+ // prew_aggr_current;
+
+ float calc_aggr_accum(float mA);
+ void printUlpData();
public:
CurrentMeter();
- void init();
+ void init(float *current);
void sleep();
void wake();
- uint32_t get_battery_voltage(void);
+ // uint32_t get_battery_voltage(void);
+
+ void read();
+ float getVcc();
+ // float getCurrent();
+ uint8_t getBatLevel();
+};
+
+const int NTP_PACKET_SIZE = 48; // NTP time is in the first 48 bytes of message
+
+class TimeMeter: public Sensor {
+ private:
+ uint8_t bcd2dec(uint8_t bcd);
+ uint8_t dec2bcd(uint8_t n);
+
+ void printUlpData();
- void read_data();
- float Vcc();
+ WiFiUDP Udp;
+ char *ntpServerName = "europe.pool.ntp.org";
+ uint32_t localPort = 8888; // local port to listen for UDP packets
+
+ byte packetBuffer[NTP_PACKET_SIZE]; //buffer to hold incoming & outgoing packets
+
+ uint32_t ntpUpdateInterval = 3600;
+ int timeZone = 3; // Moscow Time
+
+ void updateUlpTime(tmElements_t &tm);
+ void sendNtpPacket(IPAddress &address);
+ time_t getNtpTime();
+ bool read_data_to_tm(tmElements_t &tm) ;
+ public:
+ TimeMeter();
+
+ void init();
+ void read();
+
+ void sleep();
+ void wake();
+
+ time_t currentTime();
+
+ time_t updateNtpTime();
+
+ void updateTime(int h, int m, int s);
+ void updateDate(int d, int m, int y);
+ void setTimeZone(int8_t tz) { timeZone = tz; }
+
+ // DS3231M_Class dateNow() {return DS3231M;}
};
#endif
diff --git a/stack.s b/stack.s
new file mode 100644
index 0000000..7bf677f
--- /dev/null
+++ b/stack.s
@@ -0,0 +1,165 @@
+/* ULP Example: Read temperautre in deep sleep
+
+ This example code is in the Public Domain (or CC0 licensed, at your option.)
+
+ Unless required by applicable law or agreed to in writing, this
+ software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+ CONDITIONS OF ANY KIND, either express or implied.
+
+ This file contains assembly code which runs on the ULP.
+
+*/
+
+/* ULP assembly files are passed through C preprocessor first, so include directives
+ and C macros may be used in these files
+ */
+// RTC_GPIO_6 == GPIO_25
+// RTC_GPIO_7 == GPIO_26
+// RTC_GPIO_9 == GPIO_32
+// RTC_GPIO_8 == GPIO_33
+//const gpio_num_t GPIO_SCL = GPIO_NUM_26;
+//const gpio_num_t GPIO_SDA = GPIO_NUM_25;
+
+.set SCL_PIN, 9
+.set SDA_PIN, 6
+
+
+.set SENSOR_STATUS_OK, 0x00
+.set SENSOR_STATUS_HW_ERROR, 0X01
+.set SENSOR_STATUS_CFG_ERROR, 0X02
+.set SENSOR_STATUS_READ_ERROR, 0X03
+
+
+.set SENSOR_WSTATUS_AWAKE, 0x01
+.set SENSOR_WSTATUS_SLEEP, 0x02
+
+
+.macro push rx
+ st \rx,r3,0
+ sub r3,r3,1
+.endm
+
+.macro pop rx
+ add r3,r3,1
+ ld \rx,r3,0
+.endm
+
+// Prepare subroutine jump
+.macro psr
+ .set addr,(.+16)
+ move r1,addr
+ push r1
+.endm
+
+// Return from subroutine
+.macro ret
+ pop r1
+ jump r1
+.endm
+
+.macro store _addr val
+ move r1,\_addr // store result
+ move r0,\val //
+ st r0,r1,0
+.endm
+
+.macro store_mem addr_src addr_dst
+ move r2, \addr_src
+ move r1, \addr_dst
+ ld r0, r2, 0
+ st r0, r1, 0 // Save data in memory
+.endm
+
+.macro set_flag addr val
+ move r2, \addr
+ ld r0, r2, 0
+ or r0, r0, \val
+ st r0, r2, 0
+.endm
+
+.macro del_flag addr val
+ move r2, \addr
+ ld r0, r2, 0
+ and r0, r0, \val
+ st r0, r2, 0
+.endm
+
+.macro inc addr
+ move r2, \addr
+ ld r0, r2, 0
+ add r0, r0, 1 // Increment
+ st r0, r2, 0 // Save data in memory 1
+.endm
+
+.macro dec addr
+ move r2, \addr
+ ld r0, r2, 0
+ sub r0, r0, 1 // Increment
+ st r0, r2, 0 // Save data in memory 1
+.endm
+
+
+
+.macro if_flag test_var SENSOR_IDX handler
+ move r0, \test_var
+ ld r0, r0, 0
+ and r0, r0, \SENSOR_IDX // mask
+ jumpr \handler,\SENSOR_IDX,eq
+.endm
+
+.macro if_noflag test_var SENSOR_IDX handler
+ move r0, \test_var
+ ld r0, r0, 0
+ and r0, r0, \SENSOR_IDX // mask
+ jumpr \handler,0,eq
+.endm
+
+.macro if_val_eq var test_var handler
+ move r0, \var
+ ld r0, r0, 0
+ jumpr \handler,\test_var,eq
+.endm
+
+.macro if_val_noteq var test_var handler
+ move r0, \var
+ ld r0, r0, 0
+ jumpr \handler,\test_var,lt
+ jumpr \handler,\test_var,gt
+.endm
+
+// don't use r1 in DATA argumemt!!! =)
+.macro read_i2c ADDR REG fail
+ move r1, \ADDR
+ push r1
+ move r1,\REG
+ push r1
+ psr
+ jump read8
+ add r3,r3,2 // remove call parameters from stack
+ move r1,r0 // save result
+ move r0,r2 // test for error
+ jumpr \fail,1,ge
+.endm
+
+// don't use r1 in DATA argumemt!!! =)
+.macro write_i2c ADDR REG DATA fail
+ move r1,\ADDR
+ push r1
+ move r1,\REG
+ push r1
+ move r1,\DATA
+ push r1
+ psr
+ jump write8
+ add r3,r3,3 // remove call parameters from stack
+ move r0, r2 // test for error in r2
+ jumpr \fail,1,ge
+.endm
+
+
+.macro update_i2c_register ADDR REG fail mask value
+ read_i2c \ADDR \REG \fail
+ and r0, r1, \mask // take result from r1
+ or r0, r0, \value
+ write_i2c \ADDR \REG r0 \fail
+.endm
diff --git a/stat_records.cpp b/stat_records.cpp
new file mode 100644
index 0000000..c522571
--- /dev/null
+++ b/stat_records.cpp
@@ -0,0 +1,947 @@
+#include "stat_records.hpp"
+#include "utils.h"
+#include
+
+RTC_DATA_ATTR statRecord_t RTC_RECORDS[CACHE_RECORD_CNT];
+RTC_DATA_ATTR uint8_t statRecord_cnt = 0;
+
+
+char *logFilenamePattern = "%04d-%02d-%02d.txt";
+char *logDirName = "/log";
+char *skimDirName = "/skim";
+
+tmElements_t toTmElement(struct tm *tm)
+{
+ tmElements_t tme;
+ tme.Second = tm->tm_sec;
+ tme.Minute = tm->tm_min;
+ tme.Hour = tm->tm_hour;
+ tme.Wday = tm->tm_wday + 1; // day of week, sunday is day 1
+ tme.Day = tm->tm_mday;
+ tme.Month = tm->tm_mon + 1; // Jan = 1 in tme, 0 in tm
+ tme.Year = (uint8_t)(tm->tm_year - 70); // offset from 1970;
+ return tme;
+}
+
+int buildDayFName(char *inputBuffer, int inputBuffer_size, char *dir, int16_t year, int8_t month, int8_t day)
+{
+ char patternStr[64];
+ snprintf(patternStr, sizeof(patternStr), logFilenamePattern, year, month, day);
+ return snprintf(inputBuffer, inputBuffer_size,"%s/%s", dir, patternStr);
+}
+
+int fidCompare(const void *i, const void *j)
+{
+ return *(int32_t *)i - *(int32_t *)j;
+}
+
+time_t setZeroHoursToTime(time_t _date)
+{
+ struct tm *tmstruct = localtime(&_date);
+ tmstruct->tm_hour = 0;
+ tmstruct->tm_min = 0;
+ tmstruct->tm_sec = 0;
+ tmElements_t tme = toTmElement(tmstruct);
+ return makeTime(tme);
+}
+
+uint32_t calcNumDays(time_t date1, time_t date2)
+{
+ time_t d1 = setZeroHoursToTime(date1);
+ time_t d2 = setZeroHoursToTime(date2);
+ return 1 + ((d1 - d2) / 86400);
+}
+
+uint32_t getArrayLogfiles(time_t lastKnownData, int32_t *resData, uint32_t resDataSize)
+{
+ char *dirName = logDirName;
+ char *filenamePattern = logFilenamePattern;
+ char fPattern[64];
+ int32_t file_cnt = 0;
+ int32_t y=0, m=0, d=0;
+ int32_t dates[20];
+ int32_t cur_date;
+
+ printf_w("get_arrayfiles: start\n");
+ snprintf(fPattern, sizeof(fPattern), "%s/%s", dirName, filenamePattern);
+ printf_w("get_arrayfiles: scan %s\n", fPattern );
+
+ File root = SPIFFS.open(dirName);
+ File file = root.openNextFile();
+ while (file) {
+ if (file_cnt >= sizeof(dates))
+ break;
+
+ if (file.isDirectory()) {
+ file = root.openNextFile();
+ continue;
+ } else {
+ sscanf(file.name(), fPattern, &y, &m, &d );
+ dates[file_cnt] = y * 10000 + m*100 + d;
+ file_cnt += 1;
+ }
+ file = root.openNextFile();
+ }
+ qsort(dates, file_cnt, sizeof(int32_t), fidCompare);
+ printf_w("get_arrayfiles: file_cnt %d\n", file_cnt );
+
+/*
+ for (int32_t i = 0; i < file_cnt ; i++ )
+ printf_w("get_arrayfiles: date %d\n", dates[i] );
+
+*/
+ int32_t diffDay = calcNumDays(now(), lastKnownData);
+ if (diffDay > file_cnt)
+ diffDay = file_cnt;
+ printf_w("get_arrayfiles: diffDay %d\n", diffDay);
+
+ int32_t startIdx = file_cnt - diffDay;
+ int32_t idx = 0;
+ for (int32_t i = 0; i < diffDay; i++) {
+ if (i >= resDataSize)
+ break;
+
+ idx = startIdx + i;
+ printf_w("--- res to send -- %d\n", dates[idx] );
+ resData[i] = dates[idx];
+ }
+
+ printf_w("get_arrayfiles: stop\n");
+ return diffDay;
+}
+
+void _rotateLogs( char * dirName, char *filenamePattern, int maxFilesCnt)
+{
+ File root = SPIFFS.open(dirName);
+ if (!root) {
+ printf_w("rotateLogs: can't open dir '%s'\n", dirName);
+ return;
+ }
+ if(!root.isDirectory()){
+ printf_w("rotateLogs: root is not dir '%s'\n", dirName);
+ return;
+ }
+ int32_t y=0, m=0, d=0;
+ int32_t min_date = 30000000, cur_date;
+ int32_t file_cnt = 0;
+ char fPattern[64];
+
+ int32_t dates[20];
+ snprintf(fPattern, sizeof(fPattern), "%s/%s", dirName, filenamePattern);
+ File file = root.openNextFile();
+ while (file) {
+ if (file_cnt == sizeof(dates))
+ break;
+
+ if (file.isDirectory())
+ continue;
+ else {
+ sscanf(file.name(), fPattern, &y, &m, &d );
+ cur_date = y * 10000 + m*100 + d;
+ dates[file_cnt] = cur_date;
+ file_cnt += 1;
+ }
+ file = root.openNextFile();
+ }
+
+ if (file_cnt > maxFilesCnt) {
+ qsort (dates, file_cnt, sizeof(int32_t), fidCompare);
+ int32_t cnt_del = file_cnt - maxFilesCnt;
+ for (int32_t i = 0; i < cnt_del; i++) {
+ min_date = dates[i];
+ char path[32];
+ int d = min_date % 100;
+ int m = (min_date / 100) % 100;
+ int y = min_date / 10000;
+ buildDayFName(path, sizeof(path), dirName, y, m, d);
+
+ // printf_w("delete , %s\n", path);
+ SPIFFS.remove(path);
+ }
+ }
+/*
+ char *to_del = "/log/2094-09-20.txt";
+ if ( SPIFFS.exists(to_del) )
+ SPIFFS.remove(to_del);
+*/
+}
+
+//--
+HourRecordAvg::HourRecordAvg()
+{
+ startValue = 0;
+ endValue = 0;
+ summ = 0;
+ cnt = 0;
+}
+
+void HourRecordAvg::add(float value)
+{
+ if (0 == cnt)
+ startValue = value;
+
+ summ += value;
+ cnt += 1;
+ endValue = value;
+}
+
+void HourRecordAvg::addBack(float value)
+{
+ if (0 == cnt)
+ endValue = value;
+
+ summ += value;
+ cnt += 1;
+ startValue = value;
+}
+
+float HourRecordAvg::avg()
+{
+ return (summ/ cnt);
+}
+
+float HourRecordAvg::diffAsc()
+{
+ if (startValue > endValue) {
+ printf_w("startValue %d, endValue %d, sould asc", startValue, endValue);
+ return 0;
+ }
+ return (endValue - startValue);
+}
+
+float HourRecordAvg::diff()
+{
+ return (endValue - startValue);
+}
+
+float HourRecordAvg::sum()
+{
+ return summ;
+}
+
+void HourRecordAvg::print()
+{
+ printf_w("HourRecordAvg: startValue %.2f, endValue %.2f, summ %.2f, cnt %d\n", startValue, endValue, summ, cnt);
+}
+
+//--
+DailyRecordsAvg::DailyRecordsAvg()
+{
+ //avg_hour_record val[HOURS_IN_DAY];
+}
+
+HourRecordAvg * DailyRecordsAvg::hourRecord(uint8_t hourIdx)
+{
+ if (hourIdx >= HOURS_IN_DAY)
+ return NULL;
+ return &val[hourIdx];
+}
+
+int DailyRecordsAvg::aggregateString(uint8_t hourIdx, float value)
+{
+ if (hourIdx >= HOURS_IN_DAY)
+ return NULL;
+ val[ hourIdx].add(value);
+ return hourIdx;
+}
+
+int DailyRecordsAvg::aggregateStringBack(uint8_t hourIdx, float value)
+{
+ if (hourIdx >= HOURS_IN_DAY)
+ return -1;
+ val[hourIdx].addBack(value);
+
+ return hourIdx;
+}
+
+void fileRecord::castData(char **dataPointers)
+{
+}
+
+bool fileRecord::parseLine(char *_buffer, char delimiter)
+{ // ahtung - spoil _buffer!
+ if (!_buffer)
+ return false;
+
+ bool nextIsNewData = true;
+ int32_t _buffer_len = strlen(_buffer);
+ int32_t field_cnt = 0;
+
+ char **dataPointers = (char **) calloc(max_cnt, sizeof(char*) );
+ for (int i = 0; i < _buffer_len ; i++) {
+ if (nextIsNewData) {
+ if (field_cnt < max_cnt)
+ dataPointers[field_cnt] = & _buffer[i];
+ field_cnt++;
+ nextIsNewData = false;
+ }
+ if (delimiter == _buffer[i]) {
+ _buffer[i] = 0;
+ nextIsNewData = true;
+ }
+ }
+
+ if (field_cnt != max_cnt) {
+ free(dataPointers);
+ return false;
+ }
+
+
+ if (!_data) {
+ free(dataPointers);
+ return false;
+ }
+ // aaand cast
+ castData(dataPointers);
+ free(dataPointers);
+ return true;
+}
+
+
+SkimFileRecord::SkimFileRecord()
+{
+ max_cnt = SKIMREC_MAX_IDX;
+ _data = (skimData_t*) calloc(1, sizeof(skimData_t));
+ needWrite = false;
+}
+
+SkimFileRecord::~SkimFileRecord()
+{
+ free(_data) ;
+}
+
+void SkimFileRecord::castData(char **dataPointers)
+{
+ if (!_data) {
+ print_w("SkimFileRecord::castData: empty data");
+ return;
+ }
+ skimData_t* _d = (skimData_t*)_data;
+
+ _d->Vcc = atof(dataPointers[SKIMREC_VCC_IDX]);
+ _d->Steps = atoi(dataPointers[SKIMREC_STEPS_IDX]);
+ _d->ObjectTempC = atof(dataPointers[SKIMREC_OBJT_IDX]);
+}
+
+
+float SkimFileRecord::getField(skimrecord_idx_t idx)
+{
+ float ret;
+ skimData_t* _d = (skimData_t*)_data;
+
+ switch(idx) {
+ case SKIMREC_VCC_IDX:
+ ret = _d->Vcc;
+ break;
+ case SKIMREC_STEPS_IDX:
+ ret = (float) _d->Steps;
+ break;
+ case SKIMREC_OBJT_IDX:
+ ret = _d->ObjectTempC;
+ break;
+ default :
+ ret = 0;
+ }
+ return ret;
+}
+
+
+skimData_t *SkimFileRecord::get()
+{
+ return (skimData_t*)_data;
+}
+
+void SkimFileRecord::set(skimData_t *s)
+{
+ if (!_data){
+ print_w("SkimFileRecord::set: empty data, it nonsence");
+ return;
+ }
+
+ memcpy( _data, s, sizeof(skimData_t));
+}
+
+void SkimFileRecord::setWritable()
+{
+ needWrite = true;
+}
+
+bool SkimFileRecord::isWritable()
+{
+ return needWrite;
+}
+
+bool SkimFileRecord::serialize(char *str, size_t str_size)
+{
+ if (!str)
+ return false;
+
+ skimData_t* _d = (skimData_t*)_data;
+ snprintf(str, str_size, "%.2f\t%d\t%.2f\n",
+ _d->Vcc, _d->Steps, _d->ObjectTempC);
+ return true;
+}
+
+void SkimFileRecord::print()
+{
+ skimData_t* _d = (skimData_t*)_data;
+ printf_w("%.2f\t%d\t%.2f\n",
+ _d->Vcc, _d->Steps, _d->ObjectTempC);
+}
+
+FileRecord::FileRecord()
+{
+ max_cnt = REC_MAX_IDX;
+ _data = (statRecord_t*) calloc(1, sizeof(statRecord_t));
+}
+
+FileRecord::~FileRecord()
+{
+ free(_data) ;
+}
+
+void FileRecord::castData(char **dataPointers)
+{
+ statRecord_t* _d = (statRecord_t*)_data;
+ _d->Time = atoi(dataPointers[REC_TIME_IDX]);
+ _d->Steps = atoi(dataPointers[REC_STEPS_IDX]);
+ _d->HeartRate = atof(dataPointers[REC_HR_IDX]);
+ _d->AmbientTempC = atof(dataPointers[REC_AMBT_IDX]);
+ _d->ObjectTempC = atof(dataPointers[REC_OBJT_IDX]);
+ _d->Vcc = atof(dataPointers[REC_VCC_IDX]);
+
+ time_t rec_date = _d->Time;
+ struct tm *tmstruct = localtime(&rec_date);
+ memcpy(&_tm, tmstruct, sizeof(struct tm));
+ // printf_w("FileRecord::castData: time_stamp %d, %d.%d.%d %d\n", rec_date, (_tm.tm_year) + 1900, (_tm.tm_mon) + 1, _tm.tm_mday, _tm.tm_hour );
+}
+
+//============================================================================================
+//hourly stat for list of sensors (for showing graph)
+SkimData::SkimData(uint8_t d, uint8_t m , uint16_t y)
+{
+ _day = d;
+ _month = m;
+ _year = y;
+ r4s = NULL;
+}
+
+SkimData::~SkimData()
+{
+ if (r4s)
+ free(r4s);
+}
+
+uint8_t SkimData::read()
+{
+ char fname[64];
+ char lineString[255];
+
+ File root = SPIFFS.open(skimDirName);
+ if (!root)
+ SPIFFS.mkdir(skimDirName);
+ else
+ root.close();
+
+ buildDayFName(fname, sizeof(fname), skimDirName, _year , _month, _day);
+ printf_w("SkimData::read fname %s\n", fname );
+
+ File in_file = SPIFFS.open(fname, "r");
+ if (in_file.size() > 560) {
+ printf_w("SkimData::read fname %s too big\n", fname );
+ in_file.close();
+ SPIFFS.remove(fname);
+ stat.hoursInFile = -1;
+ return stat.hoursInFile;
+ }
+ uint8_t line = 0;
+ bool parse_error = false;
+ while (in_file.available()) {
+ String buffer = in_file.readStringUntil('\n');
+ if (0 == line) { // skip version
+ line++;
+ continue;
+ }
+ lineString[0] = '\0';
+ buffer.toCharArray(lineString, sizeof(lineString) );
+ stat.byHours[ line - 1 ].parseLine(lineString, '\t');
+ printf_w("'%d' -- '%s'\n", line - 1, lineString);
+ if (line - 1 > 23) {
+ printf_w("SkimData::read: alarm! WTF! %d records in skim file", line - 1);
+ parse_error = true;
+ break;
+ }
+ line++;
+ }
+ in_file.close();
+
+ if (!parse_error)
+ stat.hoursInFile = (line > 0) ? (line - 1) : -1;
+ else {
+ SPIFFS.remove(fname);
+ stat.hoursInFile = -1;
+ }
+ stat.hoursInTotal = stat.hoursInFile;
+ printf_w("SkimData::read fname %s, hoursInFile %d \n", fname, stat.hoursInFile );
+
+ return stat.hoursInFile;
+}
+
+void SkimData::write()
+{
+ char fname[64];
+ char lineString[255];
+ // return;////////
+ buildDayFName(fname, sizeof(fname), skimDirName, _year , _month, _day);
+ printf_w("SkimData::write, try to write %s \n", fname);
+
+ char *modifier = "w";
+ if (SPIFFS.exists(fname) )
+ modifier = "a";
+
+ File in_file = SPIFFS.open(fname, modifier);
+ if (!in_file)
+ printf_w("file open failed %s\n", fname);
+
+ if ("w" == modifier)
+ in_file.print("V:1\n");
+ // _rotateLogs( skimDirName, logFilenamePattern, maxFilesCnt);
+
+ for (int h = 0; h < HOURS_IN_DAY; h++ ) {
+ if (stat.byHours[h].isWritable()) {
+ stat.byHours[h].serialize(lineString, sizeof(lineString) );
+ in_file.print(lineString);
+ printf_w("SkimData::write: %d --- '%s'\n", h, lineString);
+ }
+ // stat.byHours[h].set(&new_record);
+ }
+
+ in_file.close();
+}
+
+bool SkimData::processLine(char *lineString)
+{
+ FileRecord fr;
+ bool parsed = fr.parseLine(lineString, '\t');
+ if (!parsed) {
+ printf_w("WTF! bad log format\n");
+ return false;
+ }
+
+ struct tm *_rec = fr.tm();
+ if (_day != _rec->tm_mday ||
+ _month != (_rec->tm_mon + 1) ||
+ _year != (_rec->tm_year + 1900)) {
+ printf_w("skip line, date in file %d-%d-%d, fname ' %d-%d-%d'\n",
+ _rec->tm_mday , _rec->tm_mon + 1 , _rec->tm_year + 1900,
+ _day, _month, _year);
+ return true;
+ }
+
+ if (stat.hoursInFile > fr.hour()) {
+ printf_w("Stop Scan %d reached, fr.hour %d\n", stat.hoursInFile, fr.hour() );
+ return false;
+ }
+
+ if (!r4s)
+ r4s = (rawForSkim_t*) calloc (1, sizeof(rawForSkim_t));
+
+ statRecord_t *stat_rec = fr.data();
+ if (stat_rec &&
+ (0 != stat_rec->ObjectTempC && 0 != stat_rec->Vcc)
+ )
+ aggreggateRecord(fr.hour() , stat_rec) ;
+
+ return true;
+}
+
+bool SkimData::aggreggateRecord(int _hour, statRecord_t *stat_rec)
+{
+ r4s->Steps.aggregateStringBack( _hour , stat_rec->Steps);
+ r4s->Temp.aggregateStringBack( _hour , stat_rec->ObjectTempC);
+ r4s->Vcc.aggregateStringBack( _hour , stat_rec->Vcc);
+
+ r4s->startHourInLog = _hour;
+ if (0 == r4s->endHourInLog)
+ r4s->endHourInLog = _hour;
+}
+
+bool SkimData::prepareFromMemory()
+{
+ if (statRecord_cnt > 0 && !r4s)
+ r4s = (rawForSkim_t*) calloc (1, sizeof( rawForSkim_t) );
+
+ for (uint8_t i = statRecord_cnt; i > 0 ; i--) {
+ if (!RTC_RECORDS[i-1].Time )
+ continue;
+ printf_w("SkimData::prepareFromMemory i %d, %d \n",
+ i, RTC_RECORDS[i-1].Time);
+
+ struct tm *_tm = localtime(&RTC_RECORDS[i-1].Time);
+ aggreggateRecord( _tm->tm_hour, &RTC_RECORDS[i-1]);
+ }
+}
+
+void SkimData::prepareFromLog()
+{
+ char fname[64];
+ char lineString[255];
+
+ buildDayFName(fname, sizeof(fname), logDirName, _year , _month, _day);
+
+ File f = SPIFFS.open(fname, "r");
+
+ long pos = f.size(), line_pos = 0;
+
+ char buf[MAX_LINE_SIZE];
+ if (0 == pos) {
+ return;
+ }
+ printf_w("SkimData::prepareFromLog fname %s, pos %d\n", fname, pos ); //
+
+ line_pos = MAX_LINE_SIZE - 1;
+ buf[ line_pos ] = 0;
+ line_pos -= 1;
+
+ char *dataPointers[REC_MAX_IDX];
+ pos -= 1; // skip eof
+
+ while (true) {
+ if (-1 == pos)
+ break;
+
+ f.seek(pos);
+ char _byte = f.peek();
+ if (-1 == _byte)
+ break;
+ if ('\n' == _byte && line_pos == MAX_LINE_SIZE - 2) { // 1st char since end
+ pos -= 1;
+ continue;
+ }
+ // printf_w("SkimData::prepareFromLog fname %s, _byte %d \n" ,fname , _byte);
+
+
+ if (0 == line_pos) { //
+ pos -= 1;
+ continue;
+ }
+
+ buf[line_pos] = _byte;
+
+ if ('\n' == _byte) {
+ char *lineString = &buf[line_pos + 1];
+ // printf_w("SkimData::prepareFromLog fname %s, get line '%s', pos %d, line_pos %d \n", fname, lineString, pos, line_pos); //
+ if (false == processLine(lineString))
+ break;
+
+ line_pos = MAX_LINE_SIZE - 1;
+ }
+
+ line_pos -= 1;
+ pos -= 1;
+ }
+}
+
+bool SkimData::isCurrentHour( uint8_t _hour)
+{
+ if ((day() == _day) &&
+ (month() == _month) &&
+ (year() == _year) &&
+ (hour() == _hour)) // use time lib, uuuuglyyyyy
+ return true;
+
+ return false;
+}
+
+bool SkimData::isToday( )
+{
+ if ((day() == _day) &&
+ (month() == _month) &&
+ (year() == _year)
+ ) // use time lib, uuuuglyyyyy
+ return true;
+
+ return false;
+}
+
+void skimData_t::fill_data(float vcc , uint32_t steps , float objC)
+{
+ Vcc = vcc;
+ Steps = steps;
+ ObjectTempC = objC;
+}
+
+bool SkimData::processRaw()
+{
+ printf_w("SkimData::processRaw r4s->startHourInLog %d, r4s->endHourInLog %d, hoursInFile %d\n",
+ r4s->startHourInLog, r4s->endHourInLog, stat.hoursInFile);
+ int start_hour = r4s->startHourInLog;
+
+ if (r4s->startHourInLog > stat.hoursInFile + 1)
+ start_hour = stat.hoursInFile + 1;
+
+ for (int h = start_hour; h <= r4s->endHourInLog; h++) {
+ skimData_t new_record;
+
+ if (r4s->startHourInLog > h)
+ new_record.fill_data(0, 0, 0);
+ else {
+ HourRecordAvg *_s = r4s->Steps.hourRecord(h);
+ HourRecordAvg *_t = r4s->Temp.hourRecord(h);
+ HourRecordAvg *_v = r4s->Vcc.hourRecord(h);
+ if (!_s || ! _t || !_v ) {
+ printf_w( "alarm! can't take data for hour %d, date %02d.%02d%.04d. It should be ready. Stop process.\n", h, _day, _month, _year);
+ break;
+ }
+ // _s->print();_t->print(); _v->print();
+ new_record.fill_data(_v->avg(), (int)_s->diffAsc(), _t->avg());
+ }
+ printf_w("SkimData::processRaw: h %d, new_record(Steps %d, ObjectTempC %f, Vcc %f)\n",
+ h, new_record.Steps , new_record.ObjectTempC, new_record.Vcc );
+ stat.byHours[h].set(&new_record);
+ if (!isCurrentHour(h)) { // safe if it is not last hour today
+ stat.byHours[h].setWritable();
+ printf_w("SkimData::processRaw: setWritable -- h - %d\n", h);
+ }
+ }
+ stat.hoursInTotal = r4s->endHourInLog;
+ return true;
+}
+
+hourStat_t *SkimData::getStat()
+{
+ return &stat;
+}
+
+void SkimData::process()
+{
+ uint8_t _hour = read();
+ if (isToday())
+ prepareFromMemory(); // take 1'st from memory
+
+ prepareFromLog(); //
+
+ if (r4s) {
+ processRaw();
+ write();
+ }
+}
+
+//============================================================================================
+// File System
+
+void FileSystem::init()
+{
+ // logDirName = "/log" ;
+
+ // SPIFFS.begin(true);
+ /*
+ size_t totalBytes = 0, usedBytes = 0;
+ totalBytes = SPIFFS.totalBytes();
+ usedBytes = SPIFFS.usedBytes();
+
+ String temp_str = "totalBytes = " + String(totalBytes) + ", usedBytes = " + String(usedBytes);
+ println_w(temp_str);
+*/
+ char fname[32];
+
+ buildDayFName(fname, sizeof(fname), logDirName, year(), month(), day());
+ // scanLogDir(); //!!! log!!!
+ printf_w("open file %s\n", fname); // "открыть файл не удалось"
+
+ File root = SPIFFS.open(logDirName);
+ if (!root) SPIFFS.mkdir(logDirName);
+ else root.close();
+
+ char *modifier = "w";
+ if (SPIFFS.exists(fname))
+ modifier = "a";
+
+ if (!_canWrite)
+ return;
+ _file = SPIFFS.open(fname, modifier);
+ if (!_file) {
+ printf_w("file open failed %s\n", fname); // "открыть файл не удалось"
+ }
+ if ("w" == modifier) {
+ _file.print("V:1\n");
+
+ rotateLogs();
+ }
+ // rotateLogs();//0000000000
+}
+
+int FileSystem::filenameDate(char *inputBuffer, int inputBuffer_size, int16_t year, int8_t month, int8_t day)
+{
+ return buildDayFName( inputBuffer, inputBuffer_size, logDirName, year, month, day);
+}
+
+void FileSystem::catFile(File f)
+{
+ while (f.available())
+ write_w(f.read());
+}
+
+void catFile(File f)
+{
+ while (f.available())
+ write_w(f.read());
+}
+
+void del_file(char *f_name)
+{
+ SPIFFS.remove( f_name );
+}
+
+bool str_to_tm (char **dataPointers, struct tm *tm_res )
+{
+ if (!dataPointers[REC_TIME_IDX])
+ return false;
+ time_t _date = atoi(dataPointers[REC_TIME_IDX]);
+ struct tm *tmstruct = localtime(&_date);
+ if (!tmstruct)
+ return false;
+ memcpy(tm_res, tmstruct, sizeof(struct tm));
+ return true;
+}
+
+void FileSystem::listDir(fs::FS &fs, const char * dirname, bool need_cat, readFileHandler r_handler)
+{
+ File root = fs.open(dirname);
+ if (!root) {
+ printf_w("can't open dir '%s'\n", dirname);
+ return;
+ }
+ if (!root.isDirectory()){
+ printf_w("root is not dir '%s'\n", dirname);
+ return;
+ }
+
+ File file = root.openNextFile();
+ while (file) {
+ if (file.isDirectory())
+ continue;
+ else {
+ // time_t cr = 0;// file.getLastWrite();
+ // struct tm *tmstruct = localtime(&cr);
+ printf_w(" FILE: %s\tSIZE: %d\n", file.name(), file.size());
+ /*
+ print_w(file.name());
+ print_w("\tSIZE: ");
+ println_w(file.size());
+ file.getCreationTime();
+ */
+ if (need_cat)
+ catFile(file);
+ char *fname = strdup(file.name());
+ file.close();
+
+ if (r_handler)
+ r_handler(fname);
+
+ free(fname);
+ }
+ file = root.openNextFile();
+
+ }
+}
+
+bool parseLine(char *_buffer, char **dataPointers, int dataPointersSize, char delimiter)
+{ // ahtung - spoil buffer!
+ if (!_buffer)
+ return false;
+ bool nextIsNewData = true;
+ int32_t _buffer_len = strlen(_buffer);
+ int32_t field_cnt = 0;
+ for (int i = 0; i < _buffer_len ; i++) {
+ if (nextIsNewData) {
+ if (field_cnt < dataPointersSize)
+ dataPointers[field_cnt] = & _buffer[i];
+ field_cnt++;
+ nextIsNewData = false;
+ }
+ if (delimiter == _buffer[i]) {
+ _buffer[i] = 0;
+ nextIsNewData = true;
+ }
+ }
+ if (field_cnt != dataPointersSize)
+ return false;
+ return true;
+}
+
+int aggregate_for_hour(char **dataPointers, int dataPointersSize, record_idx_t field_idx, float*temp_data, int8_t *temp_data_cnt)
+{
+ int current_hour = 0;
+ if ( !dataPointers[REC_TIME_IDX] ||
+ field_idx >= dataPointersSize ||
+ !dataPointers[field_idx] )
+ return -1;
+
+ time_t _date = atoi( dataPointers[REC_TIME_IDX] );
+ struct tm *tmstruct = localtime(&_date);
+ temp_data[tmstruct->tm_hour] += atof( dataPointers[field_idx] );
+ temp_data_cnt[tmstruct->tm_hour] += 1;
+
+ return tmstruct->tm_hour;
+}
+
+void FileSystem::scanLogDir()
+{
+ // printf_w("start scanLogDir /\n");
+ // listDir(SPIFFS, "/");
+ printf_w("start scanLogDir %s\n", logDirName);
+ listDir(SPIFFS, logDirName, false, NULL);
+ printf_w("start scanLogDir %s\n", skimDirName);
+ listDir(SPIFFS, skimDirName, false, NULL);
+ /*
+ File fff = SPIFFS.open("/log/2021-01-17.txt");
+ catFile(fff);
+ fff.close();
+ */
+// listDir(SPIFFS, skimDirName, false, del_file);
+}
+
+void FileSystem::saveRecordsToFile()
+{
+ if ( !_canWrite )
+ return;
+
+ statRecord_t *record = &RTC_RECORDS[0];
+ char buf[MAX_LINE_SIZE];
+ for (int i = 0; i < statRecord_cnt; i++, record++) {
+ snprintf(buf, sizeof(buf), "%d\t%.2f\t%d\t%.2f\t%.2f\t%.2f\n",
+ record->Time, record->Vcc, record->Steps, record->HeartRate, record->AmbientTempC, record->ObjectTempC );
+ printf_w(buf);
+ _file.print(buf);
+ }
+}
+
+void FileSystem::rotateLogs()
+{
+ _rotateLogs( logDirName, filenamePattern, MAX_FILES_CNT);
+ _rotateLogs( skimDirName, logFilenamePattern, 30);
+}
+
+void FileSystem::writeLog( statRecord_t *record )
+{
+ // printf_w("RTC_RECORDS %p, statRecord_cnt %d\n", RTC_RECORDS, statRecord_cnt );
+ //return ;
+ if (record->Time < START_GOOD_DATE)
+ return;
+ memcpy( (void*) &RTC_RECORDS[statRecord_cnt], (void*) record, sizeof(statRecord_t) );
+
+ if (CACHE_RECORD_CNT == (statRecord_cnt + 1)){
+ saveRecordsToFile();
+ statRecord_cnt = 0;
+ } else
+ statRecord_cnt++;
+
+}
+
+void FileSystem::close()
+{
+ if ( !_canWrite )
+ return;
+ _file.close();
+}
diff --git a/stat_records.hpp b/stat_records.hpp
new file mode 100644
index 0000000..6ef6e4a
--- /dev/null
+++ b/stat_records.hpp
@@ -0,0 +1,232 @@
+#ifndef _STAT_RECORDS_
+#define _STAT_RECORDS_
+
+#include
+//#include "FS.h"
+//#include "SPIFFS.h"
+#include "LITTLEFS.h"
+//#include "display.hpp"
+#include
+
+struct statRecord_t{
+ time_t Time;
+ uint32_t Steps;
+ float HeartRate;
+ float SpO2;
+ float AmbientTempC;
+ float ObjectTempC;
+ float Vcc;
+};
+
+#define CACHE_RECORD_CNT 10
+
+extern statRecord_t RTC_RECORDS[CACHE_RECORD_CNT];
+extern uint8_t statRecord_cnt;
+
+
+#define SPIFFS LITTLEFS
+
+#define HOURS_IN_DAY 24
+
+typedef enum record_idx_s{
+ REC_TIME_IDX = 0,
+ REC_VCC_IDX,
+ REC_STEPS_IDX,
+ REC_HR_IDX,
+ REC_AMBT_IDX,
+ REC_OBJT_IDX,
+ REC_MAX_IDX
+} record_idx_t;
+
+enum skimrecord_idx_t{
+ // SKIMREC_TIME_IDX = TIME_IDX,
+ SKIMREC_VCC_IDX,
+ SKIMREC_STEPS_IDX,
+ SKIMREC_OBJT_IDX,
+ SKIMREC_MAX_IDX
+};
+
+enum result_aggregate_t {
+ RESULT_AGGREGATE_AVG = 0,
+ RESULT_AGGREGATE_SUM,
+ RESULT_AGGREGATE_DIFF,
+};
+
+struct HourRecordAvg {
+ private:
+ float startValue;
+ float endValue;
+ float summ;
+ uint8_t cnt; // we have only 60 recors in total;
+
+ public:
+ HourRecordAvg();
+ void add(float val);
+ void addBack(float val);
+
+ float avg();
+ float diff();
+ float diffAsc();
+ float sum();
+
+ void print();
+};
+
+int buildDayFName(char *inputBuffer, int inputBuffer_size, char *dir, int16_t year, int8_t month, int8_t day);
+uint32_t getArrayLogfiles(time_t lastKnownData, int32_t *resData, uint32_t resDataSize);
+tmElements_t toTmElement(struct tm *tm);
+
+struct skimData_t {
+ float Vcc;
+ uint32_t Steps;
+ float ObjectTempC;
+
+ void fill_data(float vcc , uint32_t steps , float objC );
+};
+
+class fileRecord {
+ protected:
+ struct tm _tm;
+ void *_data ;
+ uint8_t max_cnt;
+ uint8_t field_cnt;
+ virtual void castData(char **dataPointers) ;
+
+ public:
+// fileRecord();
+ // ~fileRecord();
+ bool parseLine(char *_buffer, char delimiter);
+ int hour() { return _tm.tm_hour; };
+ struct tm *tm() { return &_tm;};
+
+};
+
+class FileRecord: public fileRecord { // i don't want to parse and cast all data,
+ private:
+ void castData(char **dataPointers) ;
+
+ public:
+ FileRecord();
+ ~FileRecord();
+ statRecord_t *data() {return (statRecord_t *) _data;};
+
+
+};
+
+class DailyRecordsAvg {
+ private:
+ HourRecordAvg val[HOURS_IN_DAY];
+ uint8_t cnt; // records since 0th hour
+ public:
+ DailyRecordsAvg();
+ int aggregateString(uint8_t _hour, float value);
+ int aggregateStringBack(uint8_t _hour, float value);
+ HourRecordAvg * hourRecord(uint8_t hourIdx);
+};
+
+class SkimFileRecord: public fileRecord { /// TODO: make parent common with FileRecord
+ private:
+ // uint8_t max_cnt = SKIMREC_MAX_IDX;
+ // skimData_t *_data = NULL;
+ void castData(char **dataPointers) ;
+ bool needWrite;
+ public:
+ SkimFileRecord();
+ ~SkimFileRecord();
+
+ skimData_t *get();
+ void set( skimData_t *s );
+ float getField( skimrecord_idx_t idx );
+
+ void setWritable();
+ bool isWritable();
+ bool serialize(char *str, size_t str_size);
+ void print();
+};
+
+struct rawForSkim_t {
+ DailyRecordsAvg Steps;
+ DailyRecordsAvg Temp;
+ DailyRecordsAvg Vcc;
+ int8_t startHourInLog;
+ int8_t endHourInLog;
+};
+
+struct hourStat_t {
+ SkimFileRecord byHours[HOURS_IN_DAY];
+ int8_t hoursInFile; // -1 in case not inited
+ int8_t hoursInTotal;
+};
+
+class SkimData {
+ private:
+ rawForSkim_t *r4s;
+ // char *skimDirName = "/skim";
+
+ uint8_t maxFilesCnt = 30;
+ uint8_t _day;
+ uint8_t _month;
+ uint16_t _year; // 64k
+
+ hourStat_t stat;
+
+ bool isCurrentHour(uint8_t _hour);
+ bool isToday();
+
+ bool processRaw();
+ void prepareFromLog(); // fill r4s // in case it happened today
+ bool prepareFromMemory();
+
+ bool processLine(char *lineString);
+
+ bool aggreggateRecord(int _hour, statRecord_t *stat_rec);
+ uint8_t read();
+ void write();
+
+ public:
+ SkimData(uint8_t d, uint8_t m , uint16_t y);
+ ~SkimData();
+
+ void process();
+ hourStat_t *getStat();
+};
+
+
+typedef void (*readFileHandler)(char* fname);
+
+#define MAX_FILES_CNT 10
+#define MAX_LINE_SIZE 128
+#define START_GOOD_DATE 1600000000 // some test for data
+
+class FileSystem {
+ private:
+ bool _canWrite = true; //false;
+/*
+ FileRecord skim_frec[HOURS_IN_DAY];
+ uint8_t skim_frec_cnt;
+ */
+ File _file;
+ char *logDirName = "/log";
+
+ char *filenamePattern = "%04d-%02d-%02d.txt";
+
+ void saveRecordsToFile();
+ void listDir(fs::FS &fs, const char * dirname, bool need_cat, readFileHandler r_handler );
+ public:
+ void init();
+ void catFile(File f);
+ void writeLog(statRecord_t *record);
+ void scanLogDir();
+ void rotateLogs();
+ void close();
+
+ int filenameDate(char *inputBuffer, int inputBuffer_size, int16_t year, int8_t month, int8_t day);
+
+
+ ///int8_t get_values_idx(time_t _date, record_idx_t field_idx, float result_data[24] );
+ // int8_t get_values_idx(int32_t tm_mday, int32_t tm_month, int32_t tm_year, record_idx_t field_idx,
+ // result_aggregate_t result_type, Graph *graph ) ;
+
+};
+
+#endif
diff --git a/stat_server.cpp b/stat_server.cpp
new file mode 100644
index 0000000..09dcdb0
--- /dev/null
+++ b/stat_server.cpp
@@ -0,0 +1,180 @@
+#include "stat_server.hpp"
+#include "LITTLEFS.h"
+#include "utils.h"
+
+#ifdef NEED_SERVER_STAT
+
+// ugly but wtf, i don't want to parse all
+int parseLastDateResponse(WiFiClientSecure *client, char *lastDate, int lastDateSize)
+{
+ int l = -1;
+
+ if (!client->find("HTTP/1.1")) // skip HTTP/1.1
+ return l;
+ int st = client->parseInt(); // parse status code
+ if (st == 200 && client->find("\"ldate\" : \"")) {
+ l = client->readBytesUntil('"', lastDate, lastDateSize);
+ lastDate[l] = 0; // terminate the C string
+ }
+ printf_w("parseLastDateResponse: l = %d, date = %s, stop\n", l, lastDate);
+
+ return l;
+}
+
+
+int32_t getLastDate(String server, String uid, String token)
+{
+ int32_t l_time = -1;//time_t
+
+ size_t allLen = 0;
+ String headerTxt = makeHeader(server, uid, token, "/action/last_tshdata.php", allLen);
+ WiFiClientSecure client;
+ if (!client.connect(server.c_str(), PORT))
+ return l_time;
+
+ printf_w("getLastDate: header -- %s\n", headerTxt.c_str() );
+
+ client.print(headerTxt);
+ client.print("\r\n");
+
+ delay(20);
+ long tOut = millis() + TIMEOUT;
+
+ while (client.connected() && tOut > millis()) {
+ if (client.available()) {
+ char lastDate[32];
+ if (-1 == parseLastDateResponse(&client, lastDate, sizeof(lastDate)))
+ break;
+
+ printf_w("getLastDate: %s\n", lastDate);
+ l_time = atoi(lastDate);
+
+ break;
+ }
+ }
+ client.stop();
+
+ printf_w("getLastDate: l_time %d\n", l_time);
+ return l_time;
+}
+
+void sendStatFile(String server, String uid, String token, String date, String filename)
+{
+ printf_w("sendStatFile: start send -- %s\n", filename.c_str());
+
+ File _file = LITTLEFS.open(filename);
+ if (!_file) {
+ printf_w("can't open dir '%s'\n", filename);
+ return;
+ }
+ String bodyPic = makeBody("dateFile", date);
+ String bodyEnd = String("--") + BOUNDARY + String("--\r\n");
+
+ size_t allLen = bodyPic.length() + _file.size() + bodyEnd.length();
+
+ String headerTxt = makeHeader(server, uid, token, "/action/send_thsdata.php", allLen);
+ printf_w("sendStatFile: header '%s', len %d\n", headerTxt.c_str(), headerTxt.length());
+ printf_w("sendStatFile: header '%s', len %d\n", bodyPic.c_str(), bodyPic.length());
+
+ WiFiClientSecure client;
+ if (!client.connect(server.c_str(), PORT)) {
+ printf_w("connection failed");
+ return ;
+ }
+ printf_w("sendStatFile: start send header\n");
+ client.print(headerTxt + bodyPic);
+
+ printf_w("sendStatFile: stop send header\n");
+
+ int ii = 0;
+ int nextPacketSize = 0;
+ int maxPacketSize = 2048;
+ while ((nextPacketSize = _file.available())) {
+ //char _data = _file.read();
+ //if (-1 == _data)
+ // break;
+ if (nextPacketSize > maxPacketSize)
+ nextPacketSize = maxPacketSize;
+
+ String buffer = "";
+ for (int i=0; i < nextPacketSize; i++)
+ buffer += (char)_file.read();
+
+ client.print( buffer );
+ printf_w("sendStatFile: byte %d, iter %d\n", nextPacketSize, ii);
+ ii++;
+ }
+ _file.close();
+ printf_w("sendStatFile: stop send data, %d \n", ii);
+ client.print("\r\n" + bodyEnd);
+ delay(20);
+ long tOut = millis() + TIMEOUT;
+ while (client.connected() && tOut > millis()) {
+ if (client.available()) {
+ // String serverRes = client.readStringUntil('\r');
+ String serverRes = client.readString();
+ printf_w( "RETURN GET:" );
+ printf_w( serverRes.c_str() );
+ printf_w( "\n" );
+ return;//(serverRes);
+ }
+ }
+ client.stop();
+}
+
+String makeHeader(String server, String uid, String token, String url, size_t length)
+{
+ String data;
+ data = F("POST ");
+ data += url;
+ data += F(" HTTP/1.1\r\n");
+ data += F("Cache-control: no-cache\r\n");
+ data += F("Content-Type: multipart/form-data; boundary=");
+ data += BOUNDARY;
+ data += "\r\n";
+ data += F("User-Agent: tsh-watch/0.0.1\r\n");
+ data += F("Accept: */*\r\n");
+ data += F("Host: ");
+ data += server;
+ data += F("\r\n");
+
+ data += F("X-uID: ");
+ data += uid;
+ data += F("\r\n");
+ data += F("X-Token: ");
+ data += token;
+ data += F("\r\n");
+
+// data += F("Accept-encoding: gzip, deflate\r\n");
+ data += F("Connection: keep-alive\r\n");
+ if (length > 0) {
+ data += F("Content-Length: ");
+ data += String(length);
+ data += "\r\n";
+ }
+ data += "\r\n";
+ return(data);
+}
+
+String makeBody(String content , String message)
+{
+ String data;
+ data = "--";
+ data += BOUNDARY;
+ data += F("\r\n");
+ if (content == "dateFile") {
+ data += F("Content-Disposition: form-data; name=\"dataFile\"; filename=\"");
+ data += message;
+ data += F(".csv\"\r\n");
+ data += F("Content-Type: text/plain\r\n");
+ data += F("\r\n");
+ }
+ else {
+ data += "Content-Disposition: form-data; name=\"" + content +"\"\r\n";
+ data += "\r\n";
+ data += message;
+ data += "\r\n";
+ }
+ return(data);
+}
+#endif
diff --git a/stat_server.hpp b/stat_server.hpp
new file mode 100644
index 0000000..83a0ce6
--- /dev/null
+++ b/stat_server.hpp
@@ -0,0 +1,24 @@
+#define NEED_SERVER_STAT
+
+#ifdef NEED_SERVER_STAT
+
+#ifndef _STAT_SERVER_
+#define _STAT_SERVER_
+
+#include "WiFiClientSecure.h"
+
+//#define SERVER "vesovoy-control.ru"
+#define PORT 443
+
+#define BOUNDARY "--------------------------133747188241686651551404"
+#define TIMEOUT 20000
+
+String makeHeader(String header, String uid, String token, String url, size_t length);
+String makeBody(String content , String message);
+
+void sendStatFile(String header, String uid, String token, String date, String filename);
+int32_t getLastDate(String header, String uid, String token);
+
+#endif // _STAT_SERVER_
+
+#endif // NEED_SERVER_STAT
diff --git a/tcMenuU8g2.cpp b/tcMenuU8g2.cpp
new file mode 100644
index 0000000..6789ae3
--- /dev/null
+++ b/tcMenuU8g2.cpp
@@ -0,0 +1,432 @@
+/*
+ * Copyright (c) 2018 https://www.thecoderscorner.com (Nutricherry LTD).
+ * This product is licensed under an Apache license, see the LICENSE file in the top-level directory.
+ */
+
+/**
+ * @file tcMenuU8g2.h
+ *
+ * U8g2 renderer that renders menus onto this type of display. This file is a plugin file and should not
+ * be directly edited, it will be replaced each time the project is built. If you want to edit this file in place,
+ * make sure to rename it first.
+ *
+ * LIBRARY REQUIREMENT
+ * This library requires the u8g2 library available for download from your IDE library manager.
+ */
+
+#include
+#include "tcMenuU8g2.h"
+#include "utils.h"
+
+const char MENU_BACK_TEXT[] PROGMEM = "[..]";
+
+void U8g2MenuRenderer::setGraphicsDevice(U8G2* u8g2, U8g2GfxMenuConfig *gfxConfig) {
+
+ this->u8g2 = u8g2;
+ this->gfxConfig = gfxConfig;
+
+ if (gfxConfig->editIcon == NULL || gfxConfig->activeIcon == NULL) {
+ gfxConfig->editIcon = defEditingIcon;
+ gfxConfig->activeIcon = defActiveIcon;
+ gfxConfig->editIconWidth = 16;
+ gfxConfig->editIconHeight = 12;
+ }
+
+ // font cannot be NULL on this board, we default if it is.
+ if (gfxConfig->itemFont == NULL) gfxConfig->itemFont = u8g2_font_6x10_tf;
+ if (gfxConfig->titleFont == NULL) gfxConfig->titleFont = u8g2_font_6x10_tf;
+}
+
+U8g2MenuRenderer::~U8g2MenuRenderer() {
+}
+
+void U8g2MenuRenderer::renderTitleArea() {
+ if (menuMgr.getCurrentMenu() == menuMgr.getRoot()) {
+ safeProgCpy(buffer, applicationInfo.name, bufferSize);
+ }
+ else {
+ menuMgr.getCurrentMenu()->copyNameToBuffer(buffer, bufferSize);
+ }
+
+ serdebugF3("Render title, fontMag: ", buffer, gfxConfig->titleFontMagnification);
+
+ u8g2->setFont(gfxConfig->titleFont);
+ u8g2->setFontRefHeightExtendedText();
+ u8g2->setFontDirection(0);
+
+ int extentY = u8g2->getMaxCharHeight();
+ titleHeight = extentY + gfxConfig->titlePadding.top + gfxConfig->titlePadding.bottom;
+ int fontYStart = titleHeight - (gfxConfig->titlePadding.bottom);
+
+ serdebugF3("titleHeight, fontYStart: ", titleHeight, fontYStart);
+
+ u8g2->setColorIndex(gfxConfig->bgTitleColor);
+ u8g2->drawBox(0, 0, u8g2->getDisplayWidth(), titleHeight);
+ u8g2->setColorIndex(gfxConfig->fgTitleColor);
+ u8g2->setCursor(gfxConfig->titlePadding.left, fontYStart);
+ u8g2->print(buffer);
+ titleHeight += gfxConfig->titleBottomMargin;
+}
+
+void U8g2MenuRenderer::drawBitmap(int x, int y, int w, int h, const unsigned char *bmp) {
+#if defined(__AVR__) || defined(ESP8266)
+ u8g2->drawXBMP(x, y, w, h, bmp);
+#else
+ u8g2->drawXBM(x, y, w, h, bmp);
+#endif
+}
+
+bool U8g2MenuRenderer::renderWidgets(bool forceDraw) {
+ TitleWidget* widget = firstWidget;
+ int xPos = u8g2->getDisplayWidth() - gfxConfig->widgetPadding.right;
+ bool redrawNeeded = forceDraw;
+ while(widget) {
+ xPos -= widget->getWidth();
+
+ if(widget->isChanged() || forceDraw) {
+ redrawNeeded = true;
+
+ serdebugF3("Drawing widget pos,icon: ", xPos, widget->getCurrentState());
+ u8g2->setColorIndex(gfxConfig->widgetColor);
+ drawBitmap(xPos, gfxConfig->widgetPadding.top, widget->getWidth(), widget->getHeight(), widget->getCurrentIcon());
+ }
+
+ widget = widget->getNext();
+ xPos -= gfxConfig->widgetPadding.left;
+ }
+ return redrawNeeded;
+}
+
+void U8g2MenuRenderer::renderListMenu(int titleHeight) {
+ ListRuntimeMenuItem* runList = reinterpret_cast(menuMgr.getCurrentMenu());
+
+ uint8_t maxY = uint8_t((u8g2->getDisplayHeight() - titleHeight) / itemHeight);
+ maxY = min(maxY, runList->getNumberOfParts());
+ uint8_t currentActive = runList->getActiveIndex();
+
+ uint8_t offset = 0;
+ if (currentActive >= maxY) {
+ offset = (currentActive+1) - maxY;
+ }
+
+ int yPos = titleHeight;
+ for (int i = 0; i < maxY; i++) {
+ uint8_t current = offset + i;
+ RuntimeMenuItem* toDraw = (current==0) ? runList->asBackMenu() : runList->getChildItem(current - 1);
+ renderMenuItem(yPos, itemHeight, toDraw);
+ yPos += itemHeight;
+ }
+
+ // reset the list item to a normal list again.
+ runList->asParent();
+}
+
+void U8g2MenuRenderer::render() {
+ if (u8g2 == NULL) return;
+
+ uint8_t locRedrawMode = redrawMode;
+ redrawMode = MENUDRAW_NO_CHANGE;
+ bool requiresUpdate = false;
+
+ countdownToDefaulting();
+
+ if (locRedrawMode == MENUDRAW_COMPLETE_REDRAW) {
+ // pre-populate the font.
+ u8g2->setFont(gfxConfig->itemFont);
+ u8g2->setFontPosBottom();
+ itemHeight = u8g2->getMaxCharHeight();
+
+ // clear screen first in complete draw mode
+ u8g2->setColorIndex(gfxConfig->bgItemColor);
+ u8g2->drawBox(0,0,u8g2->getDisplayWidth(), u8g2->getDisplayHeight());
+
+ itemHeight = itemHeight + gfxConfig->itemPadding.top + gfxConfig->itemPadding.bottom;
+ serdebugF2("Redraw all, new item height ", itemHeight);
+
+ renderTitleArea();
+ taskManager.yieldForMicros(0);
+
+ renderWidgets(true);
+ taskManager.yieldForMicros(0);
+ requiresUpdate = true;
+ }
+ else {
+ requiresUpdate = renderWidgets(false);
+ }
+
+ u8g2->setFont(gfxConfig->itemFont);
+ int maxItemsY = ((u8g2->getDisplayHeight()-titleHeight) / itemHeight);
+
+ if(menuMgr.getCurrentMenu()->getMenuType() == MENUTYPE_RUNTIME_LIST) {
+ if(menuMgr.getCurrentMenu()->isChanged() || locRedrawMode != MENUDRAW_NO_CHANGE) {
+ requiresUpdate = true;
+ renderListMenu(titleHeight);
+ }
+ }
+ else {
+ MenuItem* item = menuMgr.getCurrentMenu();
+ // first we find the first currently active item in our single linked list
+ uint8_t currActiveOffs = offsetOfCurrentActive(item);
+ if (currActiveOffs >= maxItemsY) {
+ uint8_t toOffsetBy = (currActiveOffs - maxItemsY) + 1;
+
+ if(lastOffset != toOffsetBy) locRedrawMode = MENUDRAW_COMPLETE_REDRAW;
+ lastOffset = toOffsetBy;
+
+ while (item != NULL && toOffsetBy) {
+ if (item->isVisible()) toOffsetBy = toOffsetBy - 1;
+ item = item->getNext();
+ }
+ }
+ else {
+ if (lastOffset != 0xff) locRedrawMode = MENUDRAW_COMPLETE_REDRAW;
+ lastOffset = 0xff;
+ }
+
+ //serdebugF3("Offset chosen, on display", lastOffset, maxItemsY);
+
+ // and then we start drawing items until we run out of screen or items
+ int ypos = titleHeight;
+ while (item && (ypos + itemHeight) <= u8g2->getDisplayHeight() ) {
+ if(item->isVisible())
+ {
+ if (locRedrawMode != MENUDRAW_NO_CHANGE || item->isChanged()) {
+ requiresUpdate = true;
+
+ taskManager.yieldForMicros(0);
+
+ renderMenuItem(ypos, itemHeight, item);
+ }
+ ypos += itemHeight;
+ }
+ item = item->getNext();
+ }
+ }
+
+ if (requiresUpdate) u8g2->sendBuffer(); // !!!!!!!!!!!!!!!!!!!!!
+}
+
+void U8g2MenuRenderer::renderMenuItem(int yPos, int menuHeight, MenuItem* item) {
+ int icoWid = gfxConfig->editIconWidth;
+ int icoHei = gfxConfig->editIconHeight;
+
+ item->setChanged(false); // we are drawing the item so it's no longer changed.
+
+ int imgMiddleY = yPos + ((menuHeight - icoHei) / 2);
+
+ if (item->isEditing()) {
+ u8g2->setColorIndex(gfxConfig->bgSelectColor);
+ u8g2->drawBox(0, yPos, u8g2->getDisplayWidth(), menuHeight);
+ u8g2->setColorIndex(gfxConfig->fgSelectColor);
+ drawBitmap(gfxConfig->itemPadding.left, imgMiddleY, icoWid, icoHei, gfxConfig->editIcon);
+ serdebugF("Item Editing");
+ }
+ else if (item->isActive()) {
+ u8g2->setColorIndex(gfxConfig->bgSelectColor);
+ u8g2->drawBox(0, yPos, u8g2->getDisplayWidth(), menuHeight);
+ u8g2->setColorIndex(gfxConfig->fgSelectColor);
+ drawBitmap(gfxConfig->itemPadding.left, imgMiddleY, icoWid, icoHei, gfxConfig->activeIcon);
+ serdebugF("Item Active");
+ }
+ else {
+ u8g2->setColorIndex(gfxConfig->bgItemColor);
+ u8g2->drawBox(0, yPos, u8g2->getDisplayWidth(), menuHeight);
+ u8g2->setColorIndex(gfxConfig->fgItemColor);
+ serdebugF("Item Normal");
+ }
+
+ taskManager.yieldForMicros(0);
+
+ int textPos = gfxConfig->itemPadding.left + icoWid + gfxConfig->itemPadding.left;
+
+ int drawingPositionY = yPos + gfxConfig->itemPadding.top;
+ if (gfxConfig->itemFont) {
+ drawingPositionY = yPos + (menuHeight - (gfxConfig->itemPadding.bottom));
+ }
+ u8g2->setCursor(textPos, drawingPositionY);
+ item->copyNameToBuffer(buffer, bufferSize);
+
+ serdebugF4("Printing menu item (name, ypos, drawingPositionY)", buffer, yPos, drawingPositionY);
+
+ u8g2->print(buffer);
+
+ if (isItemActionable(item)) {
+ int rightOffset = u8g2->getDisplayWidth() - (gfxConfig->itemPadding.right + icoWid);
+ u8g2->setColorIndex(gfxConfig->fgSelectColor);
+ drawBitmap(rightOffset, imgMiddleY, icoWid, icoHei, gfxConfig->activeIcon);
+ buffer[0] = 0;
+ }
+ else if (item->getMenuType() == MENUTYPE_BACK_VALUE) {
+ safeProgCpy(buffer, MENU_BACK_TEXT, bufferSize);
+ }
+ else {
+ menuValueToText(item, JUSTIFY_TEXT_LEFT);
+ }
+
+ int16_t right = u8g2->getDisplayWidth() - (u8g2->getStrWidth(buffer) + gfxConfig->itemPadding.right);
+ u8g2->setCursor(right, drawingPositionY);
+ u8g2->print(buffer);
+ serdebugF2("Value ", buffer);
+
+ taskManager.yieldForMicros(0);
+}
+
+/**
+ * Provides a basic graphics configuration suitable for low / medium resolution displays
+ * @param config usually a global variable holding the graphics configuration.
+ */
+void prepareBasicU8x8Config(U8g2GfxMenuConfig& config) {
+ makePadding(config.titlePadding, 1, 1, 1, 1);
+ makePadding(config.itemPadding, 1, 1, 1, 1);
+ makePadding(config.widgetPadding, 2, 2, 0, 2);
+
+ config.bgTitleColor = WHITE;
+ config.fgTitleColor = BLACK;
+ config.titleFont = u8g2_font_6x12_tf;
+ config.titleBottomMargin = 1;
+ config.widgetColor = BLACK;
+ config.titleFontMagnification = 1;
+
+ config.bgItemColor = BLACK;
+ config.fgItemColor = WHITE;
+ config.bgSelectColor = BLACK;
+ config.fgSelectColor = WHITE;
+ config.itemFont = u8g2_font_6x10_tf;
+ config.itemFontMagnification = 1;
+
+ config.editIcon = loResEditingIcon;
+ config.activeIcon = loResActiveIcon;
+ config.editIconHeight = 6;
+ config.editIconWidth = 8;
+}
+
+BaseDialog* U8g2MenuRenderer::getDialog() {
+ if(dialog == NULL) {
+ dialog = new U8g2Dialog();
+ }
+ return dialog;
+}
+
+
+#define ROW_MAX 4
+
+void U8g2Dialog::drawText(int _x, int _y, char *line_buf) {
+ U8g2MenuRenderer* adaRenderer = reinterpret_cast(MenuRenderer::getInstance());
+ U8G2* graphics = adaRenderer->getGraphics();
+ graphics->setFont(u8g_font_5x7);
+
+ uint8_t rows; // calc on each loop bad idea
+ uint8_t fontLineSpacing = 9;
+ rows = graphics->getHeight() / fontLineSpacing;
+
+ if ( rows > ROW_MAX )
+ rows = ROW_MAX;
+ printf_w("drawText: x %d, y %d, rows %d, line_buf %s\n", _x, _y, rows, line_buf);
+
+ uint8_t i, y;
+ // graphic commands to redraw the complete screen are placed here
+ y = _y; // reference is the top left -1 position of the string
+ y--; // correct the -1 position of the drawStr
+
+ int line_start = 0, line_len = 0, lines_cnt = 0;
+ bool new_line = true;
+ for ( i = 0; (0 != line_buf[i]) && ( lines_cnt < rows) ; i++ )
+ {
+ printf_w("drawText: i %d, lines_cnt %d, %c\n", i, lines_cnt, line_buf[i]);
+ if ( true == new_line) {
+ line_start = i;
+ new_line = false;
+ }
+ if ( '\n' == line_buf[i] || '\0' == line_buf[i] ) {
+ line_buf[i] = '\0';
+ graphics->drawStr( _x, y, (char *) ( &line_buf[line_start] ) );
+ printf_w("drawText: x %d, y %d, line_start %d, i %d, %s\n", _x, y, line_start, i, (char *) ( &line_buf[line_start] ));
+ y += fontLineSpacing;
+ new_line = true;
+ line_len = 0;
+ lines_cnt +=1;
+ }
+ else {
+ line_len +=1;
+ }
+ }
+ if ( line_len > 0 ) {
+ graphics->drawStr( _x, y, (char *) ( &line_buf[line_start] ) );
+ printf_w("drawText: x %d, y %d, line_start %d, i %d, %s\n", _x, y, line_start, i, (char *) ( &line_buf[line_start] ));
+ }
+
+
+}
+
+void U8g2Dialog::internalRender(int currentValue) {
+ U8g2MenuRenderer* adaRenderer = reinterpret_cast(MenuRenderer::getInstance());
+ U8g2GfxMenuConfig* gfxConfig = adaRenderer->getGfxConfig();
+ U8G2* graphics = adaRenderer->getGraphics();
+
+ if(needsDrawing == MENUDRAW_COMPLETE_REDRAW) {
+ // clear screen first in complete draw mode
+ graphics->setColorIndex(gfxConfig->bgItemColor);
+ graphics->drawBox(0,0,graphics->getDisplayWidth(), graphics->getDisplayHeight());
+ }
+
+ graphics->setFont(gfxConfig->itemFont);
+
+ char data[20];
+ safeProgCpy(data, headerPgm, sizeof(data));
+
+ int fontYStart = gfxConfig->itemPadding.top;
+ int extentY = graphics->getMaxCharHeight();
+ int dlgNextDraw = extentY + gfxConfig->titlePadding.top + gfxConfig->titlePadding.bottom;
+ if (gfxConfig->itemFont) {
+ fontYStart = dlgNextDraw - (gfxConfig->titlePadding.bottom);
+ }
+
+ graphics->setColorIndex(gfxConfig->bgTitleColor);
+ graphics->drawBox(0, 0, graphics->getDisplayWidth(), dlgNextDraw);
+ graphics->setColorIndex(gfxConfig->fgTitleColor);
+ graphics->setCursor(gfxConfig->titlePadding.left, fontYStart);
+ graphics->print(data);
+
+ dlgNextDraw += gfxConfig->titleBottomMargin;
+
+ int startingPosition = dlgNextDraw;
+ fontYStart = dlgNextDraw + gfxConfig->itemPadding.top;
+ dlgNextDraw = dlgNextDraw + extentY + gfxConfig->titlePadding.top + gfxConfig->titlePadding.bottom;
+ if (gfxConfig->itemFont) {
+ fontYStart = dlgNextDraw - (gfxConfig->titlePadding.bottom);
+ }
+ graphics->setColorIndex(gfxConfig->bgItemColor);
+ graphics->drawBox(0, startingPosition, graphics->getDisplayWidth(), dlgNextDraw);
+ graphics->setColorIndex(gfxConfig->fgItemColor);
+// graphics->setCursor(gfxConfig->titlePadding.left, fontYStart);
+ // graphics->print(MenuRenderer::getInstance()->getBuffer());
+ drawText(gfxConfig->titlePadding.left, fontYStart, MenuRenderer::getInstance()->getBuffer());
+
+ bool active;
+ if(button1 != BTNTYPE_NONE) {
+ active = copyButtonText(data, 0, currentValue);
+ drawButton(graphics, gfxConfig, data, 0, active);
+ }
+ if(button2 != BTNTYPE_NONE) {
+ active = copyButtonText(data, 1, currentValue);
+ drawButton(graphics, gfxConfig, data, 1, active);
+ }
+ graphics->sendBuffer();
+}
+
+void U8g2Dialog::drawButton(U8G2* gfx, U8g2GfxMenuConfig* config, const char* title, uint8_t num, bool active) {
+ int extentY = gfx->getMaxCharHeight();
+ int itemHeight = ( extentY + config->itemPadding.top + config->itemPadding.bottom);
+ int start = gfx->getDisplayHeight() - itemHeight;
+ int fontYStart = start + config->itemPadding.top;
+ if (config->itemFont) {
+ fontYStart += extentY;
+ }
+ int buttonWidth = gfx->getDisplayWidth() / 2;
+ int xOffset = (num == 0) ? 0 : buttonWidth;
+ gfx->setColorIndex(active ? config->bgSelectColor : config->bgItemColor);
+ gfx->drawBox(xOffset, start, buttonWidth, itemHeight);
+ gfx->setColorIndex(active ? config->fgSelectColor : config->fgItemColor);
+ gfx->setCursor(xOffset + ((buttonWidth - gfx->getStrWidth(title)) / 2), fontYStart);
+ gfx->print(title);
+}
diff --git a/tcMenuU8g2.h b/tcMenuU8g2.h
new file mode 100644
index 0000000..b5dff8d
--- /dev/null
+++ b/tcMenuU8g2.h
@@ -0,0 +1,103 @@
+/*
+ * Copyright (c) 2018 https://www.thecoderscorner.com (Nutricherry LTD).
+ * This product is licensed under an Apache license, see the LICENSE file in the top-level directory.
+ */
+
+/**
+ * @file tcMenuU8g2.h
+ *
+ * U8g2 renderer that renders menus onto this type of display. This file is a plugin file and should not
+ * be directly edited, it will be replaced each time the project is built. If you want to edit this file in place,
+ * make sure to rename it first.
+ *
+ * LIBRARY REQUIREMENT
+ * This library requires the u8g2 library available for download from your IDE library manager.
+ */
+
+#ifndef _TCMENU_U8G2_H_
+#define _TCMENU_U8G2_H_
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+extern const ConnectorLocalInfo applicationInfo;
+
+/**
+ * A standard menu render configuration that describes how to renderer each item and the title.
+ * Specialised for u8g2 fonts.
+ */
+typedef struct ColorGfxMenuConfig U8g2GfxMenuConfig;
+
+// some colour displays don't create this value
+#ifndef BLACK
+#define BLACK 0
+#endif
+
+// some colour displays don't create this value
+#ifndef WHITE
+#define WHITE 1
+#endif
+
+/**
+ * A basic renderer that can use the AdaFruit_GFX library to render information onto a suitable
+ * display. It is your responsibility to fully initialise and prepare the display before passing
+ * it to this renderer. The usual procedure is to create a display variable globally in your
+ * sketch and then provide that as the parameter to setGraphicsDevice. If you are using the
+ * designer you provide the display variable name in the code generation parameters.
+ *
+ * You can also override many elements of the display using AdaColorGfxMenuConfig, to use the defaults
+ * just call prepareAdaColorDefaultGfxConfig(..) passing it a pointer to your config object. Again the
+ * designer UI takes care of this.
+ */
+class U8g2MenuRenderer : public BaseMenuRenderer {
+private:
+ U8G2* u8g2;
+ U8g2GfxMenuConfig *gfxConfig;
+ int16_t titleHeight;
+ int16_t itemHeight;
+public:
+ U8g2MenuRenderer(uint8_t bufferSize = 64) : BaseMenuRenderer(bufferSize) {
+ this->u8g2 = NULL;
+ this->gfxConfig = NULL;
+ }
+
+ void setGraphicsDevice(U8G2* u8g2, U8g2GfxMenuConfig *gfxConfig);
+
+ virtual ~U8g2MenuRenderer();
+ virtual void render();
+
+ U8G2* getGraphics() { return u8g2; }
+ U8g2GfxMenuConfig* getGfxConfig() { return gfxConfig; }
+ BaseDialog* getDialog() override;
+private:
+ void drawBitmap(int x, int y, int w, int h, const unsigned char *bmp);
+ void renderMenuItem(int yPos, int menuHeight, MenuItem* item);
+ void renderTitleArea();
+ bool renderWidgets(bool forceDraw);
+ void renderListMenu(int titleHeight);
+};
+
+class U8g2Dialog : public BaseDialog {
+public:
+ U8g2Dialog() {
+ U8g2MenuRenderer* r = reinterpret_cast(MenuRenderer::getInstance());
+ bitWrite(flags, DLG_FLAG_SMALLDISPLAY, (r->getGraphics()->getDisplayWidth() < 100));
+ }
+protected:
+ void internalRender(int currentValue) override;
+ void drawButton(U8G2* gfx, U8g2GfxMenuConfig* config, const char* title, uint8_t num, bool active);
+ void drawText(int _x, int _y, char *line_buf) ;
+};
+
+/**
+ * Provides a basic graphics configuration suitable for low / medium resolution displays
+ * @param config usually a global variable holding the graphics configuration.
+ */
+void prepareBasicU8x8Config(U8g2GfxMenuConfig& config);
+
+#endif // _TCMENU_U8G2_H_
diff --git a/tsh-watch.ino b/tsh-watch.ino
index 845a973..0f6e9d7 100644
--- a/tsh-watch.ino
+++ b/tsh-watch.ino
@@ -1,22 +1,115 @@
-#include "hardware.hpp"
+ #include "hardware.hpp"
Hardware watch;
-void setup() {
- /*
- for (int i = 0; i < 0; i++) {
- int _State = digitalRead(i);
-
- print_w("status: pin ");
- print_w(i); print_w(" - "); print_w(_State); println_w("");
- }
- */
- // put your setup code here, to run once:
+void setup()
+{
watch.init();
+ printf_w("CONFIG_ULP_COPROC_RESERVE_MEM = %d\n" , CONFIG_ULP_COPROC_RESERVE_MEM);
}
-void loop() {
- // put your main code here, to run repeatedly:
+void loop()
+{
watch.update();
- watch.power_safe_logic();
+ watch.runPowerSafe();
+}
+
+// --------------------------------------------------
+// callback from menu and external actions
+
+void CALLBACK_FUNCTION onBack(int id)
+{
+ watch.showActivity( WATCH_ACTICITY );
+ returnToMenu();
+}
+
+void CALLBACK_FUNCTION onAutoUpdateCh(int id)
+{
+ watch.syncTimeViaWifi();
+}
+
+void CALLBACK_FUNCTION onDateCh(int id)
+{
+ DateStorage dt = menuDate.getDate();
+ watch.setDate(dt);
+}
+
+void CALLBACK_FUNCTION onTimeCh(int id)
+{
+ TimeStorage tm = menuTime.getTime();
+ watch.setTime(tm);
+}
+
+void CALLBACK_FUNCTION onTimezoneCh(int id)
+{
+ int tz = menuTimeZone.getCurrentValue();
+ watch.setTimezone(tz, true);
+}
+
+void CALLBACK_FUNCTION onTemperatureCh(int id)
+{
+ bool val = menuTemperature.getBoolean();
+ watch.setTempSwitch(val, true);
+}
+
+void CALLBACK_FUNCTION onPedoMeterCh(int id)
+{
+ bool val = menuPedoMeter.getBoolean();
+ watch.setPedoSwitch(val, true);
+}
+
+void CALLBACK_FUNCTION onSetupWiFi(int id)
+{
+ printf_w("Start Wifi portal\n");
+ watch.showWifiPortal();
+}
+
+void windowCallbackFn(unsigned int currentValue, RenderPressMode userClicked, displayActivity_t _activity)
+{
+ if (0 == currentValue) // if the encoder / select button is held, we go back to the menu.
+ renderer.giveBackDisplay();
+ else if( watch.getGraphStarting()) {
+ // you need to handle the clearing and preparation of the display when you're first called.
+ // the easiest way is to set a flag such as this and then prepare the display.
+ watch.setGraphStarting(false);
+ switches.getEncoder()->changePrecision( MAX_FILES_CNT+1, 1);
+ }
+ else {
+ watch.start_graph_logic(_activity, currentValue-1);
+ watch.showGraph();
+ }
+}
+
+void tempCallbackFn(unsigned int currentValue, RenderPressMode userClicked)
+{
+ windowCallbackFn(currentValue, userClicked, GRAPH_BODY_TEMP_ACTIVITY);
+}
+
+void CALLBACK_FUNCTION onGraphTempPressed(int id)
+{
+ watch.setGraphStarting(true);
+ showWindow(tempCallbackFn);
+}
+
+//!!! Если открыть график после открытия другого, вначале показывается прошлый, и только после пересчета новый
+
+void stepsCallbackFn(unsigned int currentValue, RenderPressMode userClicked)
+{
+ windowCallbackFn(currentValue, userClicked, GRAPH_STEPS_ACTIVITY);
+}
+
+void CALLBACK_FUNCTION onGraphStepsPressed(int id)
+{
+ watch.setGraphStarting(true);
+ showWindow(stepsCallbackFn);
+}
+
+void CALLBACK_FUNCTION onSyncStatViaWiFi(int id)
+{
+ watch.syncStatViaWifi();
+}
+
+void saveWifiConfigCallback ()
+{
+ watch.updateWebApiConfig();
}
diff --git a/tsh-watch_menu.cpp b/tsh-watch_menu.cpp
new file mode 100644
index 0000000..e3ebfce
--- /dev/null
+++ b/tsh-watch_menu.cpp
@@ -0,0 +1,170 @@
+/*
+ The code in this file uses open source libraries provided by thecoderscorner
+
+ DO NOT EDIT THIS FILE, IT WILL BE GENERATED EVERY TIME YOU USE THE UI DESIGNER
+ INSTEAD EITHER PUT CODE IN YOUR SKETCH OR CREATE ANOTHER SOURCE FILE.
+
+ All the variables you may need access to are marked extern in this file for easy
+ use elsewhere.
+*/
+
+#include
+#include
+#include "tsh-watch_menu.h"
+#include "utils.h"
+
+// Global variable declarations
+
+const PROGMEM ConnectorLocalInfo applicationInfo = { "tsh watch", "dbc86145-ea32-4366-bc86-5b0108ebab98" };
+
+U8g2GfxMenuConfig gfxConfig;
+U8g2MenuRenderer renderer;
+
+// Global Menu Item declarations
+
+
+const BooleanMenuInfo PROGMEM minfoPulseMeter = { "PulseMeter", 13, 0xFFFF, 1, NO_CALLBACK, NAMING_ON_OFF };
+BooleanMenuItem menuPulseMeter(&minfoPulseMeter, false, NULL);
+
+const BooleanMenuInfo PROGMEM minfoPedoMeter = { "PedoMeter", 12, 0xFFFF, 1, onPedoMeterCh, NAMING_ON_OFF };
+BooleanMenuItem menuPedoMeter(&minfoPedoMeter, false, &menuPulseMeter);
+
+const BooleanMenuInfo PROGMEM minfoTemperature = { "Temperature", 11, 0xFFFF, 1, onTemperatureCh, NAMING_ON_OFF };
+BooleanMenuItem menuTemperature(&minfoTemperature, false, &menuPedoMeter);
+
+const SubMenuInfo PROGMEM minfoSensors = { "Sensors", 10, 0xFFFF, 0, NO_CALLBACK };
+RENDERING_CALLBACK_NAME_INVOKE(fnSensorsRtCall, backSubItemRenderFn, "Sensors", -1, NO_CALLBACK)
+BackMenuItem menuBackSensors(fnSensorsRtCall, &menuTemperature);
+SubMenuItem menuSensors(&minfoSensors, &menuBackSensors, NULL);
+
+const char enumStrTimeZone_0[] PROGMEM = "+9";
+const char enumStrTimeZone_1[] PROGMEM = "+8";
+const char enumStrTimeZone_2[] PROGMEM = "+7";
+const char enumStrTimeZone_3[] PROGMEM = "+6";
+const char enumStrTimeZone_4[] PROGMEM = "+5";
+const char enumStrTimeZone_5[] PROGMEM = "+4";
+const char enumStrTimeZone_6[] PROGMEM = "+3";
+const char enumStrTimeZone_7[] PROGMEM = "+2";
+const char enumStrTimeZone_8[] PROGMEM = "+1";
+const char enumStrTimeZone_9[] PROGMEM = "0";
+const char enumStrTimeZone_10[] PROGMEM = "-1";
+const char enumStrTimeZone_11[] PROGMEM = "-2";
+const char enumStrTimeZone_12[] PROGMEM = "-3";
+const char enumStrTimeZone_13[] PROGMEM = "-4";
+const char enumStrTimeZone_14[] PROGMEM = "-5";
+const char enumStrTimeZone_15[] PROGMEM = "-6";
+
+const char* const enumStrTimeZone[] PROGMEM = { enumStrTimeZone_0, enumStrTimeZone_1, enumStrTimeZone_2, enumStrTimeZone_3, enumStrTimeZone_4,
+ enumStrTimeZone_5, enumStrTimeZone_6, enumStrTimeZone_7, enumStrTimeZone_8, enumStrTimeZone_9,
+ enumStrTimeZone_10, enumStrTimeZone_11, enumStrTimeZone_12, enumStrTimeZone_13, enumStrTimeZone_14,
+ enumStrTimeZone_15 };
+
+int8_t const enumIntTimeZone[] PROGMEM = { 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, -1, -2, -3, -4, -5, -6 }; // should be synced with enumStrTimeZone_* and enumStrTimeZone
+
+
+const EnumMenuInfo PROGMEM minfoTimeZone = { "Time Zone", 9, 0xFFFF, 15, onTimezoneCh, enumStrTimeZone };
+EnumMenuItem menuTimeZone(&minfoTimeZone, 0, NULL);
+
+RENDERING_CALLBACK_NAME_INVOKE(fnTimeRtCall, timeItemRenderFn, "Time", -1, onTimeCh)
+
+TimeFormattedMenuItem menuTime(fnTimeRtCall, 8, (MultiEditWireType)EDITMODE_TIME_24H, &menuTimeZone);
+RENDERING_CALLBACK_NAME_INVOKE(fnDateRtCall, dateItemRenderFn, "Date", -1, onDateCh)
+
+DateFormattedMenuItem menuDate(fnDateRtCall, 7, &menuTime);
+
+//const BooleanMenuInfo PROGMEM minfoAutoUpdate = { "Update via Internet", 5, 0xFFFF, 1, onAutoUpdateCh, NAMING_ON_OFF };
+//BooleanMenuItem menuAutoUpdate(&minfoAutoUpdate, false, &menuDate);
+const AnyMenuInfo PROGMEM minfoAutoUpdate = { "Update via Internet", 6, 0xFFFF, 0, onAutoUpdateCh };
+ActionMenuItem menuAutoUpdate(&minfoAutoUpdate, &menuDate);
+
+const SubMenuInfo PROGMEM minfoDateTime = { "Date/Time", 5, 0xFFFF, 0, NO_CALLBACK };
+RENDERING_CALLBACK_NAME_INVOKE(fnDateTimeRtCall, backSubItemRenderFn, "Date/Time", -1, NO_CALLBACK)
+BackMenuItem menuBackDateTime(fnDateTimeRtCall, &menuAutoUpdate);
+SubMenuItem menuDateTime(&minfoDateTime, &menuBackDateTime, &menuSensors);
+
+//onSetupWiFi
+
+//const BooleanMenuInfo PROGMEM minfoSetupWiFi = { "Setup WiFi", 2, 0xFFFF, 1, onSetupWiFi, NAMING_ON_OFF };
+//BooleanMenuItem menuSetupWiFi(&minfoSetupWiFi, false, &menuDateTime);
+
+const AnyMenuInfo PROGMEM minfoSetupWiFi = { "Setup WiFi", 4, 0xFFFF, 0, onSetupWiFi };
+ActionMenuItem menuSetupWiFi(&minfoSetupWiFi, &menuDateTime);
+
+
+const SubMenuInfo PROGMEM minfoSettings = { "Settings", 3, 0xFFFF, 0, NO_CALLBACK };
+
+RENDERING_CALLBACK_NAME_INVOKE(fnSettingsRtCall, backSubItemRenderFn, "Settings", -1, NO_CALLBACK)
+BackMenuItem menuBackSettings(fnSettingsRtCall, &menuSetupWiFi);
+SubMenuItem menuSettings(&minfoSettings, &menuBackSettings, NULL);
+
+//menuSyncStatViaWiFi
+const AnyMenuInfo PROGMEM minfoSyncStatViaWiFi = { "Sync action data", 14, 0xFFFF, 0, onSyncStatViaWiFi };
+ActionMenuItem menuSyncStatViaWiFi(&minfoSyncStatViaWiFi, &menuSettings);
+
+/*
+const AnyMenuInfo PROGMEM minfoGraphTemp = { "Graph Temp.", 2, 0xFFFF, 0, onGraphTempPressed };
+ActionMenuItem menuGraphTemp(&minfoGraphTemp, &menuSettings);
+
+const AnyMenuInfo PROGMEM minfoGraphSteps = { "Graph Steps", 1, 0xFFFF, 0, onGraphStepsPressed };
+ActionMenuItem menuGraphSteps(&minfoGraphSteps, &menuGraphTemp);
+*/
+const AnyMenuInfo PROGMEM minfoGraphTemp = { "Graph Temp.", 2, 0xFFFF, 0, onGraphTempPressed };
+ActionMenuItem menuGraphTemp(&minfoGraphTemp, &menuSyncStatViaWiFi);
+
+const AnyMenuInfo PROGMEM minfoGraphSteps = { "Graph Steps", 1, 0xFFFF, 0, onGraphStepsPressed };
+ActionMenuItem menuGraphSteps(&minfoGraphSteps, &menuGraphTemp);
+
+const AnyMenuInfo PROGMEM minfoClose = { "Close", 14, 0xFFFF, 0, onBack };
+ActionMenuItem menuClose(&minfoClose, &menuGraphSteps);
+
+
+void myDisplayFunction(unsigned int encoderValue, RenderPressMode clicked) {
+
+}
+
+// Set up code
+void setupMenu( U8G2 *_gfx ) {
+ menuPulseMeter.setReadOnly(true);
+
+ prepareBasicU8x8Config(gfxConfig);
+ renderer.setGraphicsDevice(_gfx, &gfxConfig);
+ /*
+ renderer.setResetCallback([] {
+ startedCustomRender = false;
+ renderer.takeOverDisplay(myDisplayFunction);
+ });
+*/
+
+ switches.initialise(internalDigitalIo(), true);
+ menuMgr.initForUpDownOk(&renderer, &menuClose, 33, 14, 27);
+}
+
+
+void showDialog(const char * Header, char *line2, CompletedHandlerFn completedHandler) {
+ BaseDialog* dlg = renderer.getDialog();
+ if (!dlg)
+ return;
+
+ // first we set the buttons how we want them. BTNTYPE_NONE means no button.
+ dlg->setButtons(BTNTYPE_NONE, BTNTYPE_CLOSE);
+
+ // then we show the dialog - 2nd boolean parameter is if dialog is local only
+ dlg->show(Header, false, completedHandler);
+ // and then we set the second line (buffer) - must be after show.
+ dlg->copyIntoBuffer(line2);
+// dlg->setUserData((void*)"Something else");
+}
+
+void hideDialog() {
+ BaseDialog* dlg = renderer.getDialog();
+ if (!dlg)
+ return;
+ dlg->hide();
+}
+
+void showWindow(RendererCallbackFn displayFn){
+ renderer.takeOverDisplay(displayFn);
+}
+void returnToMenu() {
+ renderer.giveBackDisplay();
+}
diff --git a/tsh-watch_menu.h b/tsh-watch_menu.h
new file mode 100644
index 0000000..20529e0
--- /dev/null
+++ b/tsh-watch_menu.h
@@ -0,0 +1,71 @@
+/*
+ The code in this file uses open source libraries provided by thecoderscorner
+
+ DO NOT EDIT THIS FILE, IT WILL BE GENERATED EVERY TIME YOU USE THE UI DESIGNER
+ INSTEAD EITHER PUT CODE IN YOUR SKETCH OR CREATE ANOTHER SOURCE FILE.
+
+ All the variables you may need access to are marked extern in this file for easy
+ use elsewhere.
+*/
+
+#ifndef MENU_GENERATED_CODE_H
+#define MENU_GENERATED_CODE_H
+
+#include
+#include
+#include
+#include "tcMenuU8g2.h"
+
+extern int8_t const enumIntTimeZone[];
+
+
+void showWindow(RendererCallbackFn displayFn);
+void returnToMenu() ;
+void showDialog(const char * Header, char *line2, CompletedHandlerFn completedHandler);
+void hideDialog();
+void setupMenu(U8G2 *_gfx); // forward reference of the menu setup function.
+extern const PROGMEM ConnectorLocalInfo applicationInfo; // defines the app info to the linker.
+
+// Global variables that need exporting
+
+extern U8G2_SH1106_128X64_NONAME_F_HW_I2C *gfx;
+extern U8g2GfxMenuConfig gfxConfig;
+extern U8g2MenuRenderer renderer;
+
+// Callback functions must always include CALLBACK_FUNCTION after the return type
+#define CALLBACK_FUNCTION
+
+// Global Menu Item exports
+
+extern ActionMenuItem menuClose;
+extern BooleanMenuItem menuPulseMeter;
+extern BooleanMenuItem menuPedoMeter;
+extern BooleanMenuItem menuTemperature;
+extern SubMenuItem menuSensors;
+extern EnumMenuItem menuTimeZone;
+extern TimeFormattedMenuItem menuTime;
+extern DateFormattedMenuItem menuDate;
+//extern BooleanMenuItem menuAutoUpdate;
+extern ActionMenuItem menuAutoUpdate;
+
+extern SubMenuItem menuDateTime;
+extern ActionMenuItem menuSetupWiFi;
+extern SubMenuItem menuSettings;
+extern ActionMenuItem menuGraphTemp;
+extern ActionMenuItem menuGraphSteps;
+extern ActionMenuItem menuSyncStatViaWiFi;
+
+void CALLBACK_FUNCTION onBack(int id);
+void CALLBACK_FUNCTION onAutoUpdateCh(int id);
+void CALLBACK_FUNCTION onDateCh(int id);
+void CALLBACK_FUNCTION onTimeCh(int id);
+void CALLBACK_FUNCTION onTimezoneCh(int id) ;
+void CALLBACK_FUNCTION onTemperatureCh(int id);
+void CALLBACK_FUNCTION onPedoMeterCh(int id);
+void CALLBACK_FUNCTION onGraphTempPressed(int id);
+void CALLBACK_FUNCTION onGraphStepsPressed(int id);
+void CALLBACK_FUNCTION onSetupWiFi(int id);
+void CALLBACK_FUNCTION onSyncStatViaWiFi(int id);
+
+
+#endif // MENU_GENERATED_CODE_H
diff --git a/ulp_main.cpp b/ulp_main.cpp
new file mode 100644
index 0000000..52580ae
--- /dev/null
+++ b/ulp_main.cpp
@@ -0,0 +1,104 @@
+/*
+ Must allocate more memory for the ulp in
+ esp32/tools/sdk/include/sdkconfig.h
+ -> #define CONFIG_ULP_COPROC_RESERVE_MEM
+ for this sketch to compile. 2048b seems
+ good.
+*/
+
+#include
+#include
+
+#include "esp_sleep.h"
+#include "soc/rtc_cntl_reg.h"
+#include "soc/rtc_io_reg.h"
+#include "soc/sens_reg.h"
+#include "soc/soc.h"
+#include "driver/rtc_io.h"
+#include "esp32/ulp.h"
+#include "sdkconfig.h"
+#include "ulp_main.h"
+#include "ulptool.h"
+#include "utils.h"
+
+
+extern const uint8_t ulp_main_bin_start[] asm("_binary_ulp_main_bin_start");
+extern const uint8_t ulp_main_bin_end[] asm("_binary_ulp_main_bin_end");
+
+const gpio_num_t GPIO_SCL = GPIO_NUM_25;
+const gpio_num_t GPIO_SDA = GPIO_NUM_32;
+
+static void startUlpProgram();
+
+int32_t tm = 1;
+int32_t tb = 3600;
+
+void printUlpStatus() {
+ printf_w("ulp status: sample_counter %d, errors: ina219 %d, ds3231 %d, lsm6ds3 %d, mlx90615 %d, max30100 %d\n",
+ ulp_sample_counter, ulp_ina219_error, ulp_ds3231_error, ulp_lsm6ds3_error, ulp_mlx90615_error, ulp_max30100_error);
+/*
+ printf_w("ulp status: sensors 0x%X, sensors_working 0x%X, lsm6ds3_inited 0x%X\n",
+ ulp_sensors_switch_extern, ulp_sensors_working, ulp_lsm6ds3_inited);
+*/
+ printf_w("ulp status: ulp_sensors_switch_extern 0x%X\n", ulp_sensors_switch_extern);
+ printf_w("read_cnt: ina219 %d,\t lsm6ds3 %d,\t mlx90615 %d,\t max30100 %d\n",
+ ulp_ina219_read_cnt, ulp_lsm6ds3_read_cnt, ulp_mlx90615_read_cnt, ulp_max30100_read_cnt);
+ printf_w("skip_cnt: ina219 %d,\t lsm6ds3 %d,\t mlx90615 %d,\t max30100 %d\n",
+ ulp_ina219_skip_cnt, ulp_lsm6ds3_skip_cnt, ulp_mlx90615_skip_cnt, ulp_max30100_skip_cnt);
+/*
+ readUlpStack();
+
+ printf_w("ulp status: cnt_read %d, icounter %d, ulp_resul_pointer %d\n",
+ ulp_cnt_read, ulp_icounter, ulp_resul_pointer);*/
+}
+
+void setupUlp( esp_sleep_wakeup_cause_t cause ) {
+ if (cause != ESP_SLEEP_WAKEUP_ULP) {
+ printf_w("ULP wakeup, init_ulp_program\n");
+ initUlpProgram();
+ }
+ else
+ printf_w("ULP wakeup, it shouldn't run!!!!! \n");
+}
+
+
+void runUlp() {
+ printf_w("ulp_run start\n");
+ startUlpProgram();
+}
+
+
+void initUlpProgram()
+{
+ esp_err_t err = ulptool_load_binary(0, ulp_main_bin_start,
+ (ulp_main_bin_end - ulp_main_bin_start) / sizeof(uint32_t));
+ ESP_ERROR_CHECK(err);
+
+ rtc_gpio_init(GPIO_SCL);
+ rtc_gpio_set_direction(GPIO_SCL, RTC_GPIO_MODE_INPUT_ONLY);
+ rtc_gpio_pulldown_dis(GPIO_SCL);
+ rtc_gpio_pullup_en(GPIO_SCL);
+
+ rtc_gpio_init(GPIO_SDA);
+ rtc_gpio_set_direction(GPIO_SDA, RTC_GPIO_MODE_INPUT_ONLY);
+ rtc_gpio_pulldown_dis(GPIO_SDA);
+ rtc_gpio_pullup_en(GPIO_SDA);
+
+ // Set ULP wake up period to
+ ulp_set_wakeup_period(0, 1 * 1000 * 1000); // 1000ms
+}
+
+void readUlpStack() {
+ for (int i =0; i < 32; i++) {
+ printf_w("0x%000x " , (uint16_t) *( &ulp_stackEnd - i) );
+ if (0 == (i+1) % 8)
+ printf_w("\n");
+ }
+ printf_w("\n");
+}
+
+static void startUlpProgram()
+{
+ esp_err_t err = ulp_run((&ulp_entry - RTC_SLOW_MEM) / sizeof(uint32_t));
+ ESP_ERROR_CHECK(err);
+}
diff --git a/ulp_main.h b/ulp_main.h
new file mode 100644
index 0000000..b7a6337
--- /dev/null
+++ b/ulp_main.h
@@ -0,0 +1,97 @@
+#include "Arduino.h"
+
+
+enum ulp_sensor_type_t{
+ SENSOR_INA219 = 0x01,
+ SENSOR_LSM6DS3 = 0x02,
+ SENSOR_MLX90615 = 0x04,
+ SENSOR_MAX30100 = 0x08,
+};
+extern uint16_t ulp_stackEnd;
+
+extern uint16_t ulp_ina219_read_cnt;
+extern uint16_t ulp_lsm6ds3_read_cnt;
+extern uint16_t ulp_mlx90615_read_cnt;
+extern uint16_t ulp_max30100_read_cnt;
+
+
+extern uint16_t ulp_ina219_skip_cnt;
+extern uint16_t ulp_lsm6ds3_skip_cnt;
+extern uint16_t ulp_mlx90615_skip_cnt;
+extern uint16_t ulp_max30100_skip_cnt;
+
+
+extern uint32_t ulp_entry;
+extern uint16_t ulp_result;
+
+extern uint16_t ulp_sample_counter;
+extern uint16_t ulp_sensors_switch_extern; // on / off switch for sensors
+extern uint16_t ulp_sensors_working;
+
+
+//ina219
+extern uint16_t ulp_ina219_error;
+extern uint16_t ulp_ina219_config;
+extern int16_t ulp_ina219_current;
+extern int16_t ulp_ina219_voltage;
+extern int16_t ulp_ina219_aggr_current;
+extern int16_t ulp_ina219_currentDivider_mA;
+extern int16_t ulp_ina219_calValue;
+extern int32_t ulp_ina219_current_table[60];
+
+
+//ds3231
+extern uint16_t ulp_ds3231_update_flag;
+
+extern uint8_t ulp_ds3231_error;
+extern uint8_t ulp_ds3231_year;
+extern uint8_t ulp_ds3231_month;
+extern uint8_t ulp_ds3231_day;
+extern uint8_t ulp_ds3231_dayOfWeek;
+extern uint8_t ulp_ds3231_hour;
+extern uint8_t ulp_ds3231_minute;
+extern uint8_t ulp_ds3231_second;
+
+extern int32_t ulp_ds3231_set_second;
+extern uint8_t ulp_ds3231_set_minute;
+extern uint8_t ulp_ds3231_set_hour;
+extern uint8_t ulp_ds3231_set_dayOfWeek;
+extern uint8_t ulp_ds3231_set_day;
+extern uint8_t ulp_ds3231_set_month;
+extern uint8_t ulp_ds3231_set_year;
+
+
+//lsm6ds3
+extern uint16_t ulp_lsm6ds3_error;
+extern uint16_t ulp_lsm6ds3_inited;
+extern uint16_t ulp_lsm6ds3_step_count;
+extern uint16_t ulp_lsm6ds3_reseted_steps;
+extern uint16_t ulp_lsm6ds3_driver_sign;
+extern uint16_t ulp_lsm6ds3_error_cnt;
+
+//mlx90615
+extern uint16_t ulp_mlx90615_error;
+extern uint16_t ulp_mlx90615_obj_temperature;
+extern uint16_t ulp_mlx90615_amb_temperature;
+
+extern uint16_t ulp_mlx90615_b1;
+extern uint16_t ulp_mlx90615_b2;
+extern uint16_t ulp_mlx90615_pec;
+
+
+// max30100
+extern uint16_t ulp_max30100_flags;
+extern uint16_t ulp_max30100_error;
+extern uint16_t ulp_max30100_toRead;
+extern uint16_t ulp_max30100_r2;
+extern uint16_t ulp_max30100_r3;
+extern uint32_t ulp_max30100_raw_data[200] ;
+extern uint32_t ulp_max30100_data_pointer;
+
+
+void readUlpStack();
+
+void initUlpProgram();
+void runUlp() ;
+void setupUlp( esp_sleep_wakeup_cause_t cause );
+void printUlpStatus();
diff --git a/upload_code.sh b/upload_code.sh
new file mode 100644
index 0000000..3efc069
--- /dev/null
+++ b/upload_code.sh
@@ -0,0 +1,16 @@
+#ARDUINO_DIR=/home/orangepi/prg/arduino-1.8.9/
+ARDUINO_LIB_DIR=${ARDUINO_DIR}libraries/
+ARDUINO_EXEC=${ARDUINO_DIR}arduino
+
+# avrispmkII
+# --useprogrammer avrispmkII
+#stty -F /dev/ttyUSB0 9600
+
+# --verbose-build
+
+#----useprogrammer--pref programmer=arduino:avrispmkII
+${ARDUINO_EXEC} --board arduino:avr:nano:cpu=atmega328old --port /dev/ttyUSB0 --verbose-upload --upload sketch_simple_bot.ino
+
+
+#arduino --upload --board arduino:avr:mega:cpu=atmega2560 --port /dev/ttyUSB1 --useprogrammer --pref programmer=arduino:buspirate
+
diff --git a/utils.h b/utils.h
index 8174ddc..1ca0dca 100644
--- a/utils.h
+++ b/utils.h
@@ -7,10 +7,14 @@
#define print_w(a) (Serial.print(a))
#define println_w(a) (Serial.println(a))
#define write_w(a) (Serial.write(a))
+#define printf_w(...)(Serial.printf(__VA_ARGS__))
#else
#define print_w(a)
#define println_w(a)
#define write_w(a)
+#define printf_w(...)
#endif
+
+
#endif