Advertisement
Guest User

USB Batt tester/profiler

a guest
Mar 12th, 2025
75
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 25.87 KB | None | 0 0
  1. //https://github.com/yucandu/othello
  2.  
  3. #include <Wire.h>
  4. #include <INA219_WE.h>
  5.  
  6. #include <ArduinoOTA.h>
  7. #include <WiFi.h>
  8. #include <FastLED.h>
  9. #include <BlynkSimpleEsp32.h>
  10. #include "time.h"
  11.  
  12. #include <Adafruit_GFX.h>
  13. #include <Adafruit_SH110X.h>
  14.  
  15. #define NUM_SAMPLES 2500
  16. #define SCREEN_WIDTH 128 // OLED display width, in pixels
  17. #define SCREEN_HEIGHT 64 // OLED display height, in pixels
  18. #define DATA_PIN 1
  19. #define currentThreshold 2
  20. CRGB leds[1];
  21. // ----- OLED Setup -----
  22. #define OLED_RESET -1 // Reset pin # (or -1 if sharing Arduino reset pin)
  23. #define i2c_Address 0x3c ///< See datasheet for Address; 0x3D for 128x64, 0x3C for 128x32
  24. Adafruit_SH1106G display = Adafruit_SH1106G(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
  25.  
  26. // ----- INA219 Sensor Setup -----
  27. INA219_WE INA1 = INA219_WE(0x40);
  28. INA219_WE INA2 = INA219_WE(0x44);
  29. //Adafruit_INA219 INA1(0x40); // Measures discharge current/voltage
  30. //Adafruit_INA219 INA2(0x44); // Measures charge current/voltage
  31. bool connected = false;
  32. // ----- 5-Way Switch Pin Definitions -----
  33. const int btnUp = 5;
  34. const int btnDown = 10; // Was 6, changed to 10
  35. const int btnLeft = 6; // Was 7, changed to 6
  36. const int btnRight = 7; // Was 10, changed to 7
  37. const int btnCenter= 20;
  38. const int FET1= 21;
  39. const int FET2= 0;
  40. const int ledpin= 1;
  41. const int chargerpin= 3;
  42. const char* ssid = "mikesnet";
  43. const char* password = "springchicken";
  44. bool toggleState = true;
  45. // Add to enum MenuState
  46. enum MenuState {
  47. MAIN_SCREEN,
  48. MENU_SCREEN,
  49. EDITING_CHARGE,
  50. EDITING_CYCLES,
  51. BATT_TEST,
  52. PROFILER,
  53. PROFILER_SETUP
  54. };
  55.  
  56. // Add new global variables
  57. unsigned long profileStartTime = 0;
  58. bool isProfilerRunning = false;
  59. float maxCurrent = 0; // For y-axis scaling
  60.  
  61. typedef struct {
  62. uint32_t time_ms[NUM_SAMPLES]; // timestamps in milliseconds
  63. float voltage[NUM_SAMPLES]; // INA219 bus voltage readings
  64. float current[NUM_SAMPLES]; // INA219 current readings
  65. float power[NUM_SAMPLES]; // INA219 power readings
  66. } SensorData;
  67.  
  68. SensorData dataBuffer;
  69. int sampleIndex = 0;
  70. int sampleTime = 10;
  71. // Add new global variables
  72. bool wasCurrentLow = false;
  73. int brFactor = 1;
  74. String status = "None";
  75. double profileMwh;
  76. double profileMah;
  77. double dischargeMwh = 0.0;
  78. double dischargeMah = 0.0;
  79. double chargeMwh = 0.0;
  80. double chargeMah = 0.0;
  81. unsigned long testStartTime = 0;
  82. bool isDischarging = false;
  83. bool isCharging = false;
  84. int currentCycle = 1;
  85. unsigned long testEndTime = 0; // To store completion time
  86. MenuState currentState = MAIN_SCREEN;
  87. const int MENU_ITEMS_TOTAL = 6; // Total number of menu items
  88. const int MENU_ITEMS_VISIBLE = 5; // How many can fit on screen
  89. int menuScrollOffset = 0; // Track scroll position
  90. int selectedMenuItem = 0;
  91. bool chargeEnabled = true;
  92. int numCycles = 1;
  93. float vcutoff = 2.9;
  94. double INA2_mWh = 0.0;
  95. double INA2_mAh = 0.0;
  96. unsigned long lastSampleTime = 0;
  97. float voltage2;
  98. float current2;
  99. float power2;
  100. float voltage1;
  101. float current1;
  102. float power1;
  103. struct tm timeinfo;
  104. unsigned long accumulatedTestTime = 0;
  105. unsigned long lastTestTime = 0;
  106. bool wasTestingPreviously = false;
  107. char auth[] = "ozogc-FyTEeTsd_1wsgPs5rkFazy6L79";
  108.  
  109. const char* ntpServer = "pool.ntp.org";
  110. const long gmtOffset_sec = -18000; //Replace with your GMT offset (secs)
  111. const int daylightOffset_sec = 3600; //Replace with your daylight offset (secs)
  112.  
  113. WidgetTerminal terminal(V10);
  114.  
  115. #define every(interval) \
  116. static uint32_t __every__##interval = millis(); \
  117. if (millis() - __every__##interval >= interval && (__every__##interval = millis()))
  118.  
  119. BLYNK_WRITE(V10) {
  120. if (String("help") == param.asStr()) {
  121. terminal.println("==List of available commands:==");
  122. terminal.println("wifi");
  123. terminal.println("volts");
  124. terminal.println("==End of list.==");
  125. }
  126. if (String("wifi") == param.asStr()) {
  127. terminal.print("Connected to: ");
  128. terminal.println(ssid);
  129. terminal.print("IP address:");
  130. terminal.println(WiFi.localIP());
  131. terminal.print("Signal strength: ");
  132. terminal.println(WiFi.RSSI());
  133. printLocalTime();
  134. }
  135. if (String("volts") == param.asStr()) {
  136. float shuntvoltage = INA1.getShuntVoltage_mV();
  137. float busvoltage = INA1.getBusVoltage_V();
  138. float power = INA1.getBusPower();
  139. float current = INA1.getCurrent_mA();
  140. float volts = busvoltage + (shuntvoltage / 1000);
  141. terminal.print("Volts: ");
  142. terminal.print(volts);
  143. terminal.println("v");
  144. terminal.print("Current: ");
  145. terminal.print(current);
  146. terminal.println("mA");
  147. terminal.print("Power: ");
  148. terminal.print(power);
  149. terminal.println("mW");
  150. }
  151. terminal.flush();
  152. }
  153.  
  154. void printLocalTime() {
  155. time_t rawtime;
  156. struct tm* timeinfo;
  157. time(&rawtime);
  158. timeinfo = localtime(&rawtime);
  159. terminal.print(asctime(timeinfo));
  160. }
  161.  
  162. // Global variables for debouncing
  163. static unsigned long lastButtonPress = 0;
  164. static String lastButtonState = "None";
  165.  
  166. // Modified getRawButtonPressed to include debouncing
  167. String getRawButtonPressed() {
  168. if (digitalRead(btnUp) == LOW) return "UP";
  169. if (digitalRead(btnDown) == LOW) return "DOWN";
  170. if (digitalRead(btnLeft) == LOW) return "LEFT";
  171. if (digitalRead(btnRight) == LOW) return "RIGHT";
  172. if (digitalRead(btnCenter) == LOW)return "CENTER";
  173. return "None";
  174. }
  175.  
  176. String getDebouncedButton() {
  177. String currentButton = getRawButtonPressed();
  178. unsigned long currentTime = millis();
  179.  
  180. if (currentButton != lastButtonState) {
  181. lastButtonState = currentButton;
  182. if (currentButton != "None" && currentTime - lastButtonPress > 100) {
  183. lastButtonPress = currentTime;
  184. return currentButton;
  185. }
  186. }
  187. return "None";
  188. }
  189.  
  190. int getDecimalPlaces(float value) {
  191. if (value >= 0.1) return 1;
  192. if (value >= 0.01) return 2;
  193. if (value >= 0.001) return 3;
  194. if (value >= 0.0001) return 4;
  195. return 5;
  196. }
  197.  
  198. void drawProfiler() {
  199. if (currentState == PROFILER_SETUP) {
  200. display.clearDisplay();
  201. display.setTextSize(1);
  202. display.setTextColor(SH110X_WHITE);
  203.  
  204. display.setCursor(0, 20);
  205. display.print("Sample time?");
  206. printRightAligned(String(sampleTime) + "s", 128, 20);
  207. display.display();
  208.  
  209. String activeButton = getDebouncedButton();
  210. float current2 = INA2.getCurrent_mA();
  211. if (activeButton == "UP" && sampleTime < 60) {
  212. sampleTime++;
  213. } else if (activeButton == "DOWN" && sampleTime > 1) {
  214. sampleTime--;
  215. } else if (activeButton == "CENTER" || (wasCurrentLow && current2 > 2)) {
  216. currentState = PROFILER;
  217. sampleIndex = 0;
  218. profileStartTime = millis();
  219. isProfilerRunning = true;
  220. profileMwh = 0;
  221. profileMah = 0;
  222. maxCurrent = 0;
  223. }
  224. return;
  225. }
  226.  
  227. // Draw the chart - whether running or complete
  228. display.clearDisplay();
  229. display.setTextSize(1);
  230.  
  231. // Get current sample if running
  232. if (isProfilerRunning && sampleIndex < NUM_SAMPLES) {
  233. float current = INA2.getCurrent_mA();
  234. float voltage = INA2.getBusVoltage_V();
  235. float power = INA2.getBusPower();
  236. unsigned long now = millis();
  237.  
  238. if (current > maxCurrent) maxCurrent = current;
  239.  
  240. dataBuffer.time_ms[sampleIndex] = now - profileStartTime;
  241. dataBuffer.current[sampleIndex] = current;
  242. dataBuffer.voltage[sampleIndex] = voltage;
  243. dataBuffer.power[sampleIndex] = power;
  244.  
  245. // Calculate accumulated values
  246. if (sampleIndex > 0) {
  247. unsigned long dt = dataBuffer.time_ms[sampleIndex] - dataBuffer.time_ms[sampleIndex-1];
  248. float hours = dt / 3600000.0;
  249. profileMwh += power * hours;
  250. profileMah += current * hours;
  251. }
  252.  
  253. sampleIndex++;
  254.  
  255. // Check if time is up
  256. if ((now - profileStartTime) >= (sampleTime * 1000)) {
  257. isProfilerRunning = false;
  258. }
  259. }
  260.  
  261. // Draw current values at top
  262. if (isProfilerRunning) {
  263. // Show live current value top left
  264. display.setCursor(0, 0);
  265. display.print(dataBuffer.current[sampleIndex-1], 1);
  266. display.print("mA");
  267.  
  268. // Show sample count top right
  269. int expectedSamples = sampleTime * 33; // Assuming 33 samples/sec
  270. printRightAligned(String(sampleIndex) + "/" + String(expectedSamples), 128, 0);
  271. } else {
  272. // Show accumulated values when complete
  273. int mwhDecimals = 4;//getDecimalPlaces(profileMwh);
  274. int mahDecimals = 4;//getDecimalPlaces(profileMah);
  275. printRightAligned(String(profileMwh, mwhDecimals) + "mWh", 128, 0);
  276. printRightAligned(String(profileMah, mahDecimals) + "mAh", 128, 8);
  277. display.setCursor(0, 0);
  278. display.print(sampleTime, 0);
  279. display.println("s");
  280. }
  281.  
  282. // Always show max current top left if we have samples
  283. if (sampleIndex > 0) {
  284. display.setCursor(0, 8);
  285. display.print(maxCurrent, 0);
  286. display.print("mA max");
  287. }
  288.  
  289. // Draw the chart
  290. int expectedSamples = sampleTime * 33; // Fixed x-axis scale
  291. float samplesPerPixel = (float)expectedSamples / 128.0;
  292.  
  293. // Draw bars for each pixel column
  294. for (int x = 0; x < 128; x++) {
  295. // Calculate which samples fall into this pixel column
  296. int startSample = (int)(x * samplesPerPixel);
  297. int endSample = (int)((x + 1) * samplesPerPixel);
  298.  
  299. // Ensure we don't exceed actual samples
  300. endSample = min(endSample, sampleIndex);
  301.  
  302. if (startSample >= sampleIndex) break;
  303.  
  304. // Calculate average current for this pixel column
  305. float sumCurrent = 0;
  306. int count = 0;
  307.  
  308. for (int i = startSample; i < endSample && i < NUM_SAMPLES; i++) {
  309. sumCurrent += dataBuffer.current[i];
  310. count++;
  311. }
  312.  
  313. if (count > 0) {
  314. float avgCurrent = sumCurrent / count;
  315. // Map average current to pixel height
  316. int barHeight = map(avgCurrent, 0, maxCurrent > 0 ? maxCurrent : 1000, 0, 47);
  317. // Draw vertical bar from bottom
  318. display.drawFastVLine(x, 63 - barHeight, barHeight, SH110X_WHITE);
  319. }
  320. }
  321. // Draw axes
  322. display.drawFastHLine(0, 63, 128, SH110X_WHITE); // x-axis
  323. display.drawFastHLine(0, 16, 128, SH110X_WHITE); // Draw top line
  324. display.drawFastVLine(0, 16, 48, SH110X_WHITE); // y-axis
  325. display.display();
  326.  
  327. // Check for exit only when complete
  328. if (!isProfilerRunning) {
  329. String activeButton = getDebouncedButton();
  330. if (activeButton == "CENTER") {
  331. sampleIndex = 0; // Reset for next time
  332. profileMwh = 0;
  333. profileMah = 0;
  334. maxCurrent = 0;
  335. memset(&dataBuffer, 0, sizeof(dataBuffer)); // Clear buffer
  336. currentState = MENU_SCREEN;
  337. }
  338. }
  339. }
  340.  
  341.  
  342.  
  343. void drawWifiIcon(int x, int y) {
  344. if (WiFi.status() == WL_CONNECTED) {
  345. // Draw three arcs relative to (x, y)
  346. // Outer arc
  347. display.drawPixel(x - 1, y + 1, SH110X_WHITE);
  348. display.drawPixel(x, y, SH110X_WHITE);
  349. display.drawPixel(x + 1, y, SH110X_WHITE);
  350. display.drawPixel(x + 2, y, SH110X_WHITE);
  351. display.drawPixel(x + 3, y + 1, SH110X_WHITE);
  352. // Middle arc
  353. display.drawPixel(x, y + 3, SH110X_WHITE);
  354. display.drawPixel(x + 1, y + 2, SH110X_WHITE);
  355. display.drawPixel(x + 2, y + 3, SH110X_WHITE);
  356. // Center dot
  357. display.drawPixel(x + 1, y + 5, SH110X_WHITE);
  358. }
  359. }
  360.  
  361.  
  362.  
  363. // Helper function for right-aligned text
  364. void printRightAligned(const String &text, int x, int y) {
  365. int16_t x1, y1;
  366. uint16_t w, h;
  367. display.getTextBounds(text.c_str(), 0, 0, &x1, &y1, &w, &h);
  368. display.setCursor(x - w, y);
  369. display.print(text);
  370. }
  371.  
  372. void printCenterAligned(const String &text, int x, int y) {
  373. int16_t x1, y1;
  374. uint16_t w, h;
  375. display.getTextBounds(text.c_str(), 0, 0, &x1, &y1, &w, &h);
  376. display.setCursor(x - (w/2), y);
  377. display.print(text);
  378. }
  379.  
  380. void drawBattTest() {
  381. String activeButton = getDebouncedButton();
  382. if(activeButton == "CENTER") {
  383. currentState = MENU_SCREEN;
  384. digitalWrite(FET1, LOW);
  385. digitalWrite(FET2, LOW);
  386. isDischarging = false;
  387. isCharging = false;
  388. return;
  389. }
  390.  
  391. voltage1 = INA1.getBusVoltage_V();
  392. current1 = INA1.getCurrent_mA();
  393. power1 = INA1.getBusPower();
  394.  
  395. if(isCharging && analogRead(chargerpin) > 2000) {
  396. isCharging = false;
  397. digitalWrite(FET2, LOW);
  398. if(currentCycle < numCycles) {
  399. currentCycle++;
  400. double efficiency = (dischargeMwh > 0) ? (dischargeMwh / chargeMwh) * 100.0 : 0.0;
  401. Blynk.virtualWrite(V17, efficiency);
  402. dischargeMwh = 0;
  403. dischargeMah = 0;
  404. isDischarging = true;
  405. lastSampleTime = millis();
  406. digitalWrite(FET1, HIGH);
  407. }else {
  408. testEndTime = millis(); // Set end time when all cycles are complete
  409. }
  410. }
  411.  
  412. // Update measurements only during discharge
  413. if(isDischarging) {
  414. if(voltage1 < vcutoff) {
  415. isDischarging = false;
  416. digitalWrite(FET1, LOW);
  417. if(chargeEnabled) {
  418. isCharging = true;
  419. chargeMah = 0;
  420. chargeMwh = 0;
  421. digitalWrite(FET2, HIGH);
  422. } else {
  423. testEndTime = millis(); // Set end time if not charging
  424. }
  425. }
  426. }
  427.  
  428.  
  429.  
  430. // Only update time if test is still running
  431. unsigned long elapsedTime;
  432. if(isDischarging || isCharging) {
  433. elapsedTime = millis() - testStartTime;
  434. } else {
  435. elapsedTime = testEndTime - testStartTime;
  436. }
  437.  
  438. int hours = elapsedTime / 3600000;
  439. int minutes = (elapsedTime % 3600000) / 60000;
  440. int seconds = (elapsedTime % 60000) / 1000;
  441.  
  442. // Display
  443. display.clearDisplay();
  444. display.setTextSize(1);
  445. display.setTextColor(SH110X_WHITE);
  446.  
  447. // Left-aligned labels
  448. display.setCursor(0, 0);
  449. display.print("V:");
  450. display.setCursor(0, 12);
  451. display.print("mA:");
  452. display.setCursor(0, 24);
  453. display.print("mWh:");
  454. display.setCursor(0, 36);
  455. display.print("mAh:");
  456. display.setCursor(0, 48);
  457.  
  458. // Right-aligned values
  459. printRightAligned(String(voltage1, 2) + "V", 128, 0);
  460. printRightAligned(String(current1, 1) + "mA", 128, 12);
  461. printRightAligned(String(dischargeMwh, 0) + "mWh", 128, 24);
  462. printRightAligned(String(dischargeMah, 0) + "mAh", 128, 36);
  463.  
  464. char timeStr[9];
  465. sprintf(timeStr, "%02d:%02d:%02d", hours, minutes, seconds);
  466. printRightAligned(timeStr, 128, 48);
  467.  
  468. // Status indication
  469. display.setCursor(0, 56);
  470. if(isDischarging) {
  471. status = "DISCHARGING";
  472. //leds[0] = CRGB(5, 0, 0);
  473. display.print("DISCHARGE C");
  474. display.print(currentCycle);
  475. } else if(isCharging) {
  476. //leds[0] = CRGB(0, 0, 5);
  477. status = "CHARGING";
  478. display.print("CHARGE C");
  479. display.print(currentCycle);
  480. } else if(!isDischarging && !isCharging) {
  481. //leds[0] = CRGB(0, 5, 0);
  482. status = "COMPLETE";
  483. display.print("COMPLETE C");
  484. display.print(currentCycle);
  485. }
  486. //FastLED.show();
  487. display.display();
  488. }
  489.  
  490. void drawMain() {
  491. String activeButton = getDebouncedButton();
  492. if(activeButton != "None" && currentState == MAIN_SCREEN) {
  493. currentState = MENU_SCREEN;
  494. return;
  495. }
  496.  
  497. String chargerStatus = String(analogRead(chargerpin));
  498. voltage2 = INA2.getBusVoltage_V();
  499. current2 = INA2.getCurrent_mA();
  500. power2 = INA2.getBusPower();
  501.  
  502. display.clearDisplay();
  503. display.setTextSize(2);
  504. display.setTextColor(SH110X_WHITE);
  505.  
  506. // Left-aligned labels
  507.  
  508.  
  509. int maxdec = 1;
  510. if (INA2_mWh > 999) {maxdec = 0;}
  511.  
  512.  
  513.  
  514. // Right-aligned values
  515. printCenterAligned(String(voltage2, 2) + "V", 64, 0);
  516. if (current2 > 0) {printCenterAligned(String(current2, 1) + "mA", 64, 18);}
  517. else {printCenterAligned("0.0mA", 64, 18);}
  518. printCenterAligned(String(INA2_mWh, maxdec) + "mWh", 64, 36);
  519.  
  520. // Draw accumulated time in smaller text
  521. display.setTextSize(1);
  522.  
  523. int hours = accumulatedTestTime / 3600;
  524. int minutes = (accumulatedTestTime % 3600) / 60;
  525. int seconds = accumulatedTestTime % 60;
  526.  
  527. char timeStr[9];
  528. sprintf(timeStr, "%02d:%02d:%02d", hours, minutes, seconds);
  529. printCenterAligned(timeStr, 64, 56);
  530.  
  531. drawWifiIcon(124, 56);
  532. display.display();
  533. }
  534.  
  535. void drawMenu() {
  536. if (currentState == MAIN_SCREEN) return;
  537. display.clearDisplay();
  538. display.setTextSize(1);
  539. const char* menuItems[] = {"Batt Test", "Charge?", "Cycles", "Profiler", "Charge Now", "Main"};
  540.  
  541. for(int i = 0; i < MENU_ITEMS_VISIBLE; i++) {
  542. int actualIndex = i + menuScrollOffset;
  543. if(actualIndex >= MENU_ITEMS_TOTAL) break;
  544.  
  545. // Set text color for menu items
  546. if(actualIndex == selectedMenuItem) {
  547. if((currentState == EDITING_CHARGE && actualIndex == 1) ||
  548. (currentState == EDITING_CYCLES && actualIndex == 2)) {
  549. display.setTextColor(SH110X_WHITE);
  550. } else {
  551. display.setTextColor(SH110X_BLACK, SH110X_WHITE);
  552. }
  553. } else {
  554. display.setTextColor(SH110X_WHITE);
  555. }
  556.  
  557. display.setCursor(0, i*12);
  558. display.print(menuItems[actualIndex]);
  559.  
  560. // Handle right-aligned values
  561. if(actualIndex == 1) { // Charge setting
  562. display.setTextColor(SH110X_WHITE);
  563. if(currentState == EDITING_CHARGE && actualIndex == selectedMenuItem) {
  564. display.setTextColor(SH110X_BLACK, SH110X_WHITE);
  565. }
  566. printRightAligned(chargeEnabled ? "Y" : "N", 128, i*12);
  567. }
  568. else if(actualIndex == 2) { // Cycles setting
  569. display.setTextColor(SH110X_WHITE);
  570. if(currentState == EDITING_CYCLES && actualIndex == selectedMenuItem) {
  571. display.setTextColor(SH110X_BLACK, SH110X_WHITE);
  572. }
  573. printRightAligned(String(numCycles), 128, i*12);
  574. }
  575. }
  576. display.display();
  577. }
  578.  
  579. void handleMenu() {
  580. String activeButton = getDebouncedButton();
  581.  
  582. if(activeButton == "None") return;
  583.  
  584. if(currentState == MENU_SCREEN) {
  585. if(activeButton == "UP") {
  586. if(selectedMenuItem > 0) {
  587. selectedMenuItem--;
  588. // Scroll up if selected item would be off screen
  589. if(selectedMenuItem < menuScrollOffset) {
  590. menuScrollOffset--;
  591. }
  592. } else {
  593. // Wrap to bottom
  594. selectedMenuItem = MENU_ITEMS_TOTAL - 1;
  595. menuScrollOffset = MENU_ITEMS_TOTAL - MENU_ITEMS_VISIBLE;
  596. }
  597. }
  598. else if(activeButton == "DOWN") {
  599. if(selectedMenuItem < MENU_ITEMS_TOTAL - 1) {
  600. selectedMenuItem++;
  601. // Scroll down if selected item would be off screen
  602. if(selectedMenuItem >= menuScrollOffset + MENU_ITEMS_VISIBLE) {
  603. menuScrollOffset++;
  604. }
  605. } else {
  606. // Wrap to top
  607. selectedMenuItem = 0;
  608. menuScrollOffset = 0;
  609. }
  610. }
  611. else if(activeButton == "CENTER") {
  612. if(selectedMenuItem == 0) { // Batt Test
  613. currentState = BATT_TEST;
  614. testStartTime = millis();
  615. lastSampleTime = millis();
  616. dischargeMwh = 0;
  617. dischargeMah = 0;
  618. chargeMah = 0;
  619. chargeMwh = 0;
  620. currentCycle = 1;
  621. isDischarging = true;
  622. isCharging = false;
  623. digitalWrite(FET2, LOW);
  624. digitalWrite(FET1, HIGH);
  625. }
  626. if(selectedMenuItem == 1) {
  627. currentState = EDITING_CHARGE;
  628. }
  629. else if(selectedMenuItem == 2) {
  630. currentState = EDITING_CYCLES;
  631. }
  632. else if(selectedMenuItem == 3) { // Profiler
  633. currentState = PROFILER_SETUP;
  634. //sampleTime = 10; // Reset to default sample time
  635. wasCurrentLow = (INA2.getCurrent_mA() < 2); // Set initial state
  636. return;
  637. }
  638. else if(selectedMenuItem == 4) { // Charge Now
  639. currentState = BATT_TEST;
  640. testStartTime = millis();
  641. lastSampleTime = millis();
  642. dischargeMwh = 0;
  643. dischargeMah = 0;
  644. chargeMah = 0;
  645. chargeMwh = 0;
  646. currentCycle = 1;
  647. isDischarging = false;
  648. isCharging = true;
  649. digitalWrite(FET1, LOW);
  650. digitalWrite(FET2, HIGH);
  651. }
  652. else if(selectedMenuItem == 5) { // Main (moved from 4 to 5)
  653. currentState = MAIN_SCREEN;
  654. selectedMenuItem = 0;
  655. menuScrollOffset = 0;
  656. return;
  657. }
  658. }
  659. }
  660. else if(currentState == EDITING_CHARGE) {
  661. if(activeButton == "UP" || activeButton == "DOWN") {
  662. chargeEnabled = !chargeEnabled;
  663. }
  664. else if(activeButton == "CENTER") {
  665. currentState = MENU_SCREEN;
  666. }
  667. }
  668. else if(currentState == EDITING_CYCLES) {
  669. if(activeButton == "UP" && numCycles < 99) {
  670. numCycles++;
  671. }
  672. else if(activeButton == "DOWN" && numCycles > 1) {
  673. numCycles--;
  674. }
  675. else if(activeButton == "CENTER") {
  676. currentState = MENU_SCREEN;
  677. }
  678. }
  679. }
  680.  
  681.  
  682. void setup() {
  683. Serial.begin(115200);
  684. Wire.begin();
  685. FastLED.addLeds<NEOPIXEL, DATA_PIN>(leds, 1);
  686. leds[0] = CRGB(20, 20, 20);
  687. FastLED.show();
  688.  
  689. // ----- Initialize INA219 Sensors -----
  690. if (!INA1.init()) {
  691. terminal.println("Failed to find INA1 at 0x40");
  692. //while (1);
  693. }
  694. if (!INA2.init()) {
  695. terminal.println("Failed to find INA2 at 0x44");
  696. //while (1);
  697. }
  698.  
  699. INA1.setBusRange(BRNG_16);
  700. INA2.setBusRange(BRNG_16);
  701.  
  702. // ----- Initialize OLED -----
  703. display.begin(i2c_Address, true);
  704. display.setRotation(2);
  705. display.clearDisplay();
  706. display.display();
  707. //display.flipScreenVertically();
  708. display.setFont();
  709. //display.setBrightness(255);
  710.  
  711. // ----- Setup 5-Way Switch Pins -----
  712. pinMode(btnUp, INPUT_PULLUP);
  713. pinMode(btnDown, INPUT_PULLUP);
  714. pinMode(btnLeft, INPUT_PULLUP);
  715. pinMode(btnRight, INPUT_PULLUP);
  716. pinMode(btnCenter, INPUT_PULLUP);
  717. pinMode(chargerpin, INPUT);
  718. drawMain();
  719. digitalWrite(FET1, LOW);
  720. digitalWrite(FET2, LOW);
  721. pinMode(FET1, OUTPUT);
  722. pinMode(FET2, OUTPUT);
  723. digitalWrite(FET1, LOW);
  724. digitalWrite(FET2, LOW);
  725. WiFi.mode(WIFI_STA);
  726. WiFi.begin(ssid, password);
  727. WiFi.setTxPower(WIFI_POWER_8_5dBm);
  728. }
  729.  
  730.  
  731. void loop() {
  732. // ----- Read INA219 Sensor Values -----
  733.  
  734. if ((WiFi.status() == WL_CONNECTED) && (!connected)) {
  735. connected = true;
  736. long m1, m2, m3, m4;
  737. m1 = millis();
  738. ArduinoOTA.setHostname("BatTester");
  739. ArduinoOTA.begin();
  740. m2 = millis();
  741. configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
  742. Blynk.config(auth, IPAddress(192, 168, 50, 197), 8080);
  743. Blynk.connect();
  744. m3 = millis();
  745. getLocalTime(&timeinfo);
  746. m4 = millis();
  747. terminal.println("***BATTERY TESTER v1.0 STARTED***");
  748. terminal.print("Connected to ");
  749. terminal.println(ssid);
  750. terminal.print("IP address: ");
  751. terminal.println(WiFi.localIP());
  752. printLocalTime();
  753. terminal.print("Times: ");
  754. terminal.println(m1);
  755. terminal.println(m2);
  756. terminal.println(m3);
  757. terminal.println(m4);
  758. terminal.flush();
  759. }
  760.  
  761. if ((WiFi.status() == WL_CONNECTED) && (connected)) {
  762. ArduinoOTA.handle();
  763. Blynk.run();
  764. }
  765.  
  766. every(10){
  767. if(currentState == MAIN_SCREEN) {
  768. drawMain();
  769. if (current2 > currentThreshold) {
  770. status = "TESTING";
  771. leds[0] = CRGB(5 * brFactor, 0, 0);
  772. // Update accumulated time
  773. if (!wasTestingPreviously) {
  774. lastTestTime = millis() / 1000; // Convert to seconds
  775. wasTestingPreviously = true;
  776. }
  777. accumulatedTestTime += (millis() / 1000) - lastTestTime;
  778. lastTestTime = millis() / 1000;
  779. } else {
  780. wasTestingPreviously = false;
  781. if (WiFi.status() == WL_CONNECTED) {
  782. leds[0] = CRGB(0, 5 * brFactor, 0);
  783. } else {
  784. leds[0] = CRGB(5 * brFactor, 20 * brFactor, 0);
  785. }
  786. }
  787. } else if(currentState == BATT_TEST) {
  788. drawBattTest();
  789. int currentHour;
  790. currentHour = timeinfo.tm_hour;
  791. if (currentHour > 9) {
  792. brFactor = 4;
  793. }
  794. else {brFactor = 1;}
  795.  
  796. if (isDischarging) {
  797. leds[0] = CRGB(5 * brFactor, 0, 0);
  798. } else if (isCharging) {
  799. leds[0] = CRGB(0, 0, 5 * brFactor);
  800. } else {
  801. leds[0] = CRGB(0, 5 * brFactor, 0);
  802. }
  803. } else if(currentState == PROFILER || currentState == PROFILER_SETUP) {
  804. leds[0] = CRGB(0, 5 * brFactor, 5 * brFactor);
  805. drawProfiler();
  806. } else {
  807. leds[0] = CRGB(5 * brFactor, 0, 5 * brFactor);
  808. handleMenu();
  809. drawMenu();
  810. }
  811. FastLED.show();
  812. }
  813.  
  814.  
  815.  
  816. every(5000){
  817. // Calculate accumulated values
  818. unsigned long currentTime = millis();
  819. double hours = (currentTime - lastSampleTime) / 3600000.0; // Convert ms to hours
  820. INA2_mWh += power2 * hours;
  821. INA2_mAh += current2 * hours;
  822. if (isDischarging) {
  823. dischargeMwh += power1 * hours;
  824. dischargeMah += current1 * hours;
  825. }
  826. else if (isCharging) {
  827. chargeMwh += power1 * hours;
  828. chargeMah += current1 * hours;
  829. }
  830. lastSampleTime = currentTime;
  831.  
  832. }
  833.  
  834. every(10000){
  835. if ((WiFi.status() == WL_CONNECTED) && (!isProfilerRunning)) {
  836. Blynk.virtualWrite(V1, INA1.getBusVoltage_V());
  837. Blynk.virtualWrite(V2, INA1.getCurrent_mA());
  838. Blynk.virtualWrite(V3, INA1.getBusPower());
  839. Blynk.virtualWrite(V4, INA2.getBusVoltage_V());
  840. Blynk.virtualWrite(V5, INA2.getCurrent_mA());
  841. Blynk.virtualWrite(V6, WiFi.RSSI());
  842. Blynk.virtualWrite(V7, INA2.getBusPower());
  843. Blynk.virtualWrite(V8, analogRead(chargerpin));
  844. Blynk.virtualWrite(V9, INA2_mWh);
  845. Blynk.virtualWrite(V11, INA2_mAh);
  846. Blynk.virtualWrite(V12, dischargeMwh);
  847. Blynk.virtualWrite(V13, dischargeMah);
  848. Blynk.virtualWrite(V14, chargeMwh);
  849. Blynk.virtualWrite(V15, chargeMah);
  850. Blynk.virtualWrite(V16, status);
  851. Blynk.virtualWrite(V17, temperatureRead());
  852.  
  853. }
  854. }
  855. }
  856.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement