xsns_34_hx711.ino 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513
  1. /*
  2. xsns_34_hx711.ino - HX711 load cell support for Sonoff-Tasmota
  3. Copyright (C) 2018 Theo Arends
  4. This program is free software: you can redistribute it and/or modify
  5. it under the terms of the GNU General Public License as published by
  6. the Free Software Foundation, either version 3 of the License, or
  7. (at your option) any later version.
  8. This program is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. GNU General Public License for more details.
  12. You should have received a copy of the GNU General Public License
  13. along with this program. If not, see <http://www.gnu.org/licenses/>.
  14. */
  15. #ifdef USE_HX711
  16. /*********************************************************************************************\
  17. * HX711 - Load cell as used in a scale
  18. *
  19. * Source: Sparkfun and https://github.com/bogde/HX711
  20. *
  21. * To reset the scale:
  22. * - Execute command Sensor34 1
  23. *
  24. * To calibrate the scale perform the following tasks:
  25. * - Set reference weight once using command Sensor34 3 <reference weight in gram>
  26. * - Remove any weight from the scale
  27. * - Execute command Sensor34 2 and follow messages shown
  28. \*********************************************************************************************/
  29. #define XSNS_34 34
  30. #ifndef HX_MAX_WEIGHT
  31. #define HX_MAX_WEIGHT 20000 // Default max weight in gram
  32. #endif
  33. #ifndef HX_REFERENCE
  34. #define HX_REFERENCE 250 // Default reference weight for calibration in gram
  35. #endif
  36. #ifndef HX_SCALE
  37. #define HX_SCALE 120 // Default result of measured weight / reference weight when scale is 1
  38. #endif
  39. #define HX_TIMEOUT 120 // A reading at default 10Hz (pin RATE to Gnd on HX711) can take up to 100 milliseconds
  40. #define HX_SAMPLES 10 // Number of samples for average calculation
  41. #define HX_CAL_TIMEOUT 15 // Calibration step window in number of seconds
  42. #define HX_GAIN_128 1 // Channel A, gain factor 128
  43. #define HX_GAIN_32 2 // Channel B, gain factor 32
  44. #define HX_GAIN_64 3 // Channel A, gain factor 64
  45. #define D_JSON_WEIGHT_REF "WeightRef"
  46. #define D_JSON_WEIGHT_CAL "WeightCal"
  47. #define D_JSON_WEIGHT_MAX "WeightMax"
  48. #define D_JSON_WEIGHT_ITEM "WeightItem"
  49. enum HxCalibrationSteps { HX_CAL_END, HX_CAL_LIMBO, HX_CAL_FINISH, HX_CAL_FAIL, HX_CAL_DONE, HX_CAL_FIRST, HX_CAL_RESET, HX_CAL_START };
  50. const char kHxCalibrationStates[] PROGMEM = D_HX_CAL_FAIL "|" D_HX_CAL_DONE "|" D_HX_CAL_REFERENCE "|" D_HX_CAL_REMOVE;
  51. long hx_weight = 0;
  52. long hx_sum_weight = 0;
  53. long hx_offset = 0;
  54. long hx_scale = 1;
  55. uint8_t hx_type = 1;
  56. uint8_t hx_sample_count = 0;
  57. uint8_t hx_tare_flg = 0;
  58. uint8_t hx_calibrate_step = HX_CAL_END;
  59. uint8_t hx_calibrate_timer = 0;
  60. uint8_t hx_calibrate_msg = 0;
  61. uint8_t hx_pin_sck;
  62. uint8_t hx_pin_dout;
  63. /*********************************************************************************************/
  64. bool HxIsReady(uint16_t timeout)
  65. {
  66. // A reading can take up to 100 mS or 600mS after power on
  67. uint32_t start = millis();
  68. while ((digitalRead(hx_pin_dout) == HIGH) && (millis() - start < timeout)) { yield(); }
  69. return (digitalRead(hx_pin_dout) == LOW);
  70. }
  71. long HxRead()
  72. {
  73. if (!HxIsReady(HX_TIMEOUT)) { return -1; }
  74. uint8_t data[3] = { 0 };
  75. uint8_t filler = 0x00;
  76. // pulse the clock pin 24 times to read the data
  77. data[2] = shiftIn(hx_pin_dout, hx_pin_sck, MSBFIRST);
  78. data[1] = shiftIn(hx_pin_dout, hx_pin_sck, MSBFIRST);
  79. data[0] = shiftIn(hx_pin_dout, hx_pin_sck, MSBFIRST);
  80. // set the channel and the gain factor for the next reading using the clock pin
  81. for (unsigned int i = 0; i < HX_GAIN_128; i++) {
  82. digitalWrite(hx_pin_sck, HIGH);
  83. digitalWrite(hx_pin_sck, LOW);
  84. }
  85. // Replicate the most significant bit to pad out a 32-bit signed integer
  86. if (data[2] & 0x80) { filler = 0xFF; }
  87. // Construct a 32-bit signed integer
  88. unsigned long value = ( static_cast<unsigned long>(filler) << 24
  89. | static_cast<unsigned long>(data[2]) << 16
  90. | static_cast<unsigned long>(data[1]) << 8
  91. | static_cast<unsigned long>(data[0]) );
  92. return static_cast<long>(value);
  93. }
  94. /*********************************************************************************************/
  95. void HxReset(void)
  96. {
  97. hx_tare_flg = 1;
  98. hx_sum_weight = 0;
  99. hx_sample_count = 0;
  100. }
  101. void HxCalibrationStateTextJson(uint8_t msg_id)
  102. {
  103. char cal_text[30];
  104. hx_calibrate_msg = msg_id;
  105. snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_SENSOR_INDEX_SVALUE, XSNS_34, GetTextIndexed(cal_text, sizeof(cal_text), hx_calibrate_msg, kHxCalibrationStates));
  106. if (msg_id < 3) { MqttPublishPrefixTopic_P(RESULT_OR_STAT, PSTR("Sensor34")); }
  107. }
  108. /*********************************************************************************************\
  109. * Supported commands for Sensor34:
  110. *
  111. * Sensor34 1 - Reset display to 0
  112. * Sensor34 2 - Start calibration
  113. * Sensor34 2 <weight in gram> - Set reference weight and start calibration
  114. * Sensor34 3 - Show reference weight in gram
  115. * Sensor34 3 <weight in gram> - Set reference weight
  116. * Sensor34 4 - Show calibrated scale value
  117. * Sensor34 4 <scale value> - Set calibrated scale value
  118. * Sensor34 5 - Show max weigth in gram
  119. * Sensor34 5 <weight in gram> - Set max weight
  120. * Sensor34 6 - Show item weigth in decigram
  121. * Sensor34 6 <weight in decigram> - Set item weight
  122. \*********************************************************************************************/
  123. bool HxCommand(void)
  124. {
  125. bool serviced = true;
  126. bool show_parms = false;
  127. char sub_string[XdrvMailbox.data_len +1];
  128. for (byte ca = 0; ca < XdrvMailbox.data_len; ca++) {
  129. if ((' ' == XdrvMailbox.data[ca]) || ('=' == XdrvMailbox.data[ca])) { XdrvMailbox.data[ca] = ','; }
  130. }
  131. switch (XdrvMailbox.payload) {
  132. case 1: // Reset scale
  133. HxReset();
  134. snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_SENSOR_INDEX_SVALUE, XSNS_34, "Reset");
  135. break;
  136. case 2: // Calibrate
  137. if (strstr(XdrvMailbox.data, ",")) {
  138. Settings.weight_reference = strtol(subStr(sub_string, XdrvMailbox.data, ",", 2), NULL, 10);
  139. }
  140. hx_scale = 1;
  141. HxReset();
  142. hx_calibrate_step = HX_CAL_START;
  143. hx_calibrate_timer = 1;
  144. HxCalibrationStateTextJson(3);
  145. break;
  146. case 3: // WeightRef to user reference
  147. if (strstr(XdrvMailbox.data, ",")) {
  148. Settings.weight_reference = strtol(subStr(sub_string, XdrvMailbox.data, ",", 2), NULL, 10);
  149. }
  150. show_parms = true;
  151. break;
  152. case 4: // WeightCal to user calculated value
  153. if (strstr(XdrvMailbox.data, ",")) {
  154. Settings.weight_calibration = strtol(subStr(sub_string, XdrvMailbox.data, ",", 2), NULL, 10);
  155. hx_scale = Settings.weight_calibration;
  156. }
  157. show_parms = true;
  158. break;
  159. case 5: // WeightMax
  160. if (strstr(XdrvMailbox.data, ",")) {
  161. Settings.weight_max = strtol(subStr(sub_string, XdrvMailbox.data, ",", 2), NULL, 10) / 1000;
  162. }
  163. show_parms = true;
  164. break;
  165. case 6: // WeightItem
  166. if (strstr(XdrvMailbox.data, ",")) {
  167. Settings.weight_item = (unsigned long)(CharToDouble(subStr(sub_string, XdrvMailbox.data, ",", 2)) * 10);
  168. }
  169. show_parms = true;
  170. break;
  171. default:
  172. serviced = false;
  173. }
  174. if (show_parms) {
  175. char item[33];
  176. dtostrfd((float)Settings.weight_item / 10, 1, item);
  177. snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{\"Sensor34\":{\"" D_JSON_WEIGHT_REF "\":%d,\"" D_JSON_WEIGHT_CAL "\":%d,\"" D_JSON_WEIGHT_MAX "\":%d,\"" D_JSON_WEIGHT_ITEM "\":%s}}"),
  178. Settings.weight_reference, Settings.weight_calibration, Settings.weight_max * 1000, item);
  179. }
  180. return serviced;
  181. }
  182. /*********************************************************************************************/
  183. long HxWeight()
  184. {
  185. return (hx_calibrate_step < HX_CAL_FAIL) ? hx_weight : 0;
  186. }
  187. void HxInit(void)
  188. {
  189. hx_type = 0;
  190. if ((pin[GPIO_HX711_DAT] < 99) && (pin[GPIO_HX711_SCK] < 99)) {
  191. hx_pin_sck = pin[GPIO_HX711_SCK];
  192. hx_pin_dout = pin[GPIO_HX711_DAT];
  193. pinMode(hx_pin_sck, OUTPUT);
  194. pinMode(hx_pin_dout, INPUT);
  195. digitalWrite(hx_pin_sck, LOW);
  196. if (HxIsReady(8 * HX_TIMEOUT)) { // Can take 600 milliseconds after power on
  197. if (!Settings.weight_max) { Settings.weight_max = HX_MAX_WEIGHT / 1000; }
  198. if (!Settings.weight_calibration) { Settings.weight_calibration = HX_SCALE; }
  199. if (!Settings.weight_reference) { Settings.weight_reference = HX_REFERENCE; }
  200. hx_scale = Settings.weight_calibration;
  201. HxRead();
  202. HxReset();
  203. hx_type = 1;
  204. }
  205. }
  206. }
  207. void HxEvery100mSecond(void)
  208. {
  209. hx_sum_weight += HxRead();
  210. hx_sample_count++;
  211. if (HX_SAMPLES == hx_sample_count) {
  212. long average = hx_sum_weight / hx_sample_count; // grams
  213. long value = average - hx_offset; // grams
  214. hx_weight = value / hx_scale; // grams
  215. if (hx_weight < 0) { hx_weight = 0; }
  216. if (hx_tare_flg) {
  217. hx_tare_flg = 0;
  218. hx_offset = average; // grams
  219. }
  220. if (hx_calibrate_step) {
  221. hx_calibrate_timer--;
  222. if (HX_CAL_START == hx_calibrate_step) { // Skip reset just initiated
  223. hx_calibrate_step--;
  224. hx_calibrate_timer = HX_CAL_TIMEOUT * (10 / HX_SAMPLES);
  225. }
  226. else if (HX_CAL_RESET == hx_calibrate_step) { // Wait for stable reset
  227. if (hx_calibrate_timer) {
  228. if (hx_weight < Settings.weight_reference) {
  229. hx_calibrate_step--;
  230. hx_calibrate_timer = HX_CAL_TIMEOUT * (10 / HX_SAMPLES);
  231. HxCalibrationStateTextJson(2);
  232. }
  233. } else {
  234. hx_calibrate_step = HX_CAL_FAIL;
  235. }
  236. }
  237. else if (HX_CAL_FIRST == hx_calibrate_step) { // Wait for first reference weight
  238. if (hx_calibrate_timer) {
  239. if (hx_weight > Settings.weight_reference) {
  240. hx_calibrate_step--;
  241. }
  242. } else {
  243. hx_calibrate_step = HX_CAL_FAIL;
  244. }
  245. }
  246. else if (HX_CAL_DONE == hx_calibrate_step) { // Second stable reference weight
  247. if (hx_weight > Settings.weight_reference) {
  248. hx_calibrate_step = HX_CAL_FINISH; // Calibration done
  249. Settings.weight_calibration = hx_weight / Settings.weight_reference;
  250. hx_weight = 0; // Reset calibration value
  251. HxCalibrationStateTextJson(1);
  252. } else {
  253. hx_calibrate_step = HX_CAL_FAIL;
  254. }
  255. }
  256. if (HX_CAL_FAIL == hx_calibrate_step) { // Calibration failed
  257. hx_calibrate_step--;
  258. hx_tare_flg = 1; // Perform a reset using old scale
  259. HxCalibrationStateTextJson(0);
  260. }
  261. if (HX_CAL_FINISH == hx_calibrate_step) { // Calibration finished
  262. hx_calibrate_step--;
  263. hx_calibrate_timer = 3 * (10 / HX_SAMPLES);
  264. hx_scale = Settings.weight_calibration;
  265. }
  266. if (!hx_calibrate_timer) {
  267. hx_calibrate_step = HX_CAL_END; // End of calibration
  268. }
  269. }
  270. hx_sum_weight = 0;
  271. hx_sample_count = 0;
  272. }
  273. }
  274. #ifdef USE_WEBSERVER
  275. const char HTTP_HX711_WEIGHT[] PROGMEM = "%s"
  276. "{s}HX711 " D_WEIGHT "{m}%s " D_UNIT_KILOGRAM "{e}"; // {s} = <tr><th>, {m} = </th><td>, {e} = </td></tr>
  277. const char HTTP_HX711_COUNT[] PROGMEM = "%s"
  278. "{s}HX711 " D_COUNT "{m}%d{e}";
  279. const char HTTP_HX711_CAL[] PROGMEM = "%s"
  280. "{s}HX711 %s{m}{e}";
  281. #endif // USE_WEBSERVER
  282. void HxShow(boolean json)
  283. {
  284. char scount[30] = { 0 };
  285. uint16_t count = 0;
  286. float weight = 0;
  287. if (hx_calibrate_step < HX_CAL_FAIL) {
  288. if (hx_weight && Settings.weight_item) {
  289. count = (hx_weight * 10) / Settings.weight_item;
  290. if (count > 1) {
  291. snprintf_P(scount, sizeof(scount), PSTR(",\"" D_JSON_COUNT "\":%d"), count);
  292. }
  293. }
  294. weight = (float)hx_weight / 1000; // kilograms
  295. }
  296. char weight_chr[33];
  297. dtostrfd(weight, Settings.flag2.weight_resolution, weight_chr);
  298. if (json) {
  299. snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s,\"HX711\":{\"" D_JSON_WEIGHT "\":%s%s}"), mqtt_data, weight_chr, scount);
  300. #ifdef USE_WEBSERVER
  301. } else {
  302. snprintf_P(mqtt_data, sizeof(mqtt_data), HTTP_HX711_WEIGHT, mqtt_data, weight_chr);
  303. if (count > 1) {
  304. snprintf_P(mqtt_data, sizeof(mqtt_data), HTTP_HX711_COUNT, mqtt_data, count);
  305. }
  306. if (hx_calibrate_step) {
  307. char cal_text[30];
  308. snprintf_P(mqtt_data, sizeof(mqtt_data), HTTP_HX711_CAL, mqtt_data, GetTextIndexed(cal_text, sizeof(cal_text), hx_calibrate_msg, kHxCalibrationStates));
  309. }
  310. #endif // USE_WEBSERVER
  311. }
  312. }
  313. #ifdef USE_WEBSERVER
  314. #ifdef USE_HX711_GUI
  315. /*********************************************************************************************\
  316. * Optional GUI
  317. \*********************************************************************************************/
  318. #define WEB_HANDLE_HX711 "s34"
  319. const char S_CONFIGURE_HX711[] PROGMEM = D_CONFIGURE_HX711;
  320. const char HTTP_BTN_MENU_MAIN_HX711[] PROGMEM =
  321. "<br/><form action='" WEB_HANDLE_HX711 "' method='get'><button name='reset'>" D_RESET_HX711 "</button></form>";
  322. const char HTTP_BTN_MENU_HX711[] PROGMEM =
  323. "<br/><form action='" WEB_HANDLE_HX711 "' method='get'><button>" D_CONFIGURE_HX711 "</button></form>";
  324. const char HTTP_FORM_HX711[] PROGMEM =
  325. "<fieldset><legend><b>&nbsp;" D_CALIBRATION "&nbsp;</b></legend>"
  326. "<form method='post' action='" WEB_HANDLE_HX711 "'>"
  327. "<br/><b>" D_REFERENCE_WEIGHT "</b> (" D_UNIT_KILOGRAM ")<br/><input type='number' step='0.001' id='p1' name='p1' placeholder='0' value='{1'><br/>"
  328. "<br/><button name='calibrate' type='submit'>" D_CALIBRATE "</button><br/>"
  329. "</form>"
  330. "</fieldset><br/><br/>"
  331. "<fieldset><legend><b>&nbsp;" D_HX711_PARAMETERS "&nbsp;</b></legend>"
  332. "<form method='post' action='" WEB_HANDLE_HX711 "'>"
  333. "<br/><b>" D_ITEM_WEIGHT "</b> (" D_UNIT_KILOGRAM ")<br/><input type='number' max='6.5535' step='0.0001' id='p2' name='p2' placeholder='0.0' value='{2'><br/>";
  334. void HandleHxAction(void)
  335. {
  336. if (HttpUser()) { return; }
  337. if (!WebAuthenticate()) { return WebServer->requestAuthentication(); }
  338. AddLog_P(LOG_LEVEL_DEBUG, S_LOG_HTTP, S_CONFIGURE_HX711);
  339. if (WebServer->hasArg("save")) {
  340. HxSaveSettings();
  341. HandleConfiguration();
  342. return;
  343. }
  344. char tmp[100];
  345. if (WebServer->hasArg("reset")) {
  346. snprintf_P(tmp, sizeof(tmp), PSTR("Sensor34 1")); // Reset
  347. ExecuteWebCommand(tmp, SRC_WEBGUI);
  348. HandleRoot(); // Return to main screen
  349. return;
  350. }
  351. if (WebServer->hasArg("calibrate")) {
  352. WebGetArg("p1", tmp, sizeof(tmp));
  353. Settings.weight_reference = (!strlen(tmp)) ? 0 : (unsigned long)(CharToDouble(tmp) * 1000);
  354. HxLogUpdates();
  355. snprintf_P(tmp, sizeof(tmp), PSTR("Sensor34 2")); // Start calibration
  356. ExecuteWebCommand(tmp, SRC_WEBGUI);
  357. HandleRoot(); // Return to main screen
  358. return;
  359. }
  360. String page = FPSTR(HTTP_HEAD);
  361. page.replace(F("{v}"), FPSTR(D_CONFIGURE_HX711));
  362. page += FPSTR(HTTP_HEAD_STYLE);
  363. page += FPSTR(HTTP_FORM_HX711);
  364. dtostrfd((float)Settings.weight_reference / 1000, 3, tmp);
  365. page.replace("{1", String(tmp));
  366. dtostrfd((float)Settings.weight_item / 10000, 4, tmp);
  367. page.replace("{2", String(tmp));
  368. page += FPSTR(HTTP_FORM_END);
  369. page += FPSTR(HTTP_BTN_CONF);
  370. ShowPage(page);
  371. }
  372. void HxSaveSettings(void)
  373. {
  374. char tmp[100];
  375. WebGetArg("p2", tmp, sizeof(tmp));
  376. Settings.weight_item = (!strlen(tmp)) ? 0 : (unsigned long)(CharToDouble(tmp) * 10000);
  377. HxLogUpdates();
  378. }
  379. void HxLogUpdates(void)
  380. {
  381. char weigth_ref_chr[33];
  382. dtostrfd((float)Settings.weight_reference / 1000, Settings.flag2.weight_resolution, weigth_ref_chr);
  383. char weigth_item_chr[33];
  384. dtostrfd((float)Settings.weight_item / 10000, 4, weigth_item_chr);
  385. snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_WIFI D_JSON_WEIGHT_REF " %s, " D_JSON_WEIGHT_ITEM " %s"),
  386. weigth_ref_chr, weigth_item_chr);
  387. AddLog(LOG_LEVEL_INFO);
  388. }
  389. #endif // USE_HX711_GUI
  390. #endif // USE_WEBSERVER
  391. /*********************************************************************************************\
  392. * Interface
  393. \*********************************************************************************************/
  394. boolean Xsns34(byte function)
  395. {
  396. boolean result = false;
  397. if (hx_type) {
  398. switch (function) {
  399. case FUNC_INIT:
  400. HxInit();
  401. break;
  402. case FUNC_EVERY_100_MSECOND:
  403. HxEvery100mSecond();
  404. break;
  405. case FUNC_COMMAND:
  406. if (XSNS_34 == XdrvMailbox.index) {
  407. result = HxCommand();
  408. }
  409. break;
  410. case FUNC_JSON_APPEND:
  411. HxShow(1);
  412. break;
  413. #ifdef USE_WEBSERVER
  414. case FUNC_WEB_APPEND:
  415. HxShow(0);
  416. break;
  417. #ifdef USE_HX711_GUI
  418. case FUNC_WEB_ADD_MAIN_BUTTON:
  419. strncat_P(mqtt_data, HTTP_BTN_MENU_MAIN_HX711, sizeof(mqtt_data) - strlen(mqtt_data) -1);
  420. break;
  421. case FUNC_WEB_ADD_BUTTON:
  422. strncat_P(mqtt_data, HTTP_BTN_MENU_HX711, sizeof(mqtt_data) - strlen(mqtt_data) -1);
  423. break;
  424. case FUNC_WEB_ADD_HANDLER:
  425. WebServer->on("/" WEB_HANDLE_HX711, HandleHxAction);
  426. break;
  427. #endif // USE_HX711_GUI
  428. #endif // USE_WEBSERVER
  429. }
  430. }
  431. return result;
  432. }
  433. #endif // USE_HX711