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