[{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003cp\u003eThis demonstrates how to add a simple menu system to your project using a\u0026nbsp;single rotary encoder for input and the serial console for output.\u003c/p\u003e\n      \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","metadata":{}},{"element_type":"user_image","content":"https://cdn-learn.adafruit.com/user_assets/assets/000/001/089/original/qtpyS3_VCNL4040.jpeg?1722391308","metadata":{"caption":""}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003ch2\u003eHardware: Parts and Tools\u003c/h2\u003e\n      \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","metadata":{}},{"element_type":"product","content":"https://www.adafruit.com/product/5426","metadata":{}},{"element_type":"product","content":"https://www.adafruit.com/product/5880","metadata":{}},{"element_type":"product","content":"https://www.adafruit.com/product/4161","metadata":{}},{"element_type":"product","content":"https://www.adafruit.com/product/5537","metadata":{}},{"element_type":"product","content":"https://www.adafruit.com/product/4210","metadata":{}},{"element_type":"product","content":"https://www.adafruit.com/product/4210","metadata":{}},{"element_type":"parts","content":"\n  \u003cdiv class=\"parts-details\"\u003e\n    \u003cdiv\u003e\n      \u003cspan class=\"parts-quantity\"\u003e1\u003c/span\u003e\u003cspan\u003e x \u003c/span\u003e\n        \u003cspan class=\"parts-name\"\u003eTamiya Universal Plate Set #70157\u003c/span\u003e\n      \u003cdiv class=\"parts-description\"\u003e3mm thick, 160x60mm ABS plates with 3mm holes on 5mm grid\u003c/div\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cdiv class=\"parts-action\"\u003e\n  \u003cdiv\u003e\n\n  \u003c/div\u003e\n\u003c/div\u003e\n\n  \u003cdiv class=\"clearfix\"\u003e\u003c/div\u003e\n","metadata":{"name":"Tamiya Universal Plate Set #70157","url":null,"description":"3mm thick, 160x60mm ABS plates with 3mm holes on 5mm grid","quantity":"1"}},{"element_type":"parts","content":"\n  \u003cdiv class=\"parts-details\"\u003e\n    \u003cdiv\u003e\n      \u003cspan class=\"parts-quantity\"\u003e1\u003c/span\u003e\u003cspan\u003e x \u003c/span\u003e\n        \u003cspan class=\"parts-name\"\u003eM2.5 Nylon Standoff Set\u003c/span\u003e\n      \u003cdiv class=\"parts-description\"\u003emisc. M2.5 machine screws, standoffs, and nuts\u003c/div\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cdiv class=\"parts-action\"\u003e\n  \u003cdiv\u003e\n\n  \u003c/div\u003e\n\u003c/div\u003e\n\n  \u003cdiv class=\"clearfix\"\u003e\u003c/div\u003e\n","metadata":{"name":"M2.5 Nylon Standoff Set","url":null,"description":"misc. M2.5 machine screws, standoffs, and nuts","quantity":"1"}},{"element_type":"parts","content":"\n  \u003cdiv class=\"parts-details\"\u003e\n    \u003cdiv\u003e\n      \u003cspan class=\"parts-quantity\"\u003e1\u003c/span\u003e\u003cspan\u003e x \u003c/span\u003e\n        \u003cspan class=\"parts-name\"\u003eM3 Nylon Standoff Set\u003c/span\u003e\n      \u003cdiv class=\"parts-description\"\u003emisc. M3 machine screws, nuts, and washers\u003c/div\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cdiv class=\"parts-action\"\u003e\n  \u003cdiv\u003e\n\n  \u003c/div\u003e\n\u003c/div\u003e\n\n  \u003cdiv class=\"clearfix\"\u003e\u003c/div\u003e\n","metadata":{"name":"M3 Nylon Standoff Set","url":null,"description":"misc. M3 machine screws, nuts, and washers","quantity":"1"}},{"element_type":"parts","content":"\n  \u003cdiv class=\"parts-details\"\u003e\n    \u003cdiv\u003e\n      \u003cspan class=\"parts-quantity\"\u003e1\u003c/span\u003e\u003cspan\u003e x \u003c/span\u003e\n        \u003cspan class=\"parts-name\"\u003e1.5mm or 2mm Allen wrench or hex bit\u003c/span\u003e\n      \u003cdiv class=\"parts-description\"\u003eavailable in tool sets like Adafruit #5473, #829, or iFixit Pro Tech Toolkit\u003c/div\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cdiv class=\"parts-action\"\u003e\n  \u003cdiv\u003e\n\n  \u003c/div\u003e\n\u003c/div\u003e\n\n  \u003cdiv class=\"clearfix\"\u003e\u003c/div\u003e\n","metadata":{"name":"1.5mm or 2mm Allen wrench or hex bit","url":null,"description":"available in tool sets like Adafruit #5473, #829, or iFixit Pro Tech Toolkit","quantity":"1"}},{"element_type":"markdown","content":"\u003ch2\u003eUsing the Rotary Encoder Menu\u003c/h2\u003e\n\n\u003cp\u003eOnce you've assembled the hardware and installed all the code, \u003ccode class=\"inline\"\u003ecode.py\u003c/code\u003e will\nstart sending the main menu to the serial console. The menu uses ANSI escape\ncodes to show your current menu selection in reverse video. To change\nselections, turn the encoder knob. To perform the action for your current\nselection, click the knob.\u003c/p\u003e\n\n\u003cp\u003eThere are three menu items:\u003c/p\u003e\n\n\u003cul\u003e\n\u003cli\u003e\u003cp\u003e\"Show Proximity\": when clicked, this will show the VCNL4040's proximity\nsensor measurement until you click the encoder knob to return to the main\nmenu. Turning the knob in this mode has no effect.\u003c/p\u003e\u003c/li\u003e\n\u003cli\u003e\u003cp\u003e\"Show Lux\": when clicked, this will show the VCNL4040's ambient illumination\nsensor measurement until you click the encoder knob to return to the main\nmenu. Turning the knob in this mode has no effect.\u003c/p\u003e\u003c/li\u003e\n\u003cli\u003e\u003cp\u003e\"Set Threshold\": when clicked, this changes the integer value of the\nproximity threshold setting in the context dictionary. Turning the knob\nupdates the threshold immediately, subject to the high and low range limits.\nA proximity value of \u003ccode class=\"inline\"\u003e2\u003c/code\u003e means a reflective object is about 150 mm to 200 mm\naway from the sensor. A value of \u003ccode class=\"inline\"\u003e5\u003c/code\u003e is in the neighborhood of 100 mm to\n110 mm, and a value of 60 is about 10 mm. Clicking the knob returns to the\nmain menu.\u003c/p\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003cp\u003eThe proximity threshold controls the QT Py ESP32-S3's Neopixel. If you put a\nreflective object in front of the sensor at or below the threshold distance,\nthe Neopixel will light up. When you take the object away, the Neopixel will\nturn off. The Neopixel updates work in the main menu and the menu-item modes.\u003c/p\u003e\n\n\u003cp\u003eThis is a screenshot of a serial console window where I was navigating through\nthe menus using the rotary encoder:\u003c/p\u003e\n","metadata":{"markdown":"## Using the Rotary Encoder Menu\n\nOnce you've assembled the hardware and installed all the code, `code.py` will\nstart sending the main menu to the serial console. The menu uses ANSI escape\ncodes to show your current menu selection in reverse video. To change\nselections, turn the encoder knob. To perform the action for your current\nselection, click the knob.\n\nThere are three menu items:\n\n- \"Show Proximity\": when clicked, this will show the VCNL4040's proximity\n  sensor measurement until you click the encoder knob to return to the main\n  menu. Turning the knob in this mode has no effect.\n\n- \"Show Lux\": when clicked, this will show the VCNL4040's ambient illumination\n  sensor measurement until you click the encoder knob to return to the main\n  menu. Turning the knob in this mode has no effect.\n\n- \"Set Threshold\": when clicked, this changes the integer value of the\n  proximity threshold setting in the context dictionary. Turning the knob\n  updates the threshold immediately, subject to the high and low range limits.\n  A proximity value of `2` means a reflective object is about 150 mm to 200 mm\n  away from the sensor. A value of `5` is in the neighborhood of 100 mm to\n  110 mm, and a value of 60 is about 10 mm. Clicking the knob returns to the\n  main menu.\n\nThe proximity threshold controls the QT Py ESP32-S3's Neopixel. If you put a\nreflective object in front of the sensor at or below the threshold distance,\nthe Neopixel will light up. When you take the object away, the Neopixel will\nturn off. The Neopixel updates work in the main menu and the menu-item modes.\n\nThis is a screenshot of a serial console window where I was navigating through\nthe menus using the rotary encoder:"}},{"element_type":"user_image","content":"https://cdn-learn.adafruit.com/user_assets/assets/000/001/090/original/console-screenshot.jpeg?1722391326","metadata":{"caption":""}},{"element_type":"markdown","content":"\u003ch2\u003eUnderstanding Menu System Code\u003c/h2\u003e\n\n\u003cp\u003eThe actions for each selection item of the main menu correspond to a function\ndefined in \u003ccode class=\"inline\"\u003ecode.py\u003c/code\u003e. The menu structure and some other global state are stored\nin a context dictionary called \u003ccode class=\"inline\"\u003ectx\u003c/code\u003e, which looks like this:\u003c/p\u003e\n","metadata":{"markdown":"## Understanding Menu System Code\n\nThe actions for each selection item of the main menu correspond to a function\ndefined in `code.py`. The menu structure and some other global state are stored\nin a context dictionary called `ctx`, which looks like this:"}},{"element_type":"code","content":"# The context dictionary holds shared data used by several\n# functions. You could use a class or individual variables for\n# this. But, for simple prototyping, a dictionary makes it easy\n# to try ideas quickly without typing lots of boilerplate code.\n# You can read about declaring dictionary literals at:\n#   https://docs.python.org/3/library/stdtypes.html#dict\n#\n# For the menu item list, each entry should be a tuple of\n# (name, callable object). The name gets used by the function\n# that prints the current menu selection. The callable object\n# gets used when you pick a menu item. In Python, functions and\n# methods are callable objects. You can call them with a `()`\n# after their name, or you can assign them to variables by\n# omitting the `()`.\n#\nctx = {\n    'menu': [             # Navigation menu\n        ('Show Proxmity', showProx),\n        ('Show Lux',      showLux),\n        ('Set Threshold', setThresh),\n    ],\n    'enc': enc,           # Encoder object for submenus to use\n    'vcnl': vcnl,         # VCNL4040 object for submenus to use\n    'np': np,             # Neopixel pin (DigitalInOut)\n    'newline': True,      # Should menu start on a new line?\n    'selection': 0,       # Menu selection index\n    'threshold': 4,       # Proximity threshold (range 2..60)\n}","metadata":{"language":"python","linenums":false}},{"element_type":"markdown","content":"\u003cp\u003eThe main event loop looks like this:\u003c/p\u003e\n","metadata":{"markdown":"The main event loop looks like this:"}},{"element_type":"code","content":"prevClick = False\nwhile True:\n    sleep(0.03)  # poll at 30 Hz so knob feels responsive\n    showMenu('Main', ctx)\n    # Read the rotary encoder\n    click = enc.clicked()\n    delta = enc.delta()\n    # Handle knob click (edge trigger on pressed -\u003e released)\n    if (not click) and (click != prevClick):\n        doAction(ctx)\n    prevClick = click\n    # Handle knob turn\n    if delta != 0:\n        select(delta, ctx)\n    # Update LED\n    updateNeopixel(ctx)","metadata":{"language":"python","linenums":false}},{"element_type":"markdown","content":"\u003cp\u003eThe \u003ccode class=\"inline\"\u003eshowMenu()\u003c/code\u003e function prints the current selection (\u003ccode class=\"inline\"\u003ectx['selection']\u003c/code\u003e).\nThe \u003ccode class=\"inline\"\u003edoAction()\u003c/code\u003e function uses the selection and the menu list (\u003ccode class=\"inline\"\u003ectx['menu']\u003c/code\u003e)\nto call the appropriate handler function when the encoder knob is clicked.\u003c/p\u003e\n\n\u003cp\u003eThe menu action handler functions, \u003ccode class=\"inline\"\u003eshowProx()\u003c/code\u003e, \u003ccode class=\"inline\"\u003eshowLux()\u003c/code\u003e, and\n\u003ccode class=\"inline\"\u003esetThresh()\u003c/code\u003e, each have an event loop similar to the main event loop. But, for\nthe handler functions, rotary encoder input is used to stop showing sensor\nreadings and return to the main menu (\u003ccode class=\"inline\"\u003eshowProx\u003c/code\u003e and \u003ccode class=\"inline\"\u003eshowLux\u003c/code\u003e) or to edit the\ninteger value of a setting (\u003ccode class=\"inline\"\u003esetThresh\u003c/code\u003e).\u003c/p\u003e\n\n\u003cp\u003eIf you wanted to extend the menu system, you could add more items to the\n\u003ccode class=\"inline\"\u003ectx['menu']\u003c/code\u003e list.\u003c/p\u003e\n","metadata":{"markdown":"The `showMenu()` function prints the current selection (`ctx['selection']`).\nThe `doAction()` function uses the selection and the menu list (`ctx['menu']`)\nto call the appropriate handler function when the encoder knob is clicked.\n\nThe menu action handler functions, `showProx()`, `showLux()`, and\n`setThresh()`, each have an event loop similar to the main event loop. But, for\nthe handler functions, rotary encoder input is used to stop showing sensor\nreadings and return to the main menu (`showProx` and `showLux`) or to edit the\ninteger value of a setting (`setThresh`).\n\nIf you wanted to extend the menu system, you could add more items to the\n`ctx['menu']` list."}},{"element_type":"markdown","content":"\u003ch2\u003eAssembling the Hardware\u003c/h2\u003e\n\n\u003col\u003e\n\u003cli\u003e\u003cp\u003eAttach Qt Py to backplate with M3 machine screws, nuts, and washers\u003c/p\u003e\u003c/li\u003e\n\u003cli\u003e\u003cp\u003eAttach knob to rotary encoder shaft. You will need a small Allen wrench or\nhex bit to tighten the knob's set screw. Before tightening the set screw, be\nsure there is a small gap between the bottom of the knob and the threads of\nthe encoder housing. If the knob is bottomed out, it will be hard to turn.\u003c/p\u003e\u003c/li\u003e\n\u003cli\u003e\u003cp\u003eConnect rotary encoder to the QT Py with a STEMMA QT cable\u003c/p\u003e\u003c/li\u003e\n\u003cli\u003e\u003cp\u003eAttach rotary encoder to backplate with M2.5 standoffs, machine screws, and\nnuts\u003c/p\u003e\u003c/li\u003e\n\u003cli\u003e\u003cp\u003eConnect proximity sensor to the rotary encoder with a STEMMA QT cable\u003c/p\u003e\u003c/li\u003e\n\u003cli\u003e\u003cp\u003eAttach proximity sensor to backplate with M2.5 standoffs, machine screws,\nand nuts\u003c/p\u003e\u003c/li\u003e\n\u003c/ol\u003e\n","metadata":{"markdown":"## Assembling the Hardware\n\n1. Attach Qt Py to backplate with M3 machine screws, nuts, and washers\n\n2. Attach knob to rotary encoder shaft. You will need a small Allen wrench or\n   hex bit to tighten the knob's set screw. Before tightening the set screw, be\n   sure there is a small gap between the bottom of the knob and the threads of\n   the encoder housing. If the knob is bottomed out, it will be hard to turn.\n\n3. Connect rotary encoder to the QT Py with a STEMMA QT cable\n\n4. Attach rotary encoder to backplate with M2.5 standoffs, machine screws, and\n   nuts\n\n5. Connect proximity sensor to the rotary encoder with a STEMMA QT cable\n\n6. Attach proximity sensor to backplate with M2.5 standoffs, machine screws,\n   and nuts\n"}},{"element_type":"markdown","content":"\u003ch2\u003eUpdating CircuitPython\u003c/h2\u003e\n\n\u003col\u003e\n\u003cli\u003e\u003cp\u003eDownload the CircuitPython 9.1.1 .UF2 file from the\n\u003ca href=\"https://circuitpython.org/board/adafruit_qtpy_esp32s3_nopsram/\"\u003eAdafruit QT Py ESP32-S3 No PSRAM\u003c/a\u003e\npage on circuitpython.org\u003c/p\u003e\u003c/li\u003e\n\u003cli\u003e\u003cp\u003eFollow the instructions in the\n\u003ca href=\"https://learn.adafruit.com/adafruit-qt-py-esp32-s3/circuitpython-2#circuitpython-quickstart-3106398\"\u003eCircuitPython Quickstart\u003c/a\u003e\nsection of the Adafruit QT Py ESP32-S3 learn guide to update your board with\nCircuitPython 9.1.1\u003c/p\u003e\u003c/li\u003e\n\u003c/ol\u003e\n","metadata":{"markdown":"## Updating CircuitPython\n\n1. Download the CircuitPython 9.1.1 .UF2 file from the\n   [Adafruit QT Py ESP32-S3 No PSRAM](https://circuitpython.org/board/adafruit_qtpy_esp32s3_nopsram/)\n   page on circuitpython.org\n\n2. Follow the instructions in the\n   [CircuitPython Quickstart](https://learn.adafruit.com/adafruit-qt-py-esp32-s3/circuitpython-2#circuitpython-quickstart-3106398)\n   section of the Adafruit QT Py ESP32-S3 learn guide to update your board with\n   CircuitPython 9.1.1\n"}},{"element_type":"markdown","content":"\u003ch2\u003eInstalling CircuitPython Code\u003c/h2\u003e\n\n\u003cp\u003eTo copy the project bundle files to your CIRCUITPY drive:\u003c/p\u003e\n\n\u003col\u003e\n\u003cli\u003e\u003cp\u003eDownload the project bundle .zip file using the button below.\u003c/p\u003e\u003c/li\u003e\n\u003cli\u003e\u003cp\u003eExpand the zip file by opening it, or use \u003ccode class=\"inline\"\u003eunzip\u003c/code\u003e in a Terminal. You should\nend up with a folder named prox-sensor-encoder-menu, which should contain a\n\u003ccode class=\"inline\"\u003eREADME.txt\u003c/code\u003e file and a \u003ccode class=\"inline\"\u003eCircuitPython 9.x\u003c/code\u003e folder.\u003c/p\u003e\u003c/li\u003e\n\u003cli\u003e\u003cp\u003eOpen the CircuitPython 9.x folder and copy all of its contents to your\nCIRCUITPY drive.\u003c/p\u003e\u003c/li\u003e\n\u003c/ol\u003e\n\n\u003cp\u003eTo learn more about copying libraries to your CIRCUITPY drive, check out the\n\u003ca href=\"https://learn.adafruit.com/welcome-to-circuitpython/circuitpython-libraries\"\u003eCircuitPython Libraries\u003c/a\u003e\nsection of the\n\u003ca href=\"https://learn.adafruit.com/welcome-to-circuitpython\"\u003eWelcome to CircuitPython!\u003c/a\u003e\nlearn guide.\u003c/p\u003e\n","metadata":{"markdown":"## Installing CircuitPython Code\n\nTo copy the project bundle files to your CIRCUITPY drive:\n\n1. Download the project bundle .zip file using the button below.\n\n2. Expand the zip file by opening it, or use `unzip` in a Terminal. You should\n   end up with a folder named prox-sensor-encoder-menu, which should contain a\n   `README.txt` file and a `CircuitPython 9.x` folder.\n\n3. Open the CircuitPython 9.x folder and copy all of its contents to your\n   CIRCUITPY drive.\n\nTo learn more about copying libraries to your CIRCUITPY drive, check out the\n[CircuitPython Libraries](https://learn.adafruit.com/welcome-to-circuitpython/circuitpython-libraries)\nsection of the\n[Welcome to CircuitPython!](https://learn.adafruit.com/welcome-to-circuitpython)\nlearn guide.\n"}},{"element_type":"button","content":"https://github.com/samblenny/prox-sensor-encoder-menu/releases/download/v0.1.0/prox-sensor-encoder-menu-bd254eb.zip","metadata":{"class":"btn btn-large btn-block btn-primary","button_type":"primary","target":"_blank","zip_folder":"false"}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003ch3\u003eCIRCUITPY/code.py\u003c/h3\u003e\n      \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","metadata":{}},{"element_type":"embed","content":"https://github.com/samblenny/prox-sensor-encoder-menu/blob/v0.1.0/code.py"}]