aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJanar Sööt <janar.soot@gmail.com>2019-04-14 18:18:52 +0300
committerKevinOConnor <kevin@koconnor.net>2019-04-14 11:18:52 -0400
commit0a392b6543e9c0d96c71bddc6965125953e666a7 (patch)
tree784a9c9794e9b3777acfc3db22c35ec5b7bcfc2f
parentd7e1061c63820323dcb68960baf37875ec216fc2 (diff)
downloadkutter-0a392b6543e9c0d96c71bddc6965125953e666a7.tar.gz
kutter-0a392b6543e9c0d96c71bddc6965125953e666a7.tar.xz
kutter-0a392b6543e9c0d96c71bddc6965125953e666a7.zip
menu: initial support for analog buttons (#977)
Signed-off-by: Janar Sööt <janar.soot@gmail.com>
-rw-r--r--config/example-extras.cfg39
-rw-r--r--klippy/extras/buttons.py91
-rw-r--r--klippy/extras/display/menu.py90
3 files changed, 204 insertions, 16 deletions
diff --git a/config/example-extras.cfg b/config/example-extras.cfg
index a75f8b10..6adf0592 100644
--- a/config/example-extras.cfg
+++ b/config/example-extras.cfg
@@ -1249,19 +1249,46 @@
# using encoder. This parameter must be provided when using menu.
#click_pin:
# The pin connected to 'enter' button or encoder 'click'. This parameter
-# must be provided when using menu.
+# must be provided when using menu. The presence of an 'analog_range_click_pin'
+# config parameter turns this parameter from digital to analog.
#back_pin:
# The pin connected to 'back' button. This parameter is optional, menu
-# can be used without it.
+# can be used without it. The presence of an 'analog_range_back_pin'
+# config parameter turns this parameter from digital to analog.
#up_pin:
# The pin connected to 'up' button. This parameter must be provided
-# when using menu without encoder.
+# when using menu without encoder. The presence of an 'analog_range_up_pin'
+# config parameter turns this parameter from digital to analog.
#down_pin:
# The pin connected to 'down' button. This parameter must be provided
-# when using menu without encoder.
+# when using menu without encoder. The presence of an 'analog_range_down_pin'
+# config parameter turns this parameter from digital to analog.
#kill_pin:
-# The pin connected to 'kill' button. This button will call
-# emergency stop.
+# The pin connected to 'kill' button. This button will call emergency stop.
+# The presence of an 'analog_range_kill_pin' config parameter turns this
+# parameter from digital to analog.
+#analog_pullup_resistor: 4700
+# The resistance (in ohms) of the pullup attached to the analog button.
+# The default is 4700 ohms.
+#analog_pin_debug:
+# When enabled it will output analog (ADC) button readings to the log.
+# It's useful for finding analog button resistance range values.
+# The default is False (disabled)
+#analog_range_click_pin:
+# The resistance range for a 'enter' button. Range minimum and maximum
+# comma-separated values must be provided when using analog button.
+#analog_range_back_pin:
+# The resistance range for a 'back' button. Range minimum and maximum
+# comma-separated values must be provided when using analog button.
+#analog_range_up_pin:
+# The resistance range for a 'up' button. Range minimum and maximum
+# comma-separated values must be provided when using analog button.
+#analog_range_down_pin:
+# The resistance range for a 'down' button. Range minimum and maximum
+# comma-separated values must be provided when using analog button.
+#analog_range_kill_pin:
+# The resistance range for a 'kill' button. Range minimum and maximum
+# comma-separated values must be provided when using analog button.
# Custom thermistors (one may define any number of sections with a
diff --git a/klippy/extras/buttons.py b/klippy/extras/buttons.py
index 9ebd3adc..b06f1f83 100644
--- a/klippy/extras/buttons.py
+++ b/klippy/extras/buttons.py
@@ -7,6 +7,10 @@ import logging
QUERY_TIME = .002
RETRANSMIT_COUNT = 50
+ADC_REPORT_TIME = 0.015
+ADC_DEBOUNCE_TIME = 0.025
+ADC_SAMPLE_TIME = 0.001
+ADC_SAMPLE_COUNT = 6
# Rotary encoder handler https://github.com/brianlow/Rotary
# Copyright 2011 Ben Buxton (bb@cactii.net).
@@ -112,6 +116,79 @@ class MCU_buttons:
self.last_button = button
+class MCU_ADC_buttons:
+ def __init__(self, printer, pin, pullup, debug=False):
+ self.reactor = printer.get_reactor()
+ self.buttons = []
+ self.last_button = None
+ self.last_pressed = None
+ self.last_debouncetime = 0
+ self.pullup = pullup
+ self.debug = debug
+ self.pin = pin
+ self.min_value = self.max_value = None
+ ppins = printer.lookup_object('pins')
+ self.mcu_adc = ppins.setup_pin('adc', self.pin)
+ self.mcu_adc.setup_minmax(ADC_SAMPLE_TIME, ADC_SAMPLE_COUNT)
+ self.mcu_adc.setup_adc_callback(ADC_REPORT_TIME, self.adc_callback)
+
+ def setup_button(self, min_value, max_value, callback):
+ if self.min_value is None:
+ self.min_value = min_value
+ else:
+ self.min_value = min(self.min_value, min_value)
+
+ if self.max_value is None:
+ self.max_value = max_value
+ else:
+ self.max_value = max(self.max_value, max_value)
+
+ self.buttons.append((min_value, max_value, callback))
+
+ def adc_callback(self, read_time, read_value):
+ adc = max(.00001, min(.99999, read_value))
+ r = self.pullup * adc / (1.0 - adc)
+ self.reactor.register_async_callback(
+ (lambda e, s=self, v=r: s.handle_button(e, v)))
+
+ def get_button(self, value):
+ if (self.min_value is not None and self.max_value is not None
+ and self.min_value <= value <= self.max_value):
+ for i, (min_value, max_value, cb) in enumerate(self.buttons):
+ if min_value < value < max_value:
+ return i
+ return None
+
+ def handle_button(self, eventtime, value):
+ btn = self.get_button(int(value))
+
+ # If the button changed, due to noise or pressing:
+ if btn != self.last_button:
+ # reset the debouncing timer
+ self.last_debouncetime = eventtime
+
+ # button debounce check & new button pressed
+ if ((eventtime - self.last_debouncetime) >= ADC_DEBOUNCE_TIME
+ and self.last_button == btn and self.last_pressed != btn):
+ # release last_pressed
+ if self.last_pressed is not None:
+ self.call_button(eventtime, self.last_pressed, False)
+ self.last_pressed = None
+ if btn is not None:
+ self.call_button(eventtime, btn, True)
+ self.last_pressed = btn
+
+ self.last_button = btn
+ if self.debug is True:
+ logging.info(
+ "analog pin: %s value: %d" % (self.pin, int(value)))
+
+ def call_button(self, eventtime, button, state):
+ if button < len(self.buttons):
+ minval, maxval, callback = self.buttons[button]
+ callback(eventtime, state)
+
+
######################################################################
# Rotary Encoders
######################################################################
@@ -138,6 +215,20 @@ class PrinterButtons:
def __init__(self, config):
self.printer = config.get_printer()
self.mcu_buttons = {}
+ self.adc_buttons = {}
+ def register_adc_button(
+ self, pin, min_val, max_val, pullup, callback, debug=False):
+ adc_buttons = self.adc_buttons.get(pin)
+ if adc_buttons is None:
+ self.adc_buttons[pin] = adc_buttons = MCU_ADC_buttons(
+ self.printer, pin, pullup, debug)
+ adc_buttons.setup_button(min_val, max_val, callback)
+ def register_adc_button_push(
+ self, pin, min_val, max_val, pullup, callback, debug=False):
+ def helper(eventtime, state, callback=callback):
+ if state:
+ callback(eventtime)
+ self.register_adc_button(pin, min_val, max_val, pullup, helper, debug)
def register_buttons(self, pins, callback):
# Parse pins
ppins = self.printer.lookup_object('pins')
diff --git a/klippy/extras/display/menu.py b/klippy/extras/display/menu.py
index b3c0ea16..b3a24154 100644
--- a/klippy/extras/display/menu.py
+++ b/klippy/extras/display/menu.py
@@ -1000,7 +1000,21 @@ class MenuManager:
self.up_pin = config.get('up_pin', None)
self.down_pin = config.get('down_pin', None)
self.kill_pin = config.get('kill_pin', None)
+ # analog button ranges
+ self.analog_range_click_pin = config.get(
+ 'analog_range_click_pin', None)
+ self.analog_range_back_pin = config.get(
+ 'analog_range_back_pin', None)
+ self.analog_range_up_pin = config.get(
+ 'analog_range_up_pin', None)
+ self.analog_range_down_pin = config.get(
+ 'analog_range_down_pin', None)
+ self.analog_range_kill_pin = config.get(
+ 'analog_range_kill_pin', None)
self._last_click_press = 0
+ self.analog_pullup = config.getfloat(
+ 'analog_pullup_resistor', 4700., above=0.)
+ self.analog_pin_debug = config.getboolean('analog_pin_debug', False)
self._encoder_fast_rate = config.getfloat(
'encoder_fast_rate', .03, above=0.)
self._last_encoder_cw_eventtime = 0
@@ -1012,6 +1026,7 @@ class MenuManager:
self.printer.register_event_handler("klippy:ready", self.handle_ready)
# register buttons & encoder
if self.buttons:
+ # digital buttons
if self.encoder_pins:
try:
pin1, pin2 = self.encoder_pins.split(',')
@@ -1021,20 +1036,75 @@ class MenuManager:
pin1.strip(), pin2.strip(),
self.encoder_cw_callback, self.encoder_ccw_callback)
if self.click_pin:
- self.buttons.register_buttons(
- [self.click_pin], self.click_callback)
+ if self.analog_range_click_pin is not None:
+ try:
+ p_min, p_max = map(
+ float, self.analog_range_click_pin.split(','))
+ except Exception:
+ raise config.error(
+ "Unable to parse analog_range_click_pin")
+ self.buttons.register_adc_button(
+ self.click_pin, p_min, p_max, self.analog_pullup,
+ self.click_callback, self.analog_pin_debug)
+ else:
+ self.buttons.register_buttons(
+ [self.click_pin], self.click_callback)
if self.back_pin:
- self.buttons.register_button_push(
- self.back_pin, self.back_callback)
+ if self.analog_range_back_pin is not None:
+ try:
+ p_min, p_max = map(
+ float, self.analog_range_back_pin.split(','))
+ except Exception:
+ raise config.error(
+ "Unable to parse analog_range_back_pin")
+ self.buttons.register_adc_button_push(
+ self.back_pin, p_min, p_max, self.analog_pullup,
+ self.back_callback, self.analog_pin_debug)
+ else:
+ self.buttons.register_button_push(
+ self.back_pin, self.back_callback)
if self.up_pin:
- self.buttons.register_button_push(
- self.up_pin, self.up_callback)
+ if self.analog_range_up_pin is not None:
+ try:
+ p_min, p_max = map(
+ float, self.analog_range_up_pin.split(','))
+ except Exception:
+ raise config.error(
+ "Unable to parse analog_range_up_pin")
+ self.buttons.register_adc_button_push(
+ self.up_pin, p_min, p_max, self.analog_pullup,
+ self.up_callback, self.analog_pin_debug)
+ else:
+ self.buttons.register_button_push(
+ self.up_pin, self.up_callback)
if self.down_pin:
- self.buttons.register_button_push(
- self.down_pin, self.down_callback)
+ if self.analog_range_down_pin is not None:
+ try:
+ p_min, p_max = map(
+ float, self.analog_range_down_pin.split(','))
+ except Exception:
+ raise config.error(
+ "Unable to parse analog_range_down_pin")
+ self.buttons.register_adc_button_push(
+ self.down_pin, p_min, p_max, self.analog_pullup,
+ self.down_callback, self.analog_pin_debug)
+ else:
+ self.buttons.register_button_push(
+ self.down_pin, self.down_callback)
if self.kill_pin:
- self.buttons.register_button_push(
- self.kill_pin, self.kill_callback)
+ if self.analog_range_kill_pin is not None:
+ try:
+ p_min, p_max = map(
+ float, self.analog_range_kill_pin.split(','))
+ except Exception:
+ raise config.error(
+ "Unable to parse analog_range_kill_pin")
+ self.buttons.register_adc_button_push(
+ self.kill_pin, p_min, p_max, self.analog_pullup,
+ self.kill_callback, self.analog_pin_debug)
+ else:
+ self.buttons.register_button_push(
+ self.kill_pin, self.kill_callback)
# Add MENU commands
self.gcode.register_mux_command("MENU", "DO", 'dump', self.cmd_DO_DUMP,