// https://docs.wokwi.com/guides/esp32 -- used to configure simulation
extern "C" {
#include "user_interface.h"
}
#include <HX711-multi.h>
#include <EEPROM.h>
#include <WiFi.h>
// Include math library for isnan() to recalibrate load cells with no calibration values in EEPROM
// And if user send not a number from serial monitor
// #include <math.h>
#define TARE_TIMEOUT_SECONDS 4
#define DELAY 100
// #define FLOAT_PRECISION 4
#define DEBUG true
// Higher volumes of data or frequent calls to Serial.print can cause delays in the program,
// especially at low baud rates.
// Possible baud rates:
// 300: For extremely simple or low-power applications (e.g., weather stations).
// 4800: For very long cables or noisy environments.
// 9600: Default and reliable for basic Arduino projects. Debugging or basic monitoring
// 38400 or 57600: Medium-speed communication
// 115200: Real-time data logging
// 250000 or higher: High-speed data transfer
#define BAUD_RATE 115200
const char* SSID = "DIR-615";
const char* PASSWORD = "Mi15EvXt";
// Pins
#define CLK 1
#define DOUT1 5
#define DOUT2 4
// #define DOUT3 0
// #define DOUT4 2
// #define DOUT5 14
// #define DOUT6 12
// #define DOUT7 13
// #define DOUT8 15
// Array of data pins
// byte DOUTS[8] = {DOUT1, DOUT2, DOUT3, DOUT4, DOUT5, DOUT6, DOUT7, DOUT8};
byte DOUTS[2] = {DOUT1, DOUT2};
// Number of load cells
const uint8_t CHANNEL_COUNT (sizeof(DOUTS) / sizeof(byte));
// Calibration values for each load cell
// float CALIBRATION_VALUES[CHANNEL_COUNT] = {1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0};
float CALIBRATION_VALUES[CHANNEL_COUNT] = {1.0, 1.0};
// ========================================
// float CALIBRATION_VALUES[CHANNEL_COUNT] = {0.6667, 17492.8339};
// float CALIBRATION_VALUES[CHANNEL_COUNT] = {0.42, 0.42, 0.42, 0.42, 0.42, 0.42, 0.42, 0.42};
const uint64_t CALIBRATION_EEPROM_ADDRESS = 0; // Starting EEPROM address for calibration values
// Initialize HX711 units
// define clock and data pin, channel, and gain value
// channel selection is made by passing the appropriate gain: 128 or 64 for channel A, 32 for channel B
// count: the number of channels
// dout: an array of pin numbers, of length 'count', one entry per channel
// pd_sck: clock pin
// gain:
// 128: channel A, gain value 128
// 64: channel A, gain value 64
// 32: channel B, gain value 32
// initialization types: HX711MULTI(int count, byte *dout, byte pd_sck, byte gain = 128);
// HX711MULTI SCALES(CHANNEL_COUNT, DOUTS, CLK); // default value 128. Then GAIN = 1
HX711MULTI SCALES(HX711MULTI(CHANNEL_COUNT, DOUTS, CLK, 128));
// int32_t RESULTS[CHANNEL_COUNT];
long RESULTS[CHANNEL_COUNT];
uint32_t stackStart;
// WiFi-server
// #define BUFFER_SIZE 64
const uint16_t wifiServerPort = 8080; // Port for TCP server
boolean alreadyConnected = false; // whether or not the client was connected previously
WiFiServer server(wifiServerPort);
WiFiClient client;
// function prototypes
void sendCalibratedData(bool to_check_scales = true);
void is_scales_not_ready_warning(bool to_show_warning = true);
void clientPrintln(const char* text = "");
void setup() {
Serial.begin(BAUD_RATE);
// // For some reason, if you add this pin, the console starts to display the text
// pinMode(15, OUTPUT);
// Serial.print doesn't guarantee immediate transmission; it writes to the buffer, which is sent in the background.
// Use Serial.flush() to wait for the buffer to clear
// Serial.flush();
// // Wait for USB Serial to become available
while (!Serial) {} // Wait for Serial connection (useful for debugging)
delay(2000);
// DEBUGING
stackStart = ESP.getFreeContStack(); // Get the stack pointer at the beginning of the program
system_set_os_print(1); // Enable more detailed crash logs
ESP.wdtDisable(); // Temporarily disable watchdog during setup
configure_web_server();
ESP.wdtEnable(1000); // Re-enable with 1-second timeout
SCALES.setDebugEnable(DEBUG);
clientPrintln(("Welcome to WeMos Scale! Starting setup..."));
// https://docs.arduino.cc/language-reference/en/variables/utilities/PROGMEM/
// // EEPROM
// EEPROM.begin(512);
bool _resume = false;
char command;
while (!_resume) {
// Ask if you want to calibrate, use values from memory, or use default values
clientPrintln(("Commands:"));
clientPrintln(("- Send 'c' to change calibration values of load cells(with taring)"));
clientPrintln(("- Send 'm' to load calibration values from EEPROM(with taring)"));
clientPrintln(("- Send 'd' to use default calibration values(without taring)"));
clientFlush();
command = get_command(true);
if (!to_continue(true)){
clientPrintln(("- Returned back. Use one of above commands"));
} else if (command == 'c'){
calibrate();
_resume = true;
} else if (command == 'm'){
tare();
loadCalibrationFromEEPROM();
_resume = true;
} else if (command == 'd'){
clientPrintln(("Using default calibration values."));
currentCalibrationValues();
_resume = true;
} else {
clientPrintln(("- INVALID command. Try again."));
}
clientFlush();
}
clientPrintln(("Setup complete. Ready for commands."));
clientPrintln(("***"));
sendLoopCommandHints();
// Serial.flush();
}
void printMemory() {
Serial.printf("Free Heap: %d\n", ESP.getFreeHeap());
Serial.printf("Max Free Block: %d\n", ESP.getMaxFreeBlockSize());
Serial.printf("Heap Fragmentation: %d%%\n", ESP.getHeapFragmentation());
}
void loop() {
printMemory();
Serial.printf("Stack used: %d\n", stackStart - ESP.getFreeContStack());
static uint32_t lastRestartCheck = 0;
if (millis() - lastRestartCheck > 60000) { // Every minute
lastRestartCheck = millis();
if (WiFi.status() != WL_CONNECTED) {
ESP.restart();
}
}
ESP.wdtFeed(); // Reset watchdog timer
handleClient();
static uint64_t number_of_read = 0;
if (client.available() > 0) {
char command = get_command(false);
if ((!is_command_special(command)) && (to_continue(true))){
if (command == 't') tare();
else if (command == 'c') calibrate();
else if (command == 'm') loadCalibrationFromEEPROM();
else {
clientPrintln(("UNKNOWN command"));
sendLoopCommandHints();
clientFlush();
}
}
}
is_scales_not_ready_warning(false);
clientPrint_uint64_t(millis());
clientPrint(" -- ");
clientPrint_uint64_t(number_of_read++);
clientPrint(" -- ");
sendCalibratedData(false);
clientFlush();
// yield(DELAY); // Yield to allow other tasks to run
delay(DELAY);
}
void clientPrint(const char* text){
// Handling WiFi connections
while (!client || !client.connected()) {
if (server.available()) {
client.println(text);
break;
} else {
Serial.println("Server does not respond");
}
}
}
void clientPrint_uint8_t(uint8_t number){
// Handling WiFi connections
while (!client || !client.connected()) {
if (server.available()) {
client.print(number);
break;
} else {
Serial.println("Server does not respond");
}
}
}
void clientPrint_float(float number){
// Handling WiFi connections
while (!client || !client.connected()) {
if (server.available()) {
client.print(number);
break;
} else {
Serial.println("Server does not respond");
}
}
}
void clientPrint_uint64_t(uint64_t number){
// Handling WiFi connections
while (!client || !client.connected()) {
if (server.available()) {
char buffer[21]; // Enough for 20 digits + null terminator
sprintf(buffer, "%llu", number); // For unsigned 64-bit
client.print(buffer);
// client.print(number);
break;
} else {
Serial.println("Server does not respond");
}
}
}
void clientPrintln_float(float number){
// Handling WiFi connections
while (!client || !client.connected()) {
if (server.available()) {
client.println(number);
break;
} else {
Serial.println("Server does not respond");
}
}
}
void clientPrintln(const char* text){
// Handling WiFi connections
while (!client || !client.connected()) {
if (server.available()) {
client.println(text);
// // Forced buffer sending
// client.flush();
break;
} else {
Serial.println("Server does not respond");
}
}
}
void clientFlush(){
// Handling WiFi connections
while (!client || !client.connected()) {
if (server.available()) {
// Forced buffer sending
client.flush();
break;
} else {
Serial.println("Server does not respond");
}
}
}
void configure_web_server(){
// Инициализация WiFi
// WiFi.begin(SSID, PASSWORD);
// // Настройка TCP Keepalive (для ESP8266)
// wifi_set_sleep_type(NONE_SLEEP_T); // Отключить энергосбережение
// ESP8266WiFiClass::setKeepAlive(1, 60); // 60 сек интервал
int status = WL_IDLE_STATUS;
Serial.println("WiFi connection...");
// // check for the presence of the shield:
// if (WiFi.status() == WL_NO_SHIELD) {
// Serial.println("WiFi shield not present");
// // don't continue:
// while (true);
// }
unsigned long start = millis();
Serial.printf("Connecting to %s/%s\n", SSID, PASSWORD);
status = WiFi.begin(SSID, PASSWORD);
// Connect to WPA/WPA2 network. Change this line if using open or WEP network:
// wait 10 seconds for connection:
while (WiFi.status() != WL_CONNECTED && millis() - start < 15000) {
delay(500);
ESP.wdtFeed(); // Important!
Serial.print("retrying...");
}
if (WiFi.status() != WL_CONNECTED) {
Serial.println("Failed to connect!");
ESP.restart();
} else {
Serial.print(("\nConnected to WIFI! IP_ADDRESS: "));
// Serial.println(WiFi.localIP().toString());
Serial.println(WiFi.localIP());
Serial.print(("nConnected! PORT_NUMBER: "));
Serial.println(wifiServerPort);
// IPAddress myAddress = WiFi.localIP();
// Serial.println(myAddress);
server.begin();
// print the SSID of the network you're attached to:
Serial.print("SSID: ");
Serial.println(WiFi.SSID());
// print your WiFi shield's IP address:
IPAddress ip = WiFi.localIP();
Serial.print("IP Address: ");
Serial.println(ip);
// print the received signal strength:
long rssi = WiFi.RSSI();
Serial.print("signal strength (RSSI):");
Serial.print(rssi);
Serial.println(" dBm");
}
}
void sendLoopCommandHints(){
clientPrintln(("Available commands:"));
clientPrintln(("- Send 'c' to change calibration values of load cells."));
clientPrintln(("- Send 't' to set tare offset for all load cells."));
clientPrintln(("- Send 'm' to load calibration values from EEPROM."));
clientPrintln(("- Send any key to show this hint."));
// clientFlush();
}
void is_scales_not_ready_warning(bool to_show_warning){
// wait for all the chips to become ready
// from the datasheet: When output data is not ready for retrieval, digital output pin DOUT is high. Serial clock
// input PD_SCK should be low. When DOUT goes to low, it indicates data is ready for retrieval.
uint8_t attempts;
while (!SCALES.is_ready()) {
attempts = 0;
while (!SCALES.is_ready() && attempts < 3){
// HX711MULTI.read or HX711MULTI.readRaw do not send warning that HX711 is not ready
if (to_show_warning){
clientPrintln(("WARNING: Some of load cells are busy or disconected."));
clientFlush();
}
delay(DELAY);
attempts++;
}
if (attempts >= 3) {
SCALES.power_down();
delay(DELAY);
SCALES.power_up();
}
}
}
bool is_command_special(char command){
bool result;
result = (command == '\n' || command == ' ' || command == '\r');
return result;
}
char get_command(bool block_specials){
clientPrintln("");
// if there are incoming bytes available
// from the server, read them and print them
while (!client.available());
char command;
while (client.available() > 0){
command = client.read();
// Wait for correct char
if ((block_specials) && (is_command_special(command))){
while(!client.available());
}
else {
// Skip other characters because correct command was get
while(client.available() > 0){
client.read();
};
}
}
// Send info message about user input if command is not special symbol
if (!is_command_special(command)){
clientPrint(("Your input: '"));
clientPrint(&command);
clientPrintln(("'."));
}
return command;
}
bool to_continue(bool question) {
if (question){
clientPrintln(("- Continue?('y' to continue or 'n' to return back):"));
clientFlush();
}
char command;
while (true){
command = get_command(true);
if (command == 'y'){
return true;
} else if (command == 'n'){
return false;
} else {
clientPrintln(("- INVALID input. Send 'y' or 'n'"));
clientFlush();
}
}
}
void sendCalibratedData(bool to_check_scales) {
if (to_check_scales){
is_scales_not_ready_warning(true);
}
// Do not use F() because of big number of calls(in loop function)
clientPrint("Current calibrated weights ");
SCALES.read(RESULTS);
for (uint8_t i=0; i < CHANNEL_COUNT; ++i) {
clientPrint_uint8_t(i + 1);
clientPrint(": ");
clientPrint_float(RESULTS[i] / CALIBRATION_VALUES[i]);
clientPrint("; ");
}
clientPrintln();
}
void currentCalibrationValues() {
clientPrintln(("Current calibration values"));
for (uint8_t i=0; i < CHANNEL_COUNT; ++i) {
clientPrint_uint8_t(i + 1);
clientPrint(": ");
clientPrint_float(CALIBRATION_VALUES[i]);
clientPrint("; ");
}
clientPrintln();
}
void tare() {
clientPrintln(("***"));
clientPrintln(("Start taring"));
currentCalibrationValues();
sendCalibratedData();
clientPrintln(("- Place the load cells an a level stable surface. Remove any load applied to the load cells. After that send 't' to set the tare offset."));
clientFlush();
bool _resume = false;
char command;
while(!_resume){
command = get_command(true);
if (!to_continue(true)){
clientPrintln(("- Returned back. Send 't' to continue."));
}
else if (command == 't'){
sendCalibratedData();
_resume = true;
} else {
clientPrintln(("- INVALID input. Send 't' to set the tare offset."));
}
clientFlush();
}
clientPrintln(("Taring..."));
is_scales_not_ready_warning();
bool tareSuccessful = false;
// Tare all load cells
uint32_t tareStartTime = millis();
while (!tareSuccessful && millis() < (tareStartTime + TARE_TIMEOUT_SECONDS*1000)) {
// clientPrintln("Before");
// if one of the cells fluctuated more than the ALLOWED TOLERANCE(still ringing), reject tare attempt;
// tareSuccessful = SCALES.tare(20, 10000);
tareSuccessful = SCALES.tare(20, 420);
}
if (tareSuccessful){
clientPrintln(("Tare successfully complete."));
sendCalibratedData();
} else {
clientPrintln(("FAILED: Timeout, check wiring and pin designations."));
}
clientPrintln(("***"));
clientFlush();
}
// Calibrate each load cell individually
void calibrate() {
clientPrintln(("***"));
clientPrintln(("Starting calibration..."));
tare();
uint8_t load_cell_number;
for (uint8_t i = 0; i < CHANNEL_COUNT; i++) {
load_cell_number = i + 1;
clientPrintln();
clientPrint(("Calibrating "));
clientPrint_uint8_t(load_cell_number);
clientPrintln((" load cell."));
// Wait for known weight input
float knownWeight = 0;
float calibrationValue = 1;
float calibratedWeight;
bool _resume = false;
while (!_resume) {
clientPrintln();
clientPrint(("- Place known weight(object) on load cell "));
clientPrint_uint8_t(load_cell_number);
clientPrintln((" and enter this weight (if your object is 1kg and you want to get data in grams then enter weight in grams, e.g., 1000, if you need kg - send 1, etc.)(known weight must be > 0): "));
clientFlush();
while (true) {
while(!client.available());
knownWeight = Serial.parseFloat();
if (knownWeight == 0){
clientPrintln(("- Known weight must be > 0. Send correct known weight"));
clientFlush();
} else {
break;
}
}
clientPrintln();
clientPrint(("Your knownWeight = "));
clientPrintln_float(knownWeight);
clientPrintln(("Calculation of calibrationValue(if calibrationValue correctly calculated then calibratedWeight = knownWeight):"));
// Measure raw data with known weight
is_scales_not_ready_warning();
SCALES.read(RESULTS);
int32_t notCalibratedWeight = RESULTS[i];
clientPrint(("notCalibratedWeight(from load cell) = "));
clientPrintln_float(notCalibratedWeight);
calibrationValue = notCalibratedWeight / knownWeight; // Calculate calibration value
clientPrint(("calibrationValue = notCalibratedWeight / knownWeight = "));
clientPrintln_float(calibrationValue);
clientPrint(("calibratedWeight = notCalibratedWeight / calibrationValue = "));
calibratedWeight = notCalibratedWeight / calibrationValue;
clientPrintln_float(calibratedWeight);
if (isnan(calibratedWeight)){
clientPrint(("FAILED: calibratedWeight is not a number(nan). Recheck that load cell "));
clientPrint_uint8_t(load_cell_number);
clientPrintln((" is connected, object is placed on load cell and you input its weight"));
} else if (to_continue(true)){
_resume = true;
}
clientFlush();
}
CALIBRATION_VALUES[i] = calibrationValue;
saveCalibrationToEEPROM(i, CALIBRATION_VALUES);
}
clientPrintln(("End calibration"));
currentCalibrationValues();
clientPrintln(("***"));
clientFlush();
delay(500);
}
void saveCalibrationToEEPROM(uint8_t i, float _calibrationValues[]) {
uint64_t address = CALIBRATION_EEPROM_ADDRESS + (i * sizeof(float));
bool _resume = false;
while (!_resume) {
clientPrint(("- Save "));
clientPrint_float(_calibrationValues[i]);
clientPrint((" value to EEPROM adress "));
clientPrint_uint64_t(address);
clientPrintln(("?(y/n)"));
clientFlush();
if (to_continue(false)){
// #if defined(ESP8266)|| defined(ESP32)
// EEPROM.begin(512);
// #endif
// Save calibration value to EEPROM
EEPROM.put(address, _calibrationValues[i]);
// #if defined(ESP8266)|| defined(ESP32)
// EEPROM.commit();
// #endif
EEPROM.get(address, _calibrationValues[i]);
clientPrint(("Value "));
clientPrint_float(_calibrationValues[i]);
clientPrint((" saved to EEPROM address: "));
clientPrint_uint64_t(address);
_resume = true;
} else {
clientPrintln(("Value not saved to EEPROM"));
_resume = true;
}
clientPrintln();
clientFlush();
}
}
void loadCalibrationFromEEPROM() {
for (uint8_t i = 0; i < CHANNEL_COUNT; i++) {
EEPROM.get(CALIBRATION_EEPROM_ADDRESS + (i * sizeof(float)), CALIBRATION_VALUES[i]);
clientPrint(("Loaded calibration value for load cell "));
clientPrint_uint8_t(i + 1);
clientPrint((": "));
clientPrintln_float(CALIBRATION_VALUES[i]);
clientFlush();
};
}
void handleClient(){
// wait for a new client:
client = server.available();
if (!client){
alreadyConnected = false;
Serial.print("Pls, connect to TCP server via 'telnet IP_ADDRESS PORT_NUMBER'.");
while (!client){
Serial.print("Waiting...");
client = server.available();
delay(1000);
}
// // Configuring Keepalive for the client
// client.setNoDelay(true);
// #ifdef ESP32
// client.setKeepAlive(1, 60);
// #endif
// #ifdef ESP8266
// int fd = client.getSocketNumber();
// int keepalive = 1;
// setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &keepalive, sizeof(keepalive));
// #endif
Serial.print("Client connected!");
}
if (!alreadyConnected) {
// clead out the input buffer:
client.flush();
Serial.println("We have a new client");
client.println("Hello, client!");
alreadyConnected = true;
}
}Loading
wemos-s2-mini
wemos-s2-mini