[{"element_type":"user_image","content":"https://cdn-learn.adafruit.com/user_assets/assets/000/001/021/original/DAHX-Comp.jpg?1721826396","metadata":{"caption":"Dirty Air Heat Exchanger (DAHX) display stand."}},{"element_type":"user_image","content":"https://cdn-learn.adafruit.com/user_assets/assets/000/001/017/original/HX_animation_-_Compressed.gif?1721741422","metadata":{"caption":"Heat Flow!"}},{"element_type":"faq","content":" Hot to cold, now you can visualise the flow! \n","metadata":{"markdown":"Hot to cold, now you can visualise the flow!"}},{"element_type":"user_image","content":"https://cdn-learn.adafruit.com/user_assets/assets/000/001/015/original/Illuminated_DAHX-mini-mini.jpg?1721740725","metadata":{"caption":"DAHX Heat Flow Code in Action"}},{"element_type":"faq","content":" Circuit Python, Libraries and Code \n","metadata":{"markdown":"Circuit Python, Libraries and Code"}},{"element_type":"code","content":"# SPDX-FileCopyrightText: 2023 Jeff Epler for Adafruit Industries\n# SPDX-License-License: MIT\n\n# Version 11\n# Modified by Warmer_Summers\n\n# Hardware:\n# - Adafruit RP2040 Scorpio\n# - NeoKey FeatherWing\n\n# Code Function:\n# This code integrates a NeoKey RP2040 FeatherWing with NeoPixel animations using CircuitPython.\n# It allows the user to enter and exit a brightness control menu by holding down both buttons,\n# and cycle through different animations using the left and right keys.\n# Additionally, a reboot sequence is initiated by holding both keys for 15 seconds.\n\n# Service Menus and Button Functionality:\n# - Press both keys simultaneously for 5 seconds to enter the brightness menu.\n# - In the brightness menu, the left key decreases brightness and the right key increases brightness in increments of 0.1.\n# - NeoPixels behind the keys indicate the current brightness setting.\n# - The main LED strands are turned off during brightness adjustment.\n# - Press both keys simultaneously to exit the brightness menu.\n# - Press the left key to move to the previous animation.\n# - Press the right key to move to the next animation.\n# - Hold both keys for 15 seconds to initiate a reboot sequence, indicated by four green flashes.\n\n# Optimisations:\n# - Added garbage collection to manage memory usage and avoid memory allocation errors.\n# - Optimised the creation of custom animations (DAHX Heat Chase and DAHX Heat Rainbow) to use less memory.\n# - Ensured consistent application of the colour map across the entire length of the LED strips.\n# - Improved key press handling to prevent sluggish behaviour.\n# - Memory logging is performed only once at startup and after switching animations.\n# - Introduced dynamic instantiation of animations using lambda functions to better manage memory.\n\nimport time\nimport board\nimport microcontroller\nimport gc\nfrom digitalio import DigitalInOut, Pull\nfrom adafruit_debouncer import Debouncer\nfrom adafruit_led_animation.animation.chase import Chase\nfrom adafruit_led_animation.animation.comet import Comet\nfrom adafruit_led_animation.animation.rainbow import Rainbow\nfrom adafruit_led_animation.animation.sparkle import Sparkle\nfrom adafruit_led_animation.animation.pulse import Pulse\nfrom adafruit_led_animation.animation.blink import Blink\nfrom adafruit_led_animation.animation import Animation\nfrom adafruit_led_animation.color import (RED, YELLOW, ORANGE, GREEN, TEAL, CYAN, BLUE, PURPLE, MAGENTA, WHITE, BLACK, GOLD, PINK, AQUA, JADE, AMBER, OLD_LACE)\nfrom adafruit_led_animation.group import AnimationGroup\nfrom adafruit_neopxl8 import NeoPxl8\nfrom adafruit_pixelmap import PixelMap\nimport neopixel\n\nprint(\"- DexThermic HX Brightness Control and Animations -\")\ntime.sleep(1)  # Sleep for a bit to avoid a race condition on some systems\n\n# ----- Key setup ----- #\nswitch_a_in = DigitalInOut(board.D5)\nswitch_b_in = DigitalInOut(board.D6)\nswitch_a_in.pull = Pull.UP\nswitch_b_in.pull = Pull.UP\nswitch_a = Debouncer(switch_a_in)\nswitch_b = Debouncer(switch_b_in)\n\n# ----- LED Setup ----- #\nstrand_length = 250  # Set strand length to 250 LEDs\npixel_brightness = 0.2  # Set initial brightness to 0.2\nnum_strands = 8\n\nnum_pixels = num_strands * strand_length\npixels = NeoPxl8(board.NEOPIXEL0, num_pixels, auto_write=False, brightness=pixel_brightness)\n\ndef strand(n):\n    return PixelMap(\n        pixels,\n        tuple(range(n * strand_length, (n + 1) * strand_length)),\n        individual_pixels=True,\n    )\n\nstrips = [strand(i) for i in range(num_strands)]\n\n# ----- Key Pixel Setup ----- #\npixel_pin = board.D9\nkey_pixels = neopixel.NeoPixel(pixel_pin, 2, brightness=0.9)  # Set brightness to 0.9 for key pixels\n\n# Define the colour relationship\nleft_key_color = BLUE\nright_key_color = RED\nstrip_color = GOLD\n\nbrightness_menu_active = False\nbrightness_value = 0.2  # Initial brightness value\nbrightness_step = 0.1\nhold_start_time = None\nkeys_released = False\nflash_start_time = None\nflash_state = False\ncooldown_end_time = None\n\ndef clear_all_strips():\n    for strip in strips:\n        strip.fill(0)\n    pixels.show()\n\ndef fill_all_strips(color):\n    for strip in strips:\n        strip.fill(color)\n    pixels.show()\n\n# Function to create a gradient from a list of colours with adjusted distribution\ndef create_color_gradient(colors, length, distribution=None):\n    if distribution is None:\n        distribution = [1] * len(colors)\n    gradient = []\n    total = sum(distribution)\n    n = len(colors)\n    for i in range(length):\n        pos = (i / (length - 1)) * total\n        left_color_index = 0\n        while pos \u003e distribution[left_color_index]:\n            pos -= distribution[left_color_index]\n            left_color_index += 1\n        right_color_index = min(left_color_index + 1, n - 1)\n        blend = pos / distribution[left_color_index]\n        left_color = colors[left_color_index]\n        right_color = colors[right_color_index]\n        r = int(left_color[0] * (1 - blend) + right_color[0] * blend)\n        g = int(left_color[1] * (1 - blend) + right_color[1] * blend)\n        b = int(left_color[2] * (1 - blend) + right_color[2] * blend)\n        gradient.append((r, g, b))\n    return gradient\n\n# Create a gradient for the strand length with custom distribution\ngradient_colors = [RED, ORANGE, YELLOW, TEAL, BLUE]\ndistribution = [1, 1, 1, 1, 2]  # Give more space to blue\ngradient = create_color_gradient(gradient_colors, strand_length, distribution)\n\n# Custom Animation class to use gradient colours\nclass DAHXHeatChase(Animation):\n    def __init__(self, pixel_object, speed, spacing, size, colors, name=None):\n        super().__init__(pixel_object, speed, color=colors[0], name=name)\n        self.colors = colors\n        self.size = size\n        self.spacing = spacing\n        self.current_pixel = 0\n\n    def draw(self):\n        self.pixel_object.fill((0, 0, 0))  # Clear the strip\n        for i in range(0, len(self.pixel_object), self.spacing + self.size):\n            for j in range(self.size):\n                pixel_index = (self.current_pixel + i + j) % len(self.pixel_object)\n                color_index = (pixel_index) % len(self.colors)\n                self.pixel_object[pixel_index] = self.colors[color_index]\n        self.current_pixel = (self.current_pixel + 1) % (self.spacing + self.size)\n\nclass DAHXHeatRainbow(Animation):\n    def __init__(self, pixel_object, speed, colors, name=None):\n        super().__init__(pixel_object, speed, color=colors[0], name=name)\n        self.colors = colors\n\n    def draw(self):\n        for i in range(len(self.pixel_object)):\n            color_index = i % len(self.colors)\n            self.pixel_object[i] = self.colors[color_index]\n\n# Animation setup\nanimation_definitions = [\n    (\"Rainbow\", lambda: AnimationGroup(\n        Rainbow(strips[0], speed=0.1, period=2, precompute_rainbow=True),\n        Rainbow(strips[1], speed=0.1, period=2, precompute_rainbow=True),\n        Rainbow(strips[2], speed=0.1, period=2, precompute_rainbow=True),\n        Rainbow(strips[3], speed=0.1, period=2, precompute_rainbow=True),\n        Rainbow(strips[4], speed=0.1, period=2, precompute_rainbow=True),\n        Rainbow(strips[5], speed=0.1, period=2, precompute_rainbow=True),\n        Rainbow(strips[6], speed=0.1, period=2, precompute_rainbow=True),\n        Rainbow(strips[7], speed=0.1, period=2, precompute_rainbow=True),\n    )),\n    (\"Chase\", lambda: AnimationGroup(\n        Chase(strips[0], speed=0.05, color=BLUE, size=5, spacing=5),\n        Chase(strips[1], speed=0.1, color=RED, size=5, spacing=5),\n        Chase(strips[2], speed=0.1, color=BLUE, size=5, spacing=5),\n        Chase(strips[3], speed=0.1, color=RED, size=5, spacing=5),\n        Chase(strips[4], speed=0.1, color=BLUE, size=5, spacing=5),\n        Chase(strips[5], speed=0.1, color=RED, size=5, spacing=5),\n        Chase(strips[6], speed=0.1, color=BLUE, size=5, spacing=5),\n        Chase(strips[7], speed=0.1, color=RED, size=5, spacing=5),\n    )),\n    (\"Comet\", lambda: AnimationGroup(\n        Comet(strips[0], speed=0.02, color=RED, tail_length=65, bounce=False),\n        Comet(strips[1], speed=0.05, color=YELLOW, tail_length=45, bounce=False),\n        Comet(strips[2], speed=0.02, color=ORANGE, tail_length=55, bounce=False),\n        Comet(strips[3], speed=0.05, color=RED, tail_length=65, bounce=False),\n        Comet(strips[4], speed=0.02, color=YELLOW, tail_length=45, bounce=False),\n        Comet(strips[5], speed=0.05, color=ORANGE, tail_length=55, bounce=False),\n        Comet(strips[6], speed=0.02, color=RED, tail_length=65, bounce=False),\n        Comet(strips[7], speed=0.05, color=YELLOW, tail_length=55, bounce=False),\n    )),\n    (\"Sparkle\", lambda: AnimationGroup(\n        Sparkle(strips[0], speed=0.1, color=TEAL, num_sparkles=10),\n        Sparkle(strips[1], speed=0.1, color=BLUE, num_sparkles=10),\n        Sparkle(strips[2], speed=0.1, color=YELLOW, num_sparkles=10),\n        Sparkle(strips[3], speed=0.1, color=TEAL, num_sparkles=10),\n        Sparkle(strips[4], speed=0.1, color=BLUE, num_sparkles=10),\n        Sparkle(strips[5], speed=0.1, color=YELLOW, num_sparkles=10),\n        Sparkle(strips[6], speed=0.1, color=TEAL, num_sparkles=10),\n        Sparkle(strips[7], speed=0.1, color=BLUE, num_sparkles=10),\n    )),\n    (\"Pulse\", lambda: AnimationGroup(\n        Pulse(strips[0], speed=0.1, color=GOLD, period=4),\n        Pulse(strips[1], speed=0.1, color=AMBER, period=3),\n        Pulse(strips[2], speed=0.1, color=YELLOW, period=5),\n        Pulse(strips[3], speed=0.1, color=GOLD, period=4),\n        Pulse(strips[4], speed=0.1, color=AMBER, period=3),\n        Pulse(strips[5], speed=0.1, color=YELLOW, period=5),\n        Pulse(strips[6], speed=0.1, color=GOLD, period=4),\n        Pulse(strips[7], speed=0.1, color=AMBER, period=3),\n    )),\n    (\"Blink\", lambda: AnimationGroup(\n        Blink(strips[0], speed=0.05, color=PURPLE),\n        Blink(strips[1], speed=0.1, color=MAGENTA),\n        Blink(strips[2], speed=0.08, color=PINK),\n        Blink(strips[3], speed=0.1, color=PURPLE),\n        Blink(strips[4], speed=0.05, color=MAGENTA),\n        Blink(strips[5], speed=0.08, color=PINK),\n        Blink(strips[6], speed=0.05, color=PURPLE),\n        Blink(strips[7], speed=0.1, color=MAGENTA),\n    )),\n    (\"DAHX Heat Chase\", lambda: AnimationGroup(\n        DAHXHeatChase(strips[0], speed=0.1, spacing=5, size=5, colors=gradient),\n        DAHXHeatChase(strips[1], speed=0.1, spacing=5, size=5, colors=gradient),\n        DAHXHeatChase(strips[2], speed=0.1, spacing=5, size=5, colors=gradient),\n        DAHXHeatChase(strips[3], speed=0.1, spacing=5, size=5, colors=gradient),\n        DAHXHeatChase(strips[4], speed=0.1, spacing=5, size=5, colors=gradient),\n        DAHXHeatChase(strips[5], speed=0.1, spacing=5, size=5, colors=gradient),\n        DAHXHeatChase(strips[6], speed=0.1, spacing=5, size=5, colors=gradient),\n        DAHXHeatChase(strips[7], speed=0.1, spacing=5, size=5, colors=gradient),\n    )),\n    (\"DAHX Heat Rainbow\", lambda: AnimationGroup(\n        DAHXHeatRainbow(strips[0], speed=0.1, colors=gradient),\n        DAHXHeatRainbow(strips[1], speed=0.1, colors=gradient),\n        DAHXHeatRainbow(strips[2], speed=0.1, colors=gradient),\n        DAHXHeatRainbow(strips[3], speed=0.1, colors=gradient),\n        DAHXHeatRainbow(strips[4], speed=0.1, colors=gradient),\n        DAHXHeatRainbow(strips[5], speed=0.1, colors=gradient),\n        DAHXHeatRainbow(strips[6], speed=0.1, colors=gradient),\n        DAHXHeatRainbow(strips[7], speed=0.1, colors=gradient),\n    )),\n]\n\nanimation_index = 0\ncurrent_animation = animation_definitions[animation_index][1]()  # Instantiate initial animation\n\n# Set initial colours\nfill_all_strips(strip_color)\nkey_pixels[0] = left_key_color\nkey_pixels[1] = right_key_color\n\n# Function to log memory usage\ndef log_memory_usage():\n    free_memory = gc.mem_free()\n    allocated_memory = gc.mem_alloc()\n    print(f\"Memory Info - Free: {free_memory} Allocated: {allocated_memory}\")\n\nlog_memory_usage()\n\nwhile True:\n    switch_a.update()  # Debouncer checks for changes in switch state\n    switch_b.update()\n\n    current_time = time.monotonic()\n\n    # Check if we're in the cooldown period\n    if cooldown_end_time and current_time \u003c cooldown_end_time:\n        continue\n    else:\n        cooldown_end_time = None\n\n    # Check if both buttons are pressed simultaneously\n    if not switch_a.value and not switch_b.value:\n        if hold_start_time is None:\n            hold_start_time = current_time\n            flash_start_time = current_time\n            print(\"Both keys pressed, starting hold timer\")\n        elif hold_start_time and current_time - hold_start_time \u003e= 15:\n            print(\"Initiating reboot sequence\")\n            for _ in range(4):\n                key_pixels.fill(GREEN)\n                time.sleep(0.25)\n                key_pixels.fill((0, 0, 0))\n                time.sleep(0.25)\n            microcontroller.reset()\n        elif hold_start_time and current_time - hold_start_time \u003e= 5 and not brightness_menu_active:\n            brightness_menu_active = True\n            hold_start_time = None\n            keys_released = False\n            flash_start_time = None\n            flash_state = False\n            print(\"Entering brightness menu\")\n            key_pixels.fill(WHITE)\n            clear_all_strips()  # Clear the main LED strands\n        elif not brightness_menu_active and current_time - flash_start_time \u003e= 1:\n            flash_start_time = current_time\n            flash_state = not flash_state\n            key_pixels[0] = WHITE if flash_state else left_key_color\n            key_pixels[1] = WHITE if flash_state else right_key_color\n    else:\n        if hold_start_time is not None and not brightness_menu_active:\n            hold_start_time = None\n            flash_start_time = None\n            print(\"Keys released before 5 seconds, resetting hold timer\")\n\n    if brightness_menu_active:\n        if keys_released:\n            if not switch_a.value and not switch_b.value:\n                brightness_menu_active = False\n                key_pixels[0] = left_key_color\n                key_pixels[1] = right_key_color\n                pixels.brightness = brightness_value\n                fill_all_strips(strip_color)\n                cooldown_end_time = current_time + 2  # Set cooldown period for 2 seconds\n                print(\"Exiting brightness menu\")\n            elif switch_a.fell:\n                brightness_value = max(0.1, brightness_value - brightness_step)\n                brightness_value = round(brightness_value, 1)\n                key_pixels.brightness = brightness_value\n                key_pixels.fill(WHITE)\n                key_pixels.show()\n                print(f\"Decreasing brightness: {brightness_value}\")\n            elif switch_b.fell:\n                brightness_value = min(1.0, brightness_value + brightness_step)\n                brightness_value = round(brightness_value, 1)\n                key_pixels.brightness = brightness_value\n                key_pixels.fill(WHITE)\n                key_pixels.show()\n                print(f\"Increasing brightness: {brightness_value}\")\n        else:\n            keys_released = switch_a.rose or switch_b.rose\n            if keys_released:\n                print(\"Keys released in brightness menu, now ready for brightness adjustment\")\n    else:\n        # Animation control\n        if switch_a.fell and (switch_a.value or switch_b.value):\n            animation_index = (animation_index - 1) % len(animation_definitions)\n            print(f\"Switching to animation: {animation_definitions[animation_index][0]}\")\n            log_memory_usage()\n            gc.collect()  # Force garbage collection\n            current_animation = animation_definitions[animation_index][1]()  # Instantiate animation\n        if switch_b.fell and (switch_a.value or switch_b.value):\n            animation_index = (animation_index + 1) % len(animation_definitions)\n            print(f\"Switching to animation: {animation_definitions[animation_index][0]}\")\n            log_memory_usage()\n            gc.collect()  # Force garbage collection\n            current_animation = animation_definitions[animation_index][1]()  # Instantiate animation\n\n        current_animation.animate()\n        pixels.show()","metadata":{"language":"auto","linenums":false}},{"element_type":"faq","content":" Rough Circuit Diagram \n","metadata":{"markdown":"Rough Circuit Diagram"}},{"element_type":"user_image","content":"https://cdn-learn.adafruit.com/user_assets/assets/000/001/018/original/led_strips_circuit-diagram.jpg?1721826008","metadata":{"caption":""}},{"element_type":"faq","content":" Layout \n","metadata":{"markdown":"Layout"}},{"element_type":"user_image","content":"https://cdn-learn.adafruit.com/user_assets/assets/000/001/019/original/MCU-Comp.jpg?1721826328","metadata":{"caption":"MCU Control unit"}},{"element_type":"user_image","content":"https://cdn-learn.adafruit.com/user_assets/assets/000/001/020/original/PSU-MCU-Compressed.jpg?1721826365","metadata":{"caption":"MCU and PSU"}},{"element_type":"user_image","content":"https://cdn-learn.adafruit.com/user_assets/assets/000/001/022/original/Solidworks-MCU.png?1721826760","metadata":{"caption":"Solidworks MCU - For 3d FDM printing"}},{"element_type":"user_image","content":"https://cdn-learn.adafruit.com/user_assets/assets/000/001/023/original/Solidworks-PSU.png?1721826794","metadata":{"caption":"Solidworks PSU - For 3D FDM printing"}}]