diff --git a/Debouncer.h b/Debouncer.h deleted file mode 100644 index 10ea253f98fe4ffc3ceba20b575c2137c87bb541..0000000000000000000000000000000000000000 --- a/Debouncer.h +++ /dev/null @@ -1,317 +0,0 @@ - -#include - -#ifndef BUTTON_DEFAULT_DEBOUNCE_PUSH_MS -#define BUTTON_DEFAULT_DEBOUNCE_PUSH_MS 50 // debounce delay for push button -#endif - -#ifndef BUTTON_DEFAULT_DEBOUNCE_ROTARY_MS -#define BUTTON_DEFAULT_DEBOUNCE_ROTARY_MS 1 // debounce delay for rotary button -#endif - -#ifndef BUTTON_DEFAULT_SHORT_MS -#define BUTTON_DEFAULT_SHORT_MS 200 // max interval between multiple short clicks -#endif - -#ifndef BUTTON_DEFAULT_LONG_MS -#define BUTTON_DEFAULT_LONG_MS 400 // min interval for long click -#endif - -#if 0 -#define DEBUGBUTTON(x...) do { Serial.print(now_ms); Serial.print(":"); x; } while (0) -#else -#define DEBUGBUTTON(x...) do { } while (0) -#endif - -/////////////////////////////////////////////////////////////// - -/* - ButtonFast:: / Button:: - !enable_multi && !enable_long: - short click: response on click - no long click - - ButtonLong:: / Button:: - !enable_multi && enable_long: - short click: response on release + debounce time - long click: response on long click delay - - Debouncer:: - enable_multi && !enable_long: - short clicks number: response on final release + debounce time - - Debouncer:: (default) - enable_multi && enable_long: - short clicks number: reponse on final release + debounce time - long click: response on long click delay -*/ - -template ::type long_ms = BUTTON_DEFAULT_LONG_MS, /* 0 to disable long press */ - const typename std::remove_reference::type debounce_ms = BUTTON_DEFAULT_DEBOUNCE_PUSH_MS, - const typename std::remove_reference::type short_ms = BUTTON_DEFAULT_SHORT_MS> -class Debouncer -{ -protected: - - using T = typename std::remove_reference::type; - - enum state_e - { - off, - on, - on_long, - on_debounce, - off_debounce, - }; - - state_e state; - uint_fast8_t short_count; - bool long_state; - T state_ms; - -public: - - Debouncer (): state(off), short_count(0), long_state(false), state_ms(0) - { - DEBUGBUTTON(Serial.println(enable_multi)); - DEBUGBUTTON(Serial.println(long_ms)); - DEBUGBUTTON(Serial.println(debounce_ms)); - DEBUGBUTTON(Serial.println(short_ms)); - } - - bool update (bool pressed); - - // simulate a long press - void extLongClick () - { - long_state = true; - } - - // simulate n short presses - void extShortClick (uint_fast8_t n) - { - short_count += n; - } - - // API1 ////////////////////////////////////// - // returns long state and clears/ignore shorts - bool longClick () - { - bool state = long_state; - long_state = false; - short_count = 0; - return state; - } - - // returns short count and clears/ingore long - uint_fast8_t shortClick () - { - auto count = short_count; - short_count = 0; - long_state = false; - return count; - } - //////////////////////////////////////////// - - // API2 //////////////////////////////////// - // returns binary LSSSSSSS and clears both - uint_fast8_t pullState () - { - uint_fast8_t ret = short_count | (long_state << 7); - long_state = false; - short_count = 0; - return ret; - } - //////////////////////////////////////////// - - // API3 //////////////////////////////////// - // returns long state, does not clear shorts - bool longState () - { - bool state = long_state; - long_state = false; - return state; - } - - // returns short count, does no clear long - uint_fast8_t shortCount () - { - auto count = short_count; - short_count = 0; - return count; - } - //////////////////////////////////////////// - - bool isOff () const - { - return state == off; - } - - bool isLong () - { - long_state = false; - return state == on_long; - } -}; - -/////////////////////////////////////////////////////////////// - -template ::type debounce_ms = BUTTON_DEFAULT_DEBOUNCE_PUSH_MS, - const typename std::remove_reference::type short_ms = BUTTON_DEFAULT_SHORT_MS> -class ButtonFast: public Debouncer -{ -public: - // multiclick disabled, longclick disabled - ButtonFast (): Debouncer() { } -}; - -/////////////////////////////////////////////////////////////// - -template ::type long_ms = BUTTON_DEFAULT_LONG_MS> -class ButtonLong: public Debouncer -{ -public: - ButtonLong (): Debouncer() { } -}; - -/////////////////////////////////////////////////////////////// - -template ::type debounce_ms = BUTTON_DEFAULT_DEBOUNCE_ROTARY_MS> -class ButtonRotary -{ -protected: - ButtonFast b1, b2; - int_fast8_t rot; - -public: - ButtonRotary (): b1(), b2(), rot(0) { } - - bool update (bool in1, bool in2); // steady state with pullup: true, true - bool right () { bool ret; if ((ret = (rot > 0))) --rot; return ret; } - bool left () { bool ret; if ((ret = (rot < 0))) ++rot; return ret; } -}; - -/////////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////// - -template ::type long_ms, - const typename std::remove_reference::type debounce_ms, - const typename std::remove_reference::type short_ms> -bool Debouncer::update (bool pressed) -{ - switch (state) - { - case off: - if (pressed) - { - // button is off, and now pressed - DEBUGBUTTON(Serial.println("off->on_debounce")); - state = on_debounce; - state_ms = now_ms; - if (!enable_multi && /*long press disabled*/(long_ms == 0)) - { - short_count = 1; - return true; - } - } - break; // off, no change, return unreaded state - - case on_long: - if (pressed) - return false; // no change - DEBUGBUTTON(Serial.println("on_long->off_debounce")); - state = off_debounce; - state_ms = now_ms; - return !enable_multi; - - case on_debounce: - if ((T)(now_ms - state_ms) < debounce_ms) - { - // don't care before debounce delay - DEBUGBUTTON(Serial.println("debounce-on")); - return false; - } - DEBUGBUTTON(Serial.println("on_debounce->on")); - state = on; - // fall through - case on: - if (/*long press enabled*/(long_ms > 0) && ((T)(now_ms - state_ms) > long_ms)) - { - // long press in progress - DEBUGBUTTON(Serial.println("on->on_long")); - state = on_long; - long_state = true; - return true; - } - if (!pressed) - { - // off after debounce delay - DEBUGBUTTON(Serial.println("on->off_debounce")); - state = off_debounce; - state_ms = now_ms; - // so it was a short click - if (enable_multi) - short_count++; - return !enable_multi && /*long press enabled*/(long_ms > 0); - } - return false; // no change - - case off_debounce: - if (((T)(now_ms - state_ms)) < debounce_ms) - { - // don't care before debounce delay - DEBUGBUTTON(Serial.println("debounce-off")); - return false; - } - if (pressed) - { - // multiclick in progress - DEBUGBUTTON(Serial.println("off_debounce->on_debounce")); - state = on_debounce; - state_ms = now_ms; - return false; - } - if (enable_multi && ((T)(now_ms - state_ms)) < short_ms) - { - // wait for multiclick - DEBUGBUTTON(Serial.println("(multiclick?)")); - return false; - } - // released for good - DEBUGBUTTON(Serial.println("off_debounce->off")); - state = off; - break; - } - - return short_count || long_state; -} - -/////////////////////////////////////////////////////////////// - -template ::type debounce_ms> -bool ButtonRotary::update (bool pressed1, bool pressed2) -{ - bool b1u = b1.update(pressed1); - bool b2u = b2.update(pressed2); - if (b1u && b2.isOff() && b1.shortCount()) - { - rot++; - return true; - } - if (b2u && b1.isOff() && b2.shortCount()) - { - rot--; - return true; - } - return false; -} - -/////////////////////////////////////////////////////////////// diff --git a/README.md b/README.md index 7cc8b252cd07f99d6e0b6c23fd35d9cb68bd0fe2..50663c234714e4151ca9cd9c6fc8073d01c4e200 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,9 @@ Pousse Seringue / Arduino / ESP8266 / LAAS-HW - WIRING: 1A/A-/grn 1B/A+/blk 2A/B-/blu 2B/B+/red - [Wiring ref1](https://www.omc-stepperonline.com/download/17HS19-2004S1.pdf) - [Wiring ref2](https://1000diy.com/p3140/kak-podklyuchit-shagovyy-motor-k-drayveru-cvetovaya-markirovka-provodov) + - Stepper: 11HS18-0674S + - 200steps 1.8°/step 2A/φ 1.4Ω/φ + - WIRING: 1A/A-/grn 1B/A+/blk 2A/B-/blu 2B/B+/red - Arduino libraries (git-clone dans le répertoires des librairies Arduino) - [Web UI](https://github.com/s00500/ESPUI.git) diff --git a/cli.cpp b/cli.cpp new file mode 100644 index 0000000000000000000000000000000000000000..61f184c840a711d10ee3bc45d3e6ee0199425eb1 --- /dev/null +++ b/cli.cpp @@ -0,0 +1,283 @@ + +#include "cli.h" + +void Cli::resetNextWord () +{ + _currentWordIndex = -1; +} + +const char* Cli::findNextWord () +{ + if (_currentWordIndex >= 0) + // skip current word + while (_currentWordIndex < (int)_currentInput.length()) + { + if (_currentInput[_currentWordIndex] == 32) + break; + _currentWordIndex++; + } + else + _currentWordIndex = 0; + + // skip spaces + while (_currentWordIndex < (int)_currentInput.length()) + { + if (_currentInput[_currentWordIndex] != 32) + break; + _currentWordIndex++; + } + + const char* ret = _currentWordIndex < (int)_currentInput.length()? _currentInput.c_str() + _currentWordIndex: nullptr; + + for (_currentWordEndIndex = _currentWordIndex; _currentInput.c_str()[_currentWordEndIndex] > 32; _currentWordEndIndex++); + +#if 0 + if (ret) + Serial.printf("# found new start of word '%s'\n", ret); + else + Serial.printf("# no more word?\n"); +#endif + return ret; +} + +void Cli::copyNextToTemp () +{ + if (!findNextWord()) + { + _temp = emptyString; + return; + } + int start = _currentWordIndex; + int end = _currentWordEndIndex; + if (start < end) + _temp = _currentInput.substring(start, end); + else + _temp.clear(); +} + +bool Cli::kw (const __FlashStringHelper* keyWord, const char* input) +{ + // return true if input matches keyWord + auto len = strlen_P((PGM_P)keyWord); + return len && strncasecmp_P((PGM_P)keyWord, input, len) == 0; +} + +bool Cli::kw (const __FlashStringHelper* keyWord) +{ + return kw(keyWord, _currentInput.c_str() + _currentWordIndex); +} + +void Cli::syntax (const __FlashStringHelper* cmd) +{ + syntax((PGM_P)cmd); +} + +void Cli::syntax (const char* cmd) +{ + if (!cmd || kw(F("AT"), cmd)) Serial.printf("%sAT -> OK\n", _msgHeader.c_str()); + if (!cmd || kw(F("HELP"), cmd)) Serial.printf("%sHELP [CMD]\n", _msgHeader.c_str()); + if (!cmd || kw(F("RAT"), cmd)) Serial.printf("%sRAT [ C ] - set or show rate (UM/MM/UH/MH)\n", _msgHeader.c_str()); + if (!cmd || kw(F("VOL"), cmd)) Serial.printf("%sVOL [ | ] - set volume or unit (UL / ML)\n", _msgHeader.c_str()); + if (!cmd || kw(F("MM"), cmd)) Serial.printf("%sMM [ ] - set distance (mm)\n", _msgHeader.c_str()); + if (!cmd || kw(F("DIA"), cmd)) Serial.printf("%sDIA [ ] - set or show inside syringe diameter (in mm)\n", _msgHeader.c_str()); + if (!cmd || kw(F("DIR"), cmd)) Serial.printf("%sDIR [ INF | WDR ] - set or show direction (INFuse / WithDRaw)\n", _msgHeader.c_str()); + if (!cmd || kw(F("FIL"), cmd)) Serial.printf("%sFIL - start filling using direction at rate for volume\n", _msgHeader.c_str()); + if (!cmd || kw(F("STP"), cmd)) Serial.printf("%sSTP - stop\n", _msgHeader.c_str()); + if (!cmd || kw(F("DIS"), cmd)) Serial.printf("%sDIS - show volume dispensed\n", _msgHeader.c_str()); + if (!cmd || kw(F("ZERO"), cmd)) Serial.printf("%sZERO - go to stopper\n", _msgHeader.c_str()); + if (!cmd || kw(F("SET0"), cmd)) Serial.printf("%sSET0 - change ZERO to current position\n", _msgHeader.c_str()); + if (!cmd || kw(F("NLCK"), cmd)) Serial.printf("%sNLCK - try to get out from stopper\n", _msgHeader.c_str()); + if (!cmd || kw(F("CONF"), cmd)) Serial.printf("%sCONF - show configuration\n", _msgHeader.c_str()); +} + +void Cli::answer (bool ok, const String& error_message) const +{ + Serial.printf("%s%s", _msgHeader.c_str(), ok? "OK": "ERROR"); + if (!ok && error_message.length()) + Serial.printf(" (%s)", error_message.c_str()); + Serial.printf("\n"); +} + +void Cli::checkEmergency () +{ + if (syringe.emergency()) + Serial.printf("%s: in EMERGENCY state, try NLCK command\n", _msgHeader.c_str()); +} + +void Cli::loop (Stream& input) +{ + while (true) + { + if (!input.available()) + return; + int c = input.read(); + if (c == 13) + break; + if (c >= 32 && c <= 127) + _currentInput += (char)c; + // ignore other chars + } + + resetNextWord(); + + Syringe::Syringe_configuration_t conf = syringe.configuration(); + + if (findNextWord()) + { + if (kw(F("AT"))) + { + answer(true); + } + else if (kw(F("HELP"))) + { + copyNextToTemp(); + if (_temp.length()) + syntax(_temp.c_str()); + else + syntax(); + } + else if (kw(F("RAT"))) + { + copyNextToTemp(); + if (_temp.equalsIgnoreCase(F("C"))) + { + copyNextToTemp(); + conf.rate_ul_per_s = atof(_temp.c_str()); + copyNextToTemp(); + if (_temp.length() == 0) + conf.rate_ul_per_s *= 60; // default uL/mn + else if (_temp.equalsIgnoreCase(F("us"))) + conf.rate_ul_per_s *= 1; + else if (_temp.equalsIgnoreCase(F("ms"))) + conf.rate_ul_per_s *= 1000; + else if (_temp.equalsIgnoreCase(F("um"))) + conf.rate_ul_per_s *= 60; + else if (_temp.equalsIgnoreCase(F("mm"))) + conf.rate_ul_per_s *= 1000 * 60.0; + else if (_temp.equalsIgnoreCase(F("uh"))) + conf.rate_ul_per_s *= 3600; + else if (_temp.equalsIgnoreCase(F("mh"))) + conf.rate_ul_per_s *= 1000 * 3600.0; + else + conf.rate_ul_per_s = -1; + answer(syringe.configureSyringe(conf), F("invalid 'RAT C rate [unit]'")); + } + else if (_temp.length() > 0) + answer(false, F("RAT must be followed by C")); + + Serial.printf("%sRAT: %g ul/mn\n", _msgHeader.c_str(), + syringe.configuration().rate_ul_per_s / 60); + + } + else if (kw(F("VOL"))) + { + copyNextToTemp(); + while (_temp.length()) + { + if (isdigit(_temp[0])) + conf.volume_value = atof(_temp.c_str()); + else if (_temp.equalsIgnoreCase(F("ul"))) + conf.volume_unit_ul = 1; + else if (_temp.equalsIgnoreCase(F("ml"))) + conf.volume_unit_ul = 1000; + else if (_temp.equalsIgnoreCase(F("cl"))) + conf.volume_unit_ul = 10000; + else if (_temp.equalsIgnoreCase(F("dl"))) + conf.volume_unit_ul = 100000; + else if (_temp.equalsIgnoreCase(F("l"))) + conf.volume_unit_ul = 1000000; + answer(syringe.configureSyringe(conf), F("invalid volume or volume unit")); + copyNextToTemp(); + } + + Serial.printf("%sVOL: %g ul (%g mm)\n", _msgHeader.c_str(), syringe.confToMm3(), syringe.confToMm()); + } + else if (kw(F("MM"))) + { + copyNextToTemp(); + if (_temp.length()) + { + if (isdigit(_temp[0])) + conf.volume_value = syringe.mmToMm3(atof(_temp.c_str())); + answer(syringe.configureSyringe(conf), F("invalid length")); + copyNextToTemp(); + } + + Serial.printf("%sVOL: %g ul (%g mm)\n", _msgHeader.c_str(), syringe.confToMm3(), syringe.confToMm()); + } + else if (kw(F("DIA"))) + { + copyNextToTemp(); + if (_temp.length()) + { + conf.diameter_mm = atof(_temp.c_str()); + answer(syringe.configureSyringe(conf), F("invalid diameter")); + } + Serial.printf("%sDIA: %g mm\n", _msgHeader.c_str(), syringe.configuration().diameter_mm); + } + else if (kw(F("DIR"))) + { + copyNextToTemp(); + if (_temp.length()) + { + if (_temp.equalsIgnoreCase(F("inf"))) + conf.direction = 1; + else if (_temp.equalsIgnoreCase(F("wdr"))) + conf.direction = -1; + else + conf.direction = 0; + answer(syringe.configureSyringe(conf), F("invalid direction -> INF | WDR")); + } + Serial.printf("%sDIR: %s\n", _msgHeader.c_str(), + syringe.configuration().direction > 0? "INFuse": "WithDRaw"); + } + else if (kw(F("FIL"))) + { + checkEmergency(); + syringe.fill(); + answer(true); + } + else if (kw(F("STP"))) + { + syringe.stayHere(); + answer(true); + } + else if (kw(F("SET0"))) + { + syringe.resetPosition(); + answer(true); + } + else if (kw(F("DIS"))) + { + Serial.printf("%sDIS: I %g W %g UL\n", _msgHeader.c_str(), + conf.direction > 0? syringe.mmToMm3(syringe.stepToMm(syringe.whereStep())): 0, + conf.direction > 0? 0 : syringe.mmToMm3(syringe.stepToMm(syringe.whereStep()))); + } + else if (kw(F("ZERO"))) + { + checkEmergency(); + syringe.findZero(); + answer(true); + } + else if (kw(F("NLCK"))) + { + syringe.runFromEmergency(); + answer(true); + } + else if (kw(F("CONF"))) + { + syringe.showConfiguration(syringe.configuration()); + answer(true); + } + else + { + Serial.printf("%sinvalid command '%s'\n", _msgHeader.c_str(), _currentInput.c_str() + _currentWordIndex); + answer(false); + } + } + + if (findNextWord()) + Serial.printf("%sgarbage at end of line: '%s'\n", _msgHeader.c_str(), _currentInput.c_str() + _currentWordIndex); + resetNextWord(); + _currentInput.clear(); +} diff --git a/cli.h b/cli.h new file mode 100644 index 0000000000000000000000000000000000000000..3cd24ddcc73c1f9b02fad1ea61f2fc54d465b082 --- /dev/null +++ b/cli.h @@ -0,0 +1,40 @@ + +#pragma once + +#include + +#include "syringe.h" + +class Cli +{ +private: + + Syringe& syringe; + + String _currentInput; + String _temp; + String _msgHeader = "#CLI: "; + int _currentWordIndex; + int _currentWordEndIndex; + + void resetNextWord (); + const char* findNextWord (); + void copyNextToTemp (); + bool kw (const __FlashStringHelper* keyWord, const char* input); + bool kw (const __FlashStringHelper* keyWord); + void syntax (const __FlashStringHelper* cmd); + void syntax (const char* cmd = nullptr); + void answer (bool ok, const String& error_message = emptyString) const; + void checkEmergency (); + +public: + + Cli (Syringe& syringe, int bufferLen = 32): syringe(syringe) + { + _currentInput.reserve(bufferLen); + _temp.reserve(bufferLen); + resetNextWord(); + } + + void loop (Stream& input); +}; diff --git a/common.h b/common.h index fa4839d0dd464f5b3cb7c3cbb702e515a2c3a55a..c648588a5f1d76c4ddc12ace6015b6b59cf98fc1 100644 --- a/common.h +++ b/common.h @@ -3,9 +3,5 @@ #include -using millis_t = decltype(millis()); - -extern millis_t nowMs; // to update on main loop(): "nowMs = millis();" - void webSetup (); void webLoop (); diff --git a/docs/contrib/NE-1000-Syringe-Pump-User-Manual.pdf b/docs/contrib/NE-1000-Syringe-Pump-User-Manual.pdf new file mode 100644 index 0000000000000000000000000000000000000000..fb0e8fb8db85419ef4574f1e1039ecaca6c6ddd1 Binary files /dev/null and b/docs/contrib/NE-1000-Syringe-Pump-User-Manual.pdf differ diff --git a/host_gui/make.sh b/host_gui/make.sh index 36a876f3e7075b901c9ecf221333d6a4c26563ec..f0537cbc2cea916f4c2475225bd2b67370084d53 100755 --- a/host_gui/make.sh +++ b/host_gui/make.sh @@ -1,21 +1,21 @@ #!/bin/sh -ex +firefox=true +[ -z "$1" ] || firefox=false + pwd=$(pwd) cd ${ESP8266ARDUINO}/tests/host make FORCE32=0 ssl -make -j 10 FORCE32=0 D=1 USERCFLAGS="-I ${ARDUINOLIB}/emuAsync/replacement" ULIBDIRS=${ARDUINOLIB}/emuAsync:${ARDUINOLIB}/ESPUI:${ARDUINOLIB}/ArduinoJson:${ARDUINOLIB}/arduinoWebSockets ${pwd}/../pousseseringue-arduino +make -j 10 FORCE32=0 USERCFLAGS="-I ${ARDUINOLIB}/emuAsync/replacement" ULIBDIRS=${ARDUINOLIB}/emuAsync:${ARDUINOLIB}/ESPUI:${ARDUINOLIB}/ArduinoJson:${ARDUINOLIB}/arduinoWebSockets ${pwd}/../pousseseringue-arduino -(./bin/pousseseringue-arduino/pousseseringue-arduino -b "$@" 2>&1 | grep -v '^http-server loop: conn=') & pid=$! -trap "kill ${pid}" EXIT INT +$firefox && ( + true '----------------------------------------' + true '----------- starting firefox -----------' + true '----------------------------------------' + sleep 5 + firefox -new-window http://localhost:9080 +) & -sleep 1 -true '----------------------------------------' -true '----------- starting firefox -----------' -true '----------------------------------------' -sleep 4 -firefox -new-window http://localhost:9080 +./bin/pousseseringue-arduino/pousseseringue-arduino "$@" -while true; do - true '^C to quit' - read junk -done +stty sane diff --git a/motor.cpp b/motor.cpp index 51c65e2d56757543d3a2e3b4dc75ab8963a224d1..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 100644 --- a/motor.cpp +++ b/motor.cpp @@ -1,19 +0,0 @@ -#include "motor.h" - -// Global object -Motor motor; - -void Motor::reset() -{ - // Open full -} - -void Motor::turn(bool direction, float duration, float speed) -{ - // Direction: push (true) or pull (false) - // Duration: in s - // Speed: in mm/min - - // This function is in charge of converting the - // linar speed in mm/min to angular speed in steps/min -} diff --git a/motor.h b/motor.h index 4b0b65fc018f34e16d17c098f00f7efa17a7552a..51ba067ca96034a0a6661e2b32b5899dd4fba5be 100644 --- a/motor.h +++ b/motor.h @@ -1,13 +1,149 @@ #pragma once +#define M4_MMREV 0.7 +#define M5_MMREV 0.8 + + +#if CORE_MOCK +class AccelStepper +{ +}; +#else +#include +#endif + class Motor { public: - void reset(); - void turn(bool direction, float duration, float speed); -private: - int current_step_count = 0; -}; + using MotorHardware_t = AccelStepper; + + Motor (AccelStepper& stepper): stepper(stepper) + { + setSpeedMmPerSec(1); + setAccelMmPerSecPerSec(0.1); // <----- this is not yet configured by user + } + + void setLock (bool locked) { _locked = locked; } + + bool locked () const { return _locked; } + + void setPhysical (int stepsPerRevolution, float mmPerRevolution, bool forwardClockwise) + { + _stepsPerRevolution = stepsPerRevolution; + _mmPerRevolution = mmPerRevolution; + _forwardClockwise = forwardClockwise; + } + + void setSpeedMmPerSec (float mmPerSec) + { +#if !CORE_MOCK + stepper.setMaxSpeed(mmToStep(mmPerSec)); +#endif + } + + void setAccelMmPerSecPerSec (float mmPerSecPerSec) + { + _retainAccel = mmPerSecPerSec; +#if !CORE_MOCK + stepper.setAcceleration(mmToStep(mmPerSecPerSec)); +#endif + } + float mmToStep (float mm) const + { + return mm * _stepsPerRevolution / _mmPerRevolution; + } + + float stepToMm (int step) const + { + return step * _mmPerRevolution / _stepsPerRevolution; + } + + int whereStep () + { +#if CORE_MOCK + return 0; +#else + return stepper.currentPosition(); +#endif + } + + void moveToMm (float mm) + { + moveToStep(mmToStep(mm)); + } + + void moveToStep (int step) + { +#if !CORE_MOCK + cli(); + setAccelMmPerSecPerSec(_retainAccel); + if (_mmPerRevolution > 0 && _stepsPerRevolution > 0) + stepper.moveTo(_forwardClockwise? step: -step); + sei(); +#endif + Serial.printf("# move to: %d step %g mm\n", step, stepToMm(step)); + } + + void stop () + { +#if !CORE_MOCK + cli(); + stepper.stop(); + sei(); +#endif + } + + void resetPosition() + { +#if !CORE_MOCK + stop(); + cli(); + stepper.setCurrentPosition(0); + sei(); +#endif + } + + void stayHere () + { +#if !CORE_MOCK + cli(); + stepper.setAcceleration(1e20); + stepper.moveTo(stepper.currentPosition()); // change target to here + setAccelMmPerSecPerSec(_retainAccel); + sei(); +#endif + } + + void showConfiguration () + { + Serial.printf("# %g mm/rev\n" + "# %d steps/rev\n" + "# %g steps/mm\n" + "# position: %ld steps / %g mm\n" + "# max speed: %g steps/s %g mm/s\n" + "# accel: %g steps/s/s %g mm/s/s\n", + _mmPerRevolution, + _stepsPerRevolution, + _stepsPerRevolution / _mmPerRevolution, + stepper.currentPosition(), + stepToMm(stepper.currentPosition()), + stepper.maxSpeed(), + stepToMm(stepper.maxSpeed()), + stepper.acceleration(), + stepToMm(stepper.acceleration()) + ); + } + +protected: + + bool _locked = false; + int _stepsPerRevolution = -1; + float _mmPerRevolution = -1; + float _retainAccel = -1; + bool _forwardClockwise; + + AccelStepper& stepper; +}; diff --git a/pousseseringue-arduino.cpp b/pousseseringue-arduino.cpp index ed4a1eb9823fe17b28db09610cd6782a1272e4ec..05cef8d3c4b4b715f30c5fb15ed5f24fbf8599f4 100644 --- a/pousseseringue-arduino.cpp +++ b/pousseseringue-arduino.cpp @@ -3,12 +3,12 @@ // Feather LCD // Feather motor controler -#if 0 -#define STASSID "laas-capture" -#define STAPSK "" -#else -#define STASSID "laas-welcome" -#define STAPSK "WifiLAAS2018" // this password is wrong +#undef STASSID +#undef STAPSK + +#ifndef STASSID +#define STASSID "" +#define STAPSK "WifiLAAS2022" // this password is wrong #endif #define NAME "PousseSeringue" @@ -19,43 +19,42 @@ #define BTNC 2 // gpio C adafruit feather oled 128x32 #define OLED_I2C 0x3c // adafruit feather oled 128x32 -#if 1 // 1 for laas board - // A4988 PINOUT #define DIR 15 // esp8266 gpio #define STEP 13 // esp8266 gpio #define SLEEP 12 // esp8266 gpio -#else // primilinary proto pinout - -#warning WARNING NOT LAAS BOARD ! +#define COURSE 14 // (d5) fin de course -// A4988 PINOUT -#define DIR 12 // esp8266 gpio -#define STEP 13 // esp8266 gpio -#define SLEEP 14 // esp8266 gpio - -#endif // proto pinout +#ifndef BAUDRATE +#define BAUDRATE 9600 +#endif // should this be configurable through a GUI ? #define MOTOR_STEPS 200 // 17HS19 #define MICROSTEPS_CONF 16 // hardware configuration: A4988's MS1,MS2,MS3 tied up -#define STEPPER_RPM 60 // some value is necessary for the driver XXXXX see below - -#define STEP_DURATION_MIN_MS 2 -#define STEP_DURATION_DEFAULT_MS 5 - - - #include // internal, wifi library #include // internal, access point mode +#include + #if !CORE_MOCK #include // https://github.com/greiman/SSD1306Ascii -#include +SSD1306AsciiWire oled; #endif // !CORE_MOCK + + +#include "motor.h" +#if CORE_MOCK +AccelStepper stepper; +#else +AccelStepper stepper(AccelStepper::DRIVER, STEP, DIR); +#endif + + #include "Debouncer.h" // local, debouncer, short counter, long detector #include "syringe.h" +#include "cli.h" #include "common.h" @@ -63,36 +62,31 @@ enum class Modes { TURN, TURNSetup, RPM, RPMSetup, INFO }; -millis_t nowMs; // to update on main loop(): "nowMs = millis();" +Debouncer bA, bB, bC; +ButtonFast bEmergency; -Debouncer bA, bB, bC; - -#if !CORE_MOCK -AccelStepper stepper(AccelStepper::DRIVER, STEP,DIR); -SSD1306AsciiWire oled; -#endif // !CORE_MOCK constexpr int DNS_PORT = 53; DNSServer dnsServer; char ssid[65]; -Modes mode = Modes::TURN; +Modes mode = Modes::RPM; +bool stepperMoving = false; -millis_t lastMoveMs = 0; int move = 0; // TURN mode constexpr float step2deg = 360.0 / MOTOR_STEPS / MICROSTEPS_CONF; // deg for 1 step -int steps = MOTOR_STEPS * MICROSTEPS_CONF / 100; // =1/100 turn +int steps = MOTOR_STEPS * MICROSTEPS_CONF / 4; // =1/4 turn // RPM mode -int stepsPerDuration = 1; -int stepDurationMs = STEP_DURATION_DEFAULT_MS; -float stepsPerDuration2rpm () { return 60.0 * stepsPerDuration / ((MOTOR_STEPS * MICROSTEPS_CONF) * (stepDurationMs / 1000.0)); } +int stepsPerSeconds = MOTOR_STEPS * MICROSTEPS_CONF / 2; +float stepsPerSeconds2rpm () { return 60.0 * stepsPerSeconds / (MOTOR_STEPS * MICROSTEPS_CONF); } int rpming = 0; // -1, 0 or +1 // single syringe global instance -Syringe syringe; +Syringe syringe(stepper); +Cli console(syringe); const char* oledMode () { @@ -122,28 +116,28 @@ void oledRedisplay () switch (mode) { case Modes::TURN: - oled.printf("<%g dR < ", move); move += shortp * steps; - move += longp * MOTOR_STEPS * MICROSTEPS_CONF; + move += longp * MOTOR_STEPS * MICROSTEPS_CONF; // long: one turn + Serial.printf("%d\n", move); +#if !CORE_MOCK + cli(); + stepper.moveTo(move); + sei(); +#endif // !CORE_MOCK break; + case Modes::TURNSetup: steps += shortp; - steps += longp * 10; + steps += longp * MOTOR_STEPS * MICROSTEPS_CONF / 16; oledRedisplay(); +#if !CORE_MOCK + cli(); + stepper.setCurrentPosition(0); + sei(); +#endif // !CORE_MOCK break; + case Modes::RPM: if (longp) - rpming = longp; // move will be increased in main loop - if (shortp) - move = 0; // stop moving + { + rpming = longp? (oldrpming > 1? -1: 1): 0; + if (rpming) + { +#if !CORE_MOCK + cli(); + stepper.setSpeed(stepsPerSeconds); + sei(); +#endif // !CORE_MOCK + Serial.printf("StepPerSecs=%d (%g RPM)\n", stepsPerSeconds, stepsPerSeconds2rpm()); + //stepper.setMaxSpeed(400); + } + } break; + case Modes::RPMSetup: - if ((stepDurationMs += longp) < STEP_DURATION_MIN_MS) - stepDurationMs = STEP_DURATION_MIN_MS; - if ((stepsPerDuration += shortp) < 1) - stepsPerDuration = 1; + if ((stepsPerSeconds += longp * MOTOR_STEPS * MICROSTEPS_CONF / 16) < 1) + stepsPerSeconds = 1; + if ((stepsPerSeconds += shortp) < 1) + stepsPerSeconds = 1; oledRedisplay(); break; + default: Serial.printf("press not handled in buttons()\n"); } } +#if !CORE_MOCK + +IRAM_ATTR +void stepperRun () +{ + if (syringe.locked()) + return; + cli(); + if (rpming) + stepperMoving = stepper.runSpeed(); + else + stepperMoving = stepper.run(); + sei(); +} + +#endif // !CORE_MOCK void setup() { - Serial.begin(9600); + Serial.begin(BAUDRATE); Serial.setDebugOutput(true); #if !CORE_MOCK Wire.begin(); @@ -226,25 +264,33 @@ void setup() delay(500); #if !CORE_MOCK + // initialize hardware + oled.begin(&OLED_HW, OLED_I2C); oled.setFont(System5x7); oled.setScrollMode(SCROLL_MODE_AUTO); oled.clear(); oled.println("Booting...\n"); -#endif // !CORE_MOCK -#if !CORE_MOCK - stepper.setMaxSpeed(10000); - stepper.setAcceleration(10000); - stepper.moveTo(200); + + //syringe.setPhysical(MOTOR_STEPS * MICROSTEPS_CONF, M4_MMREV, /*forwardClockWise*/true, /*emergencySensorBehind*/false); + syringe.setPhysical(MOTOR_STEPS * MICROSTEPS_CONF, M5_MMREV, /*forwardClockWise*/false, /*emergencySensorBehind*/true); + + pinMode(SLEEP, OUTPUT); + digitalWrite(SLEEP, 0); + + #endif // !CORE_MOCK uint8_t mac[6]; WiFi.macAddress(mac); sprintf(ssid, NAME "-%02x%02x%02x", mac[3], mac[4], mac[5]); - WiFi.mode(WIFI_AP_STA); - WiFi.softAP(ssid); - WiFi.begin(STASSID, STAPSK); - WiFi.hostname(ssid); + if (strlen(STASSID) > 0) + { + WiFi.mode(WIFI_AP_STA); + WiFi.softAP(ssid); + WiFi.begin(STASSID, STAPSK); + WiFi.hostname(ssid); + } // redirect everything to my AP ip: dnsServer.setErrorReplyCode(DNSReplyCode::NoError); @@ -261,21 +307,40 @@ void setup() pinMode(BTNA, INPUT_PULLUP); pinMode(BTNB, INPUT_PULLUP); pinMode(BTNC, INPUT_PULLUP); - + pinMode(COURSE, INPUT_PULLUP); + oledRedisplay(); + +#if !CORE_MOCK +#ifdef ISR_ATTR + timer1_isr_init(); + timer1_enable(TIM_DIV1, TIM_EDGE, TIM_LOOP); + timer1_attachInterrupt(stepperRun); + timer1_write(microsecondsToClockCycles(100)); +#else + #warning =================================================================================================== + #warning "Please use the ISR version of AccelStepper (git clone -b isr https://github.com/d-a-v/AccelStepper)" + #warning =================================================================================================== + schedule_recurrent_function_us([](){ stepperRun(); return true; }, 1); +#endif +#endif + + // check if emergency button is pressed at boot time + syringe.setEmergency(digitalRead(COURSE)); } void loop() { - nowMs = millis(); - + if (bEmergency.update(digitalRead(COURSE))) + syringe.manageEmergency(bEmergency.pressed(), stepperMoving); + #if CORE_MOCK bool changeA = false; bool changeB = false; bool changeC = false; #else - // button management (nowMs must be updated) + // button management bool changeA = bA.update(!digitalRead(BTNA)); bool changeB = bB.update(!digitalRead(BTNB)); bool changeC = bC.update(!digitalRead(BTNC)); @@ -287,7 +352,7 @@ void loop() // are not cleared until they are read int shortA = bA.shortCount(); bool longA = bA.longState(); - + buttons(shortA, longA); } @@ -303,24 +368,13 @@ void loop() { int shortB = bB.shortCount(); bool longB = bB.longState(); - + if (longB) changeMode(); (void)shortB; - - move = 0; } - // Stepper - #if !CORE_MOCK - if (stepper.distanceToGo() == 0) - { - stepper.moveTo(-stepper.currentPosition()); - Serial.println("Rotating Motor in opposite direction..."); - } - stepper.run(); - #endif // !CORE_MOCK + console.loop(Serial); webLoop(); - - dnsServer.processNextRequest(); // AP redirection + dnsServer.processNextRequest(); // access-point redirection } diff --git a/pousseseringue-arduino.ino b/pousseseringue-arduino.ino index e70951cbf584db88608da47144d6c847bb6d8936..e3e70bcc95a52ecce0d4c4136ec6f8aaa52acae7 100644 --- a/pousseseringue-arduino.ino +++ b/pousseseringue-arduino.ino @@ -1,10 +1,11 @@ // please edit this file: pousseseringue-arduino.cpp -// Pour la compilation locale sur le PC +// needed for compilation on host #if CORE_MOCK #include "pousseseringue-arduino.cpp" #include "web.cpp" #include "syringe.cpp" #include "motor.cpp" + #include "cli.cpp" #endif // !CORE_MOCK diff --git a/pousseseringue-arduino.ino.globals.h-template b/pousseseringue-arduino.ino.globals.h-template new file mode 100644 index 0000000000000000000000000000000000000000..dd1b1ccb0fb6935d7b55f4f652130d04bfc65f84 --- /dev/null +++ b/pousseseringue-arduino.ino.globals.h-template @@ -0,0 +1,10 @@ + +// this file is included first with git version or from release 3.1 of the esp8266 arduino core + +#undef STASSID +#undef STAPSK + +#define STASSID "hackaton" +#define STAPSK "WifiLAAS2022" // changeme + +#define BAUDRATE 74880 diff --git a/syringe.cpp b/syringe.cpp index 7a831b35bb0534fb3399e8761f9798699438bffd..b681e4720c6e5a9e8d5bf8fa29eeb201325a2505 100644 --- a/syringe.cpp +++ b/syringe.cpp @@ -2,149 +2,316 @@ #include "syringe.h" -float Syringe::volumeToDistance(float volume) +void Syringe::showConfiguration (const Syringe_configuration_t& conf) +{ + Motor::showConfiguration(); + Serial.printf("# diameter: %g mm\n" + "# capacity: %g uL\n" + "# rate: %g uL/s = %g mm/s\n" + "# volume: %g uL\n" + "# direction: %s\n" + "# zeroing: %d\n" + "# emergency: %d\n", + conf.diameter_mm, + conf.capacity_ul, + conf.rate_ul_per_s, + mm3ToMm(conf.rate_ul_per_s), + conf.volume_value * conf.volume_unit_ul, + conf.direction > 0? "infuse": "withdraw", + conf.findZero, + emergency()); +} + +float Syringe::confToMm3 () const +{ + return current_configuration.direction * current_configuration.volume_value * current_configuration.volume_unit_ul; +} + +float Syringe::mm3ToMm (float mm3) const +{ + return mm3 / (pow(current_configuration.diameter_mm / 2, 2) * M_PI); +} + +float Syringe::mmToMm3 (float mm) const { - // Volume is in mL => convert to mm³ - float volume_in_mm3 = 1000*volume; + return mm * (pow(current_configuration.diameter_mm / 2, 2) * M_PI); +} - // Distance to travel is in mm => use syringe piston surface in mm² - float distance = volume_in_mm3/this->piston_surface; +float Syringe::confToMm () const +{ + return mm3ToMm(confToMm3()); +} - return distance; +void Syringe::fill () +{ + setSpeedMmPerSec(mm3ToMm(current_configuration.rate_ul_per_s)); + moveToMm(confToMm()); } -void Syringe::configureSyringe(Syringe_configuration_t config) +bool Syringe::configureSyringe(const Syringe_configuration_t& config) { - this->current_configuration = config; - this->piston_surface = M_PI * pow(config.diameter,2) / 4.; + if (config.diameter_mm <= 0) + return false; + + if (config.volume_value < 0) + return false; + + if (config.volume_unit_ul <= 0) + return false; + + if (config.rate_ul_per_s <= 0) + return false; + + if (config.direction != 1 && config.direction != -1) + return false; + + this->current_configuration = config; + + this->piston_surface = M_PI * pow(config.diameter_mm,2) / 4.; + this->is_configured = true; - this->is_configured = true; + return true; } +void Syringe::findZero (float mmPerSec, float maxCourseMm) +{ + if (emergency()) + return; + if (mmPerSec > 0) + setSpeedMmPerSec(mmPerSec); + else + setSpeedMmPerSec(mm3ToMm(current_configuration.rate_ul_per_s)); + resetPosition(); + moveToMm(-maxCourseMm); + current_configuration.findZero = true; +} + +void Syringe::runFromEmergency (float mmPerSec, float maxCourseMm) +{ + if (emergency()) + { + current_configuration.findZero = true; + Serial.printf("EMERGENCY: running away slowly\n"); + setSpeedMmPerSec(mmPerSec); + resetPosition(); + moveToMm(maxCourseMm); + } +} + +void Syringe::resetPosition () +{ + //current_configuration.findZero = false; + Motor::resetPosition(); +} + +void Syringe::manageEmergency (bool pressed, bool stepperMoving) +{ + if (pressed) + { + setEmergency(); + if (stepperMoving) + { + Serial.printf("EMERGENCY\n"); + if (configuration().findZero) + runFromEmergency(); + else + stayHere(); + } + else + Serial.printf("EMERGENCY: kids are playing\n"); + } + else if (configuration().findZero) + { + // emergency button released + current_configuration.findZero = false; + setEmergency(false); + resetPosition(); + Serial.printf("EMERGENCY: danger faded away\n"); + } + else + { + Serial.printf("EMERGENCY: released\n"); + setEmergency(false); + } +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +#if 0 +float Syringe::volumeToDistance(float volume) +{ + // Volume is in mL => convert to mm³ + float volume_in_mm3 = 1000*volume; + + // Distance to travel is in mm => use syringe piston surface in mm² + float distance = volume_in_mm3/this->piston_surface; + + return distance; +} +#endif + +#if 0 void Syringe::setInitialContent(float initial_volume) { - this->remaining_volume = initial_volume; + this->remaining_volume = initial_volume; } void Syringe::moveMotor(bool direction, float duration, float speed) { - float quantity = speed*this->piston_surface*duration; - if (direction == true) - { - if (quantity <= this->remaining_volume) - { - this->remaining_volume -= quantity; - } - else - { - quantity = this->remaining_volume; - this->remaining_volume = 0; - } - } - else - { - if (this->remaining_volume + quantity <= this->current_configuration.capacity) - { - this->remaining_volume += quantity; - } - else - { - quantity = this->current_configuration.capacity - this->remaining_volume; - this->remaining_volume = this->current_configuration.capacity; - } - } - - // TODO: recompute duration to match quantity before sending commande to motor - motor.turn(direction, duration, speed); + float quantity = speed*this->piston_surface*duration; + if (direction == true) + { + if (quantity <= this->remaining_volume) + { + this->remaining_volume -= quantity; + } + else + { + quantity = this->remaining_volume; + this->remaining_volume = 0; + } + } + else + { + if (this->remaining_volume + quantity <= this->current_configuration.capacity_ul) + { + this->remaining_volume += quantity; + } + else + { + quantity = this->current_configuration.capacity_ul - this->remaining_volume; + this->remaining_volume = this->current_configuration.capacity_ul; + } + } + + // TODO: recompute duration to match quantity before sending commande to motor + motor.turn(direction, duration, speed); } void Syringe::reset() { - // TODO: go back to home. At which speed? - float speed = 100; // Default to 10 cm/min - this->pull(this->current_configuration.capacity, speed); + // TODO: go back to home. At which speed? + float speed = 100; // Default to 10 cm/min + this->pull(this->current_configuration.capacity_ul, speed); } void Syringe::push(float volume, float throughput) { - // Ensure syringe is configured before using it - if (this->is_configured == false) - return; + // Ensure syringe is configured before using it + if (this->is_configured == false) + return; - // Volume in mL, throughput in µL/min, duration in min - this->pushFor(volume/throughput, throughput); + // Volume in mL, throughput in µL/min, duration in min + this->pushFor(volume/throughput, throughput); } void Syringe::pushAll(float throughput) { - // Ensure syringe is configured before using it - if (this->is_configured == false) - return; + // Ensure syringe is configured before using it + if (this->is_configured == false) + return; - this->push(this->current_configuration.capacity, throughput); + this->push(this->current_configuration.capacity_ul, throughput); } void Syringe::pushFor(float duration, float throughput) { - // Ensure syringe is configured before using it - if (this->is_configured == false) - return; + // Ensure syringe is configured before using it + if (this->is_configured == false) + return; - float volume = throughput*duration; - float distance = this->volumeToDistance(volume); - float speed = distance/duration; + float volume = throughput*duration; + float distance = this->volumeToDistance(volume); + float speed = distance/duration; - this->moveMotor(true, duration, speed); + this->moveMotor(true, duration, speed); } void Syringe::pushVol(float volume, float duration) { - // Ensure syringe is configured before using it - if (this->is_configured == false) - return; + // Ensure syringe is configured before using it + if (this->is_configured == false) + return; - float throughput = volume/duration; - this->push(volume, throughput); + float throughput = volume/duration; + this->push(volume, throughput); } void Syringe::pull(float volume, float throughput) { - // Ensure syringe is configured before using it - if (this->is_configured == false) - return; + // Ensure syringe is configured before using it + if (this->is_configured == false) + return; - // Volume in mL, throughput in µL/min, duration in min - this->pullFor(volume/throughput, throughput); + // Volume in mL, throughput in µL/min, duration in min + this->pullFor(volume/throughput, throughput); } void Syringe::pullAll(float throughput) { - // Ensure syringe is configured before using it - if (this->is_configured == false) - return; + // Ensure syringe is configured before using it + if (this->is_configured == false) + return; - this->pull(this->current_configuration.capacity, throughput); + this->pull(this->current_configuration.capacity_ul, throughput); } void Syringe::pullFor(float duration, float throughput) { - // Ensure syringe is configured before using it - if (this->is_configured == false) - return; + // Ensure syringe is configured before using it + if (this->is_configured == false) + return; - float volume = throughput*duration; - float distance = this->volumeToDistance(volume); - float speed = distance/duration; + float volume = throughput*duration; + float distance = this->volumeToDistance(volume); + float speed = distance/duration; - this->moveMotor(false, duration, speed); + this->moveMotor(false, duration, speed); } void Syringe::pullVol(float volume, float duration) { - // Ensure syringe is configured before using it - if (this->is_configured == false) - return; + // Ensure syringe is configured before using it + if (this->is_configured == false) + return; - float throughput = volume/duration; - this->pull(volume, throughput); + float throughput = volume/duration; + this->pull(volume, throughput); } +#endif diff --git a/syringe.h b/syringe.h index 49dea578113e5d65ed020218cd2830f5d604ddd5..b39c03c90ec7cead2325e1be00a767e7215c1ff0 100644 --- a/syringe.h +++ b/syringe.h @@ -3,31 +3,68 @@ #include "motor.h" -class Syringe +class Syringe: public Motor { public: - /** - * Diameter in mm - * Capacity in mL - */ + typedef struct { - int diameter; - int capacity; + float diameter_mm; + float capacity_ul; + float rate_ul_per_s; + float volume_value; // see volume_unit_ul + float volume_unit_ul; // volume in ul = volume_value * volume_unit_ul + int direction; + bool findZero; } Syringe_configuration_t; + private: + float volumeToDistance(float volume); void moveMotor(bool direction, float duration, float speed); public: + + Syringe (MotorHardware_t& stepper): Motor(stepper) + { + } + + void setPhysical (int stepsPerRevolution, float mmPerRevolution, bool forwardClockwise, bool emergencySensorBehind) + { + Motor::setPhysical(stepsPerRevolution, mmPerRevolution, forwardClockwise); + _emergencySensorBehind = emergencySensorBehind; + } + + void showConfiguration (const Syringe_configuration_t& conf); + + void setEmergency (bool emergency = true) { _emergency = emergency; } + bool emergency () const { return _emergency; } + + void resetPosition(); + void findZero (float mmPerSec = -1, float maxCourseMm = 200); + void manageEmergency (bool pressed, bool stepperMoving); + void runFromEmergency (float mmPerSec = 0.1, float maxCourseMm = 10); + + const Syringe_configuration_t& configuration () const { return current_configuration; }; + + bool infusing () { return current_configuration.direction == 1; } + // Configuration - void configureSyringe(Syringe_configuration_t config); + + bool configureSyringe(const Syringe_configuration_t& config); void setInitialContent(float initial_volume); + float mm3ToMm (float mm3) const; + float mmToMm3 (float mm) const; + float confToMm () const; + float confToMm3 () const; + // Actions - void reset(); + void fill (); + +#if 0 void push(float volume, float throughput); // Volume in mL, throughput in µL/min void pushAll(float throughput); // Throughput in µL/min void pushFor(float duration, float throughput); // Duration in min, throughput in µL/min @@ -37,12 +74,26 @@ public: void pullAll(float throughput); // Throughput in µL/min void pullFor(float duration, float throughput); // Duration in min, throughput in µL/min void pullVol(float volume, float duration); // Volume in mL, duration in min +#endif private: - Syringe_configuration_t current_configuration = {0}; + + Syringe_configuration_t current_configuration = + { + .diameter_mm = 10, + .capacity_ul = 1000, + .rate_ul_per_s = 80, + .volume_value = 50, + .volume_unit_ul = 1, + .direction = 1, + .findZero = false, + }; + + bool _emergency = false; + bool _emergencySensorBehind; + bool is_configured = false; float remaining_volume = 0; float piston_surface = 0; - - Motor motor; + }; diff --git a/web.cpp b/web.cpp index b2b0794b03f37f23bac52961981caa58a16eb180..32a793aff532ee46516b69ec6a5d18d84abe09a5 100644 --- a/web.cpp +++ b/web.cpp @@ -30,7 +30,7 @@ void buttonCallback(Control *sender, int type) { switch (type) { case B_DOWN: Serial.println("Button DOWN"); - syringe.push(1, 1); + //syringe.push(1, 1); break; case B_UP: @@ -82,10 +82,11 @@ void selectExample(Control *sender, int value) { Serial.println(sender->value); } +uint8_t mock_read_uart(void); void webSetup () { - ESPUI.setVerbosity(Verbosity::VerboseJSON); + //ESPUI.setVerbosity(Verbosity::VerboseJSON); status = ESPUI.addControl(ControlType::Label, "Status:", "Stop", ControlColor::Turquoise); @@ -102,11 +103,11 @@ void webSetup () millisLabelId = ESPUI.addControl(ControlType::Label, "Millis:", "0", ControlColor::Emerald, Control::noParent); switchOne = ESPUI.addControl(ControlType::Switcher, "Switch one", "", ControlColor::Alizarin, Control::noParent, &switchExample); - + uint16_t constantFlow = ESPUI.addControl(ControlType::Slider, "Select a constant flow (mL/s)", "30", ControlColor::Alizarin, Control::noParent, &slider); ESPUI.addControl(ControlType::Min, "", "0", ControlColor::None, constantFlow); ESPUI.addControl(ControlType::Max, "", "30", ControlColor::None, constantFlow); - + uint16_t displacementDistance = ESPUI.addControl(ControlType::Slider, "Select a seringe displacement distance (mm)", "100", ControlColor::Alizarin, Control::noParent, &slider); ESPUI.addControl(ControlType::Min, "", "0", ControlColor::None, displacementDistance); ESPUI.addControl(ControlType::Max, "", "100", ControlColor::None, displacementDistance); @@ -134,7 +135,6 @@ void webSetup () * password, for example begin("ESPUI Control", "username", "password") */ - ESPUI.begin("ESPUI Control"); } @@ -144,14 +144,14 @@ void webLoop () /////////////////// // ESPUI management - static millis_t oldTime = 0; + static decltype(millis()) oldTime = 0; static bool testSwitchState = false; - if (nowMs - oldTime > 5000) + if (millis() - oldTime > 5000) { ESPUI.updateControlValue(millisLabelId, String(millis())); testSwitchState = !testSwitchState; ESPUI.updateControlValue(switchOne, testSwitchState ? "1" : "0"); - oldTime = nowMs; + oldTime = millis(); } }