- Set B = b * 10^6 for raw to absolute weight conversion: y=a*x+b
\*********************************************************************************************/
bool HxCommand(void) {
bool serviced = true;
bool show_parms = true;
for (uint32_t ca = 0; ca < XdrvMailbox.data_len; ca++) {
if ((' ' == XdrvMailbox.data[ca]) || ('=' == XdrvMailbox.data[ca])) { XdrvMailbox.data[ca] = ','; }
}
bool any_value = (ArgC() > 1);
long value;
char argument[32];
if (any_value) {
value = strtol(ArgV(argument, 2), nullptr, 10);
}
switch (XdrvMailbox.payload) {
case 1: // Reset scale
if (0 == Settings->weight_user_tare) {
Hx->tare_flg = true;
Response_P(S_JSON_SENSOR_INDEX_SVALUE, XSNS_34, "Reset");
}
show_parms = false;
break;
case 2: // Calibrate in gram and precision 1 to 10
if (any_value) {
Settings->weight_reference = (value && (value < (Settings->weight_max * 1000))) ? value : HX_REFERENCE;
if (ArgC() > 2) {
value = strtol(ArgV(argument, 3), nullptr, 10);
Settings->weight_precision = (value && (value <= 20)) ? value : HX711_CAL_PRECISION;
}
}
Hx->scale = 1; // Uncalibrated
Hx->sample_count = 0;
Hx->offset = 0; // Disable tare while calibrating
Hx->calibrate_step = HX_CAL_START;
Hx->calibrate_timer = 1;
// HxCalibrationStateTextJson(HX_MSG_CAL_REMOVE);
HxCalibrationStateTextJson(HX_MSG_CAL_REFERENCE);
show_parms = false;
break;
case 3: // WeightRef to user reference in gram
if (any_value) {
Settings->weight_reference = (value && (value < (Settings->weight_max * 1000))) ? value : HX_REFERENCE;
}
break;
case 4: // WeightCal to user calculated value
if (any_value) {
Settings->weight_calibration = value; // Allow zero for re-init
Hx->scale = (value) ? Settings->weight_calibration : 1; // Fix divide by zero
}
break;
case 5: // WeightMax in kg
if (any_value) {
Settings->weight_max = (value) ? value : HX_MAX_WEIGHT / 1000;
}
break;
case 6: // WeightItem in gram
if (any_value) {
Settings->weight_item = (unsigned long)(CharToFloat(ArgV(argument, 2)) * 10);
}
break;
// case 7: // WeightSave
// Settings->energy_frequency_calibration = Hx->weight;
// Response_P(S_JSON_SENSOR_INDEX_SVALUE, XSNS_34, PSTR(D_JSON_DONE));
// show_parms = false;
// break;
case 8: // Json on weight change
if (any_value) {
Settings->SensorBits1.hx711_json_weight_change = value &1;
}
break;
case 9: // WeightDelta
if (any_value) {
Settings->weight_change = value;
SetWeightDelta();
}
break;
case 10: // Fixed (user) tare
if (any_value) {
Settings->weight_user_tare = (1 == value) ? Hx->raw : value;
HxTareInit();
Hx->weight_diff = Hx->weight_delta +1; // Force display of current weight
}
break;
case 11: // AbsoluteConversion, A
if (any_value) {
Settings->weight_absconv_a = value;
}
break;
case 12: // AbsoluteConversion, B
if (any_value) {
Settings->weight_absconv_b = value;
}
break;
}
if (show_parms) {
char item[33];
dtostrfd((float)Settings->weight_item / 10, 1, item);
Response_P(PSTR("{\"Sensor34\":{\""
D_JSON_WEIGHT_REF "\":%d,\"" D_JSON_WEIGHT_CAL "\":%d,\"" D_JSON_WEIGHT_PREC "\":%d,\""
D_JSON_WEIGHT_MAX "\":%d,\""
D_JSON_WEIGHT_ITEM "\":%s,\"" D_JSON_WEIGHT_CHANGE "\":\"%s\",\"" D_JSON_WEIGHT_DELTA "\":%d,\""
D_JSON_WEIGHT_TARE "\":%d,\"" D_JSON_WEIGHT_ABSC_A "\":%d,\"" D_JSON_WEIGHT_ABSC_B "\":%d}}"),
Settings->weight_reference, Settings->weight_calibration, Settings->weight_precision,
Settings->weight_max * 1000,
item, GetStateText(Settings->SensorBits1.hx711_json_weight_change), Settings->weight_change,
Settings->weight_user_tare, Settings->weight_absconv_a, Settings->weight_absconv_b);
}
return serviced;
}
/*********************************************************************************************/
long HxWeight(void) {
return (Hx->calibrate_step < HX_CAL_FAIL) ? Hx->weight : 0;
}
void HxInit(void) {
uint32_t hx711_config = (PinUsed(GPIO_HX711_DAT) && PinUsed(GPIO_HX711_SCK)) ? 1 : 0;
#if defined(USE_I2C) && defined(USE_HX711_M5SCALES)
uint32_t bus;
if (!hx711_config && I2cEnabled(XI2C_89)) {
for (bus = 0; bus < 2; bus++) {
if (!I2cSetDevice(HX_SCALES_ADDR, bus)) { continue; }
I2cSetActiveFound(HX_SCALES_ADDR, "M5Unit Scales", bus);
hx711_config = 2;
break;
}
}
#endif // USE_I2C && USE_HX711_M5SCALES
if (hx711_config > 0) {
Hx = (Hx_t*)calloc(sizeof(Hx_t), 1); // Need calloc to reset registers to 0/false
if (nullptr == Hx) { return; }
// Hx->calibrate_step = HX_CAL_END; // HX_CAL_END = 0
#if defined(USE_I2C) && defined(USE_HX711_M5SCALES)
if (2 == hx711_config) {
Hx->pin_sck = bus; // If both are equal use M5Scale instead of HX711
Hx->pin_dout = bus;
} else {
#endif // USE_I2C && USE_HX711_M5SCALES
Hx->pin_sck = Pin(GPIO_HX711_SCK);
Hx->pin_dout = Pin(GPIO_HX711_DAT);
pinMode(Hx->pin_sck, OUTPUT);
pinMode(Hx->pin_dout, INPUT);
digitalWrite(Hx->pin_sck, LOW);
#if defined(USE_I2C) && defined(USE_HX711_M5SCALES)
}
#endif // USE_I2C && USE_HX711_M5SCALES
SetWeightDelta();
if (HxIsReady(8 * HX_TIMEOUT)) { // Could take 600 milliseconds after power on
if (!Settings->weight_max) { Settings->weight_max = HX_MAX_WEIGHT / 1000; }
if (!Settings->weight_precision) { Settings->weight_precision = HX711_CAL_PRECISION; }
if (!Settings->weight_calibration) { Settings->weight_calibration = HX_SCALE * Settings->weight_precision; }
if (!Settings->weight_reference) { Settings->weight_reference = HX_REFERENCE; }
Hx->scale = Settings->weight_calibration;
HxTareInit();
HxRead();
} else {
free(Hx);
Hx = nullptr;
}
}
}
void HxEvery100mSecond(void) {
long raw = HxRead();
if (-1 == raw) { return; }
if (Hx->sample_count < HX_SAMPLES) { // Test for HxSaveBeforeRestart()
Hx->reads[Hx->sample_count] = raw;
}
Hx->sample_count++;
if (HX_SAMPLES == Hx->sample_count) {
Hx->sample_count = 0;
// Sort HX_SAMPLES
for (uint32_t i = 0; i < HX_SAMPLES; i++) {
for (uint32_t j = i + 1; j < HX_SAMPLES; j++) {
if (Hx->reads[j] > Hx->reads[i]) {
std::swap(Hx->reads[i], Hx->reads[j]);
}
}
}
// Drop two lows and two highs from average
long sum_raw = 0;
for (uint32_t i = 2; i < HX_SAMPLES -2; i++) {
sum_raw += Hx->reads[i];
}
Hx->raw_absolute = (sum_raw / (HX_SAMPLES -4)) * Settings->weight_precision; // Uncalibrated value
Hx->raw = Hx->raw_absolute / Hx->scale; // grams
if ((0 == Settings->weight_user_tare) && Hx->tare_flg) { // Reset scale based on current load
Hx->tare_flg = false;
Settings->weight_offset = Hx->raw_absolute; // Save for restart use
Hx->offset = Hx->raw_absolute;
}
long value = Hx->raw_absolute - Hx->offset; // Uncalibrated value
Hx->weight = value / Hx->scale; // grams
if (Hx->weight < 0) { // We currently do not support negative weight
Hx->weight = 0;
}
if (Hx->calibrate_step) {
Hx->calibrate_timer--;
// AddLog(LOG_LEVEL_DEBUG, PSTR("HX7: Step %d, weight %d, last %d, raw %d, empty %d"), Hx->calibrate_step, Hx->weight, Hx->last_weight, Hx->raw, Hx->raw_empty);
if (HX_CAL_START == Hx->calibrate_step) { // Skip reset just initiated
if (0 == Hx->offset) {
Hx->calibrate_step--; // HX_CAL_RESET
Hx->last_weight = Hx->weight; // Uncalibrated value
Hx->raw_empty = Hx->raw;
}
Hx->calibrate_timer = HX_CAL_TIMEOUT * (10 / HX_SAMPLES);
}
else if (HX_CAL_RESET == Hx->calibrate_step) { // Wait for stable reset
if (Hx->calibrate_timer) {
if (Hx->weight < Hx->last_weight -100) { // Load decrease detected
Hx->last_weight = Hx->weight;
Hx->raw_empty = Hx->raw;
// HxCalibrationStateTextJson(HX_MSG_CAL_REFERENCE);
}
else if (Hx->weight > Hx->last_weight +100) { // Load increase detected
Hx->calibrate_step--; // HX_CAL_FIRST
Hx->calibrate_timer = HX_CAL_TIMEOUT * (10 / HX_SAMPLES);
}
} else {
Hx->calibrate_step = HX_CAL_FAIL;
}
}
else if (HX_CAL_FIRST == Hx->calibrate_step) { // Wait for first reference weight
if (Hx->calibrate_timer) {
if (Hx->weight > Hx->last_weight +100) {
Hx->calibrate_step--; // HX_CAL_DONE
}
} else {
Hx->calibrate_step = HX_CAL_FAIL;
}
}
else if (HX_CAL_DONE == Hx->calibrate_step) { // Second stable reference weight
if (Hx->weight > Hx->last_weight +100) {
Hx->calibrate_step = HX_CAL_FINISH; // Calibration done
Settings->weight_offset = Hx->raw_empty;
Hx->offset = Hx->raw_empty;
Settings->weight_calibration = (Hx->weight - Hx->raw_empty) / Settings->weight_reference; // 1 gram
Hx->weight = 0; // Reset calibration value
HxCalibrationStateTextJson(HX_MSG_CAL_DONE);
} else {
Hx->calibrate_step = HX_CAL_FAIL;
}
}
if (HX_CAL_FAIL == Hx->calibrate_step) { // Calibration failed
Hx->calibrate_step--; // HX_CAL_FINISH
HxTareInit();
HxCalibrationStateTextJson(HX_MSG_CAL_FAIL);
}
if (HX_CAL_FINISH == Hx->calibrate_step) { // Calibration finished
Hx->calibrate_step--; // HX_CAL_LIMBO
Hx->calibrate_timer = 3 * (10 / HX_SAMPLES);
Hx->scale = Settings->weight_calibration;
if (Settings->weight_user_tare != 0) { // Re-enable fixed tare if needed
Settings->weight_user_tare = Hx->raw_empty / Hx->scale;
HxTareInit();
}
}
if (!Hx->calibrate_timer) {
Hx->calibrate_step = HX_CAL_END; // End of calibration
Hx->weight_diff = Hx->weight_delta +2;
}
} else {
if (Settings->SensorBits1.hx711_json_weight_change) {
if (abs(Hx->weight - Hx->weight_diff) > Hx->weight_delta) { // Use weight_delta threshold to decrease "ghost" weights
Hx->weight_diff = Hx->weight;
Hx->weight_changed = true;
}
else if (Hx->weight_changed && (abs(Hx->weight - Hx->weight_diff) < Hx->weight_delta)) {
ResponseClear();
ResponseAppendTime();
HxShow(true);
ResponseJsonEnd();
MqttPublishTeleSensor();
Hx->weight_changed = false;
}
}
}
}
}
void HxSaveBeforeRestart(void) {
Hx->sample_count = HX_SAMPLES +1; // Stop updating Hx->weight
}
#ifdef USE_WEBSERVER
const char HTTP_HX711_WEIGHT[] PROGMEM =
"{s}HX711 " D_WEIGHT "{m}%s " D_UNIT_KILOGRAM "{e}"; // {s} = | , {m} = | , {e} = |
const char HTTP_HX711_COUNT[] PROGMEM =
"{s}HX711 " D_COUNT "{m}%d{e}";
const char HTTP_HX711_CAL[] PROGMEM =
"{s}HX711 %s{m}{e}";
#endif // USE_WEBSERVER
void HxShow(bool json) {
char scount[30] = { 0 };
uint16_t count = 0;
float weight = 0;
if (Hx->calibrate_step < HX_CAL_FAIL) {
if ((Settings->weight_absconv_a != 0) && (Settings->weight_absconv_b != 0)) {
weight = (float)Settings->weight_absconv_a / 1e9 * Hx->raw_absolute + (float)Settings->weight_absconv_b / 1e6;
}
else {
if (Hx->weight && Settings->weight_item) {
count = (Hx->weight * 10) / Settings->weight_item;
if (count > 1) {
snprintf_P(scount, sizeof(scount), PSTR(",\"" D_JSON_COUNT "\":%d"), count);
}
}
weight = (float)Hx->weight / 1000; // kilograms
}
}
char weight_chr[33];
dtostrfd(weight, Settings->flag2.weight_resolution, weight_chr);
if (json) {
ResponseAppend_P(PSTR(",\"HX711\":{\"" D_JSON_WEIGHT "\":%s%s,\"" D_JSON_WEIGHT_RAW "\":%d,\"" D_JSON_WEIGHT_RAW_ABS "\":%d}"),
weight_chr, scount, Hx->raw, Hx->raw_absolute);
#ifdef USE_WEBSERVER
} else {
WSContentSend_PD(HTTP_HX711_WEIGHT, weight_chr);
if (count > 1) {
WSContentSend_PD(HTTP_HX711_COUNT, count);
}
if (Hx->calibrate_step) {
char cal_text[30];
WSContentSend_PD(HTTP_HX711_CAL, GetTextIndexed(cal_text, sizeof(cal_text), Hx->calibrate_msg, kHxCalibrationStates));
}
#endif // USE_WEBSERVER
}
}
#ifdef USE_WEBSERVER
#ifdef USE_HX711_GUI
/*********************************************************************************************\
* Optional GUI
\*********************************************************************************************/
#define WEB_HANDLE_HX711 "s34"
const char HTTP_BTN_MENU_MAIN_HX711[] PROGMEM =
"";
const char HTTP_BTN_MENU_HX711[] PROGMEM =
"";
const char HTTP_FORM_HX711a[] PROGMEM =
""
"
";
const char HTTP_FORM_HX711b[] PROGMEM =
"