#include #include /* * Wemos d1 mini * Flash Size: 4M (1M SPIFFS) */ //Upload config: platformio run --target uploadfs #define PIN_VBAT A0 unsigned long vbat_calib1_adc=581; //adc value at vbat_calib_voltage (voltage at battery) float vbat_calib1_voltage=6.0; //voltage used for calibration unsigned long vbat_calib2_adc=782; //second calibration point (higher voltage) float vbat_calib2_voltage=8.0; int vbat_raw_filtered=-1; //-1 flags it as initialized bool vbatSent=false; bool weight_updated=false; unsigned long last_weightchange=10000; //give some headstart if startup takes a bit longer #include #define TM1637_CLK D5 #define TM1637_DIO D6 TM1637Display display(TM1637_CLK, TM1637_DIO); const uint8_t SEG_DONE[] = { SEG_B | SEG_C | SEG_D | SEG_E | SEG_G, // d SEG_A | SEG_B | SEG_C | SEG_D | SEG_E | SEG_F, // O SEG_C | SEG_E | SEG_G, // n SEG_A | SEG_D | SEG_E | SEG_F | SEG_G // E }; const uint8_t SEG_POINT[] = { SEG_DP}; uint8_t display_data[] = { 0xff, 0xff, 0xff, 0xff }; uint8_t display_blank[] = { 0x00, 0x00, 0x00, 0x00 }; uint8_t display_custom[] = { 0x00, 0x00, 0x00, 0x00 }; unsigned long last_displayupdate=0; #define DISPLAYUPDATEINTERVAL 100 //maximum time to update display #define DISPLAYUPDATEINTERVAL_MIN 10 //minimum display update time bool update_display=true; uint8_t displaybrightness = 7; //0 to 7 unsigned long last_new_display_custom=0; unsigned long display_custom_duration=1000*3; unsigned long last_display_blink=0; //for turning off shortly unsigned long display_blink_duration=100; #include "HX711.h" //#define SCALE_CALIBRATION 23805 //calibrated with 2.25kg weight. devide adc reading by calibration weight (in kg) to get this value (or other way around) #define SCALE_CALIBRATION 23960 //8 club mate 0.5L bottles weight 7.097kg. scale returns units=340090 with 16 bottles -> 340090/(2*7.097kg) = 23960 // HX711 circuit wiring const int LOADCELL_DOUT_PIN = D2; const int LOADCELL_SCK_PIN = D3; const int PIN_SELFENABLE = D1; HX711 scale; float weight_current=0; //last weight reading float weight_filtered=0; float spread=0; #define MEASURE_INTERVAL 100 //ms #define READING_FILTER_SIZE 40 //latency is about READING_FILTER_SIZE/2*MEASURE_INTERVAL float weight_read[READING_FILTER_SIZE] = {0}; uint8_t weight_read_pos=0; #define MEANVALUECOUNT 5 //0<= meanvaluecount < READING_FILTER_SIZE/2. how many values will be used from sorted weight array from the center region. abour double this values reading are used float weight_tare=0; //minimal filtered weight #define MIN_WEIGHT_DIFFERENCE 50 //minimum weight float weight_max=0; //max filtered weight bool weight_sent=false; unsigned long weight_sent_time=0; #define MAXONTIME 60000*2 //turn off after ms #define MQTT_SENDINTERVALL 500 //ms unsigned long last_mqtt_send=0; bool livesend=false; //if true, sends continuous data over mqtt #define FW_NAME "scale" #define FW_VERSION "0.0.1" void loopHandler(); HomieNode scaleNode("weight", "Scale", "scale"); //paramters: topic, $name, $type HomieNode displayNode("display", "Display", "scale"); //paramters: topic, $name, $type HomieNode hardwareNode("hardware", "Hardware", "scale"); //paramters: topic, $name, $type int sort_desc(const void *cmp1, const void *cmp2); float getFilteredWeight(); float getWeightSpread(); void sendWeight(float w); bool cmdHandler(const HomieRange& range, const String& value); bool displayNodeHandler(const HomieRange& range, const String& value); void powerOff(); void displayNumber(float numberdisplay); float getVBat(); void updateVBat(); void sendVBat(); float mapFloat(float x, float in_min, float in_max, float out_min, float out_max); void setup() { pinMode(PIN_SELFENABLE,OUTPUT); digitalWrite(PIN_SELFENABLE, HIGH); pinMode(LED_BUILTIN,OUTPUT); digitalWrite(LED_BUILTIN, HIGH); Serial.begin(115200); Serial.println("Hello"); display.setBrightness(displaybrightness, true); //brightness 0 to 7 Homie.disableResetTrigger(); //disable config reset if pin 1 (D3) is low on startup Homie_setFirmware(FW_NAME, FW_VERSION); Homie_setBrand(FW_NAME); Serial.println("setLoopFunction"); Homie.setLoopFunction(loopHandler); scaleNode.advertise("human"); scaleNode.advertise("spread"); scaleNode.advertise("raw"); scaleNode.advertise("max"); displayNode.advertise("segments").settable(displayNodeHandler); hardwareNode.advertise("cmd").settable(cmdHandler); //function inputHandler gets called on new message on topic/input/set hardwareNode.advertise("vbat"); hardwareNode.advertise("vbatraw"); Serial.println("homie setup"); Homie.setup(); scale.begin(LOADCELL_DOUT_PIN, LOADCELL_SCK_PIN); //calibration Serial.println("setup"); scale.set_scale(SCALE_CALIBRATION); delay(500); scale.tare(); delay(2000); Serial.println("tared. measuring..."); //after this taring put known weight on scale and get value from scale.get_units(10). then devide this value by the weight and use this number for set_scale(NUMBER) } void loop() { unsigned long loopmillis=millis(); static unsigned long last_measure=0; if (loopmillis>last_measure+MEASURE_INTERVAL) { last_measure=loopmillis; updateVBat(); //also update vbat reading //Serial.print("reading="); weight_current=0; if (scale.wait_ready_timeout(1000)) { //for non blocking mode weight_read_pos++; weight_read_pos%=READING_FILTER_SIZE; weight_current=scale.get_units(1); weight_read[weight_read_pos]=weight_current; //one reading takes 91ms } else { Serial.println("HX711 not found."); hardwareNode.setProperty("cmd").send("HX711 not found"); //can be done in main loop } weight_filtered=getFilteredWeight(); spread=getWeightSpread(); //Serial.println(weight_current); //Serial.print("spread="); Serial.println(spread,3); #define MAXSPREAD 0.2 //in kg if (spread10000) { //send voltage some time after turn on if (Homie.getMqttClient().connected()) { sendVBat(); vbatSent=true; }else{ Serial.println("Cannot send vbat because mqtt not connected!"); } } } if (Homie.getMqttClient().connected() && livesend && (millis()>last_mqtt_send+MQTT_SENDINTERVALL)) { last_mqtt_send=millis(); //float weight_filtered=getFilteredWeight(); float spread=getWeightSpread(); char charBuf[10]; dtostrf(weight_current,4, 3, charBuf); scaleNode.setProperty("raw").send(charBuf); dtostrf(spread,4, 3, charBuf); scaleNode.setProperty("spread").send(charBuf); dtostrf(weight_max-weight_tare,4, 3, charBuf); scaleNode.setProperty("max").send(charBuf); //filtered and auto tared } } int sort_desc(const void *cmp1, const void *cmp2) //compare function for qsort { float a = *((float *)cmp1); float b = *((float *)cmp2); return a > b ? -1 : (a < b ? 1 : 0); } float getFilteredWeight() { float copied_values[READING_FILTER_SIZE]; for(int i=0;i");Serial.print(i/8); Serial.print("="); Serial.println(number); number=0; //reset number for next byte } } } last_new_display_custom=millis(); //save time when message arrived return true; } void powerOff() { Serial.println("Turning Off"); Serial.flush(); delay(100); digitalWrite(PIN_SELFENABLE, LOW); } void displayNumber(float numberdisplay) { uint8_t displayresolution=3; //how many digits after dot bool _negative=false; if (numberdisplay<0) { numberdisplay*=-1; _negative=true; } if ((numberdisplay>999.9) | (displayresolution==0)) { display.showNumberDec((int)(numberdisplay+0.5), false); //just diplay number }else{ uint8_t d1=0; uint8_t d2=0; uint8_t d3=0; uint8_t d4=0; if(numberdisplay<10 && displayresolution>=3) { // 5.241, 0.005 etc. int _number=(int)(numberdisplay*1000+0.5); //in 1000th kg rounded d1=_number%10; d2=(_number/10)%10; d3=(_number/100)%10; d4=(_number/1000)%10; display_data[3] = display.encodeDigit(d1); //rightmost digit display_data[2] = display.encodeDigit(d2); display_data[1] = display.encodeDigit(d3); display_data[0] = display.encodeDigit(d4); //leftmost digit display_data[0] |= SEG_DP; //add decimal point after left most digit }else if(numberdisplay<100 && displayresolution>=2) { //10.24, 99.20 int _number=(int)(numberdisplay*100+0.5); //in 100th kg rounded d1=_number%10; d2=(_number/10)%10; d3=(_number/100)%10; d4=(_number/1000)%10; display_data[3] = display.encodeDigit(d1); //rightmost digit display_data[2] = display.encodeDigit(d2); display_data[1] = display.encodeDigit(d3); display_data[1] |= SEG_DP; //add decimal point after second digit from the left display_data[0] = display.encodeDigit(d4); //leftmost digit if (d4==0) { //number smaller than 1000 display_data[0]={0}; //turn off left segment } }else if (numberdisplay<1000 && displayresolution>=1) //100.0, 999.9 { int _number=(int)(numberdisplay*10+0.5); //in 10th kg rounded d1=_number%10; d2=(_number/10)%10; d3=(_number/100)%10; d4=(_number/1000)%10; display_data[3] = display.encodeDigit(d1); //rightmost digit display_data[2] = display.encodeDigit(d2); display_data[2] |= SEG_DP; //add decimal point after second digit from the right display_data[1] = display.encodeDigit(d3); display_data[0] = display.encodeDigit(d4); //leftmost digit if (d4==0) { //number smaller than 1000 display_data[0]={0}; //turn off left segment if (d3==0) { //number smaller than 100 display_data[1]={0}; //turn off 2nd from left segment } } } if (_negative) { //show negative number by using rightmost dot display_data[3] |= SEG_DP; } } } float getVBat() { //get filtered vbat value return mapFloat(vbat_raw_filtered,vbat_calib1_adc,vbat_calib2_adc, vbat_calib1_voltage,vbat_calib2_voltage); //2-point mapping adc -> voltage; } void updateVBat() { //continuous update for filtering float vbat_filter=0.95; //the closer to 1 the higher the filtering. 0 means no filtering if (vbat_raw_filtered<0) { //at first reading (vbat_raw_filtered is initialized with value <0) vbat_filter=0; //use first reading with 100% } vbat_raw_filtered=vbat_raw_filtered*vbat_filter + analogRead(PIN_VBAT)*(1.0-vbat_filter); } void sendVBat() { char charBuf[10]; dtostrf(vbat_raw_filtered,1, 0, charBuf); hardwareNode.setProperty("vbatraw").send(charBuf); Serial.print("vbatraw="); Serial.println(vbat_raw_filtered); dtostrf(getVBat(),4, 3, charBuf); hardwareNode.setProperty("vbat").send(charBuf); Serial.print("vbat="); Serial.println(getVBat(),3); } float mapFloat(float x, float in_min, float in_max, float out_min, float out_max) { return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; }