commit 8f3bce87434cf9d20eb10effbe5e73e3535e1a2e
Author: Gerd Beuster <gerd@frombelow.net>
Date: Tue, 23 Mar 2021 22:31:24 +0100
First public version
Diffstat:
21 files changed, 2813 insertions(+), 0 deletions(-)
diff --git a/.gitignore b/.gitignore
@@ -0,0 +1,3 @@
+build/
+sdkconfig
+sdkconfig.old+
\ No newline at end of file
diff --git a/CMakeLists.txt b/CMakeLists.txt
@@ -0,0 +1,6 @@
+# The following lines of boilerplate have to be in your project's
+# CMakeLists in this exact order for cmake to work correctly
+cmake_minimum_required(VERSION 3.5)
+
+include($ENV{IDF_PATH}/tools/cmake/project.cmake)
+project(ledstrip)
diff --git a/LICENSE b/LICENSE
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/Makefile b/Makefile
@@ -0,0 +1,9 @@
+#
+# This is a project Makefile. It is assumed the directory this Makefile resides in is a
+# project subdirectory.
+#
+
+PROJECT_NAME := ledstrip
+
+include $(IDF_PATH)/make/project.mk
+
diff --git a/README.md b/README.md
@@ -0,0 +1,104 @@
+Blinkenstrip
+------------
+
+# Overview
+
+Shows effects on a WS2811 connected to an ESP32. The ESP runs a
+minimal web server allowing to control the effects. The system is
+highly configurable: It can show effects both on LED strips of
+arbitrary length and on LEDs arranged as a matrix.
+
+# Installation & Operation
+
+Connect the data line of the WS2811 strip to pin 4 of the
+ESP32. Install the software the usual way. When powered up, the ESP
+spawns an AP with ESSID "blinkenstrip" and password
+"blinkenstrip". Connect to the AP and access the web interface at
+<http://192.168.4.1>. <http://192.168.4.1/config.html> gives access to
+system configuration. Here you can set the layout of your
+strip/matrix, and configure the system to connect to an existing
+network instead of an access point. If you configure your ESP to join
+an existing network and don't know its IP address, run `idf.py
+monitor` and check the debug output.
+
+# Effects
+
+- Sequence
+
+ All LEDs show the same, changing color. This is a nice effect if you
+ want to illuminate an object.
+
+- Blink
+
+ Individual LEDs switch randomly between blue and green color. This
+ effect looks quite nice both on LED strips and on matrices.
+
+- Running Dot
+
+ An individual red dot "running" on a blue backdrop.
+
+- Cellular Automaton
+
+ Conway's game of life. While designed for LED matrices, this effect
+ also look surpringsly nice on strips.
+
+- Changing lines
+
+ This is more of a test effect for LED matrices.
+
+- Worms
+
+ A configurable number of worms eating blue dots. This is the
+ recommended effect for LED strips. The number of worms can be set in
+ the configuration menu.
+
+# Changing defaults
+
+If you want different default values on first startup (i.e. before
+accessing <http://192.168.4.1/config.html>), you can set different
+values in sub-menu "Blinkenstrip" of the system configuration executed by
+`idfy.py menuconfig`.
+
+
+# Factory reset & Hardware setup
+
+If pins 12 and 13 are connected on reset, the user configuration is
+deleted and the factory default values are loaded.
+
+For installations with a large number of LEDs, it is recommend to
+chain a suitable fuse (e.g. 6 A for 250 LEDs) into the power line of
+the LED strip/matrix.
+
+Directory `case/` provides an OpenSCAD file for a printable case. This
+case is designed to host a ridiciously overpowered 110 W Mean Well
+LRS-150F-5 power supply.
+
+# Bugs
+
+HTML characters in names (e.g. spaces) are not translated to ASCII.
+
+## Changelog
+
+- v1.0
+First public version.
+
+## Copyright and License
+
+main/led_strip.(h|c) © 2016 by Lucas Bruder. Parts of
+main/webserver.c © 2016, 2020 Espressif Systems.
+
+Everything else:
+
+Copyright © 2021 Gerd Beuster <gerd@frombelow.net>
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
diff --git a/case/case.scad b/case/case.scad
@@ -0,0 +1,250 @@
+// Dimensions of the power supply (+ 1 mm on each side)
+power_supply_width = 97 + 1;
+power_supply_depth = 159 + 1;
+power_supply_height = 30 + 1;
+
+// Additional depth for the MCU compartment
+mcu_compartment_depth = 60;
+mcu_depth = 15;
+mcu_width=40;
+mcu_height=5;
+
+// Dimensions of power switch (+ 1 mm)
+power_switch_width = 18 + 1;
+power_switch_height = 12 + 1;
+
+// Diameters of cables ( + 2 mm)
+diameter_power_cable = 6 + 2;
+diameter_data_cable = 5 + 2;
+diameter_cord_grip_screw = 5 + 2;
+
+// Thickness of walls, guide rails, etc.
+wall_thickness = 2;
+
+// Interiour width must be big enough for power supply plus guide rails for
+// lid.
+inner_frame_width = power_supply_width + 2 * wall_thickness;
+inner_frame_depth = power_supply_depth + mcu_compartment_depth;
+// No wall on top, because we will place the lid there.
+inner_frame_height = power_supply_height + 2 * wall_thickness;
+
+// Create walls around empty box of given size. The top is left open.
+// Front does not have full height in order to leave room for the lid.
+module Frame(width, depth, height, wall_thickness) {
+ // Add walls to inner frame
+ outer_frame_width = width + 2 * wall_thickness;
+ outer_frame_depth = depth + 2 * wall_thickness;
+ outer_frame_height = height + 1 * wall_thickness;
+ // Construct frame
+ difference() {
+ cube([outer_frame_width, outer_frame_depth, outer_frame_height]);
+ translate([wall_thickness, wall_thickness, wall_thickness])
+ cube([width, depth, height]);
+ translate([wall_thickness, 0, outer_frame_height - wall_thickness * 2 ])
+ cube([width, wall_thickness, wall_thickness * 2]);
+ }
+}
+
+// Guide rails for lid and supporting structures.
+module Guide_Rails(width, depth, height, wall_thickness) {
+ // Left top
+ translate([wall_thickness, 0, height])
+ cube([wall_thickness, depth + wall_thickness, wall_thickness]);
+ // Left bottom
+ translate([wall_thickness, 0, height - 2 * wall_thickness])
+ cube([wall_thickness, depth + wall_thickness, wall_thickness]);
+ // Right top
+ translate([width, 0, height])
+ cube([wall_thickness, depth + wall_thickness, wall_thickness]);
+ // Right bottom
+ translate([width, 0, height - 2 * wall_thickness])
+ cube([wall_thickness, depth + wall_thickness, wall_thickness]);
+ // Back top
+ translate([wall_thickness, depth, height])
+ cube([width, wall_thickness, wall_thickness]);
+ // Back bottom
+ translate([wall_thickness, depth, height - 2 * wall_thickness])
+ cube([width, wall_thickness, wall_thickness]);
+ // Support structure back
+ translate([width/3, depth, wall_thickness])
+ cube([wall_thickness, wall_thickness, height - 3 * wall_thickness]);
+ translate([width/3*2, depth, wall_thickness])
+ cube([wall_thickness, wall_thickness, height - 3 * wall_thickness]);
+ // Support structure left side
+ translate([wall_thickness, depth-depth/4, wall_thickness])
+ cube([wall_thickness, wall_thickness, height - 3 * wall_thickness]);
+ translate([wall_thickness, depth-depth/4 * 2, wall_thickness])
+ cube([wall_thickness, wall_thickness, height - 3 * wall_thickness]);
+ translate([wall_thickness, depth-depth/4 * 3, wall_thickness])
+ cube([wall_thickness, wall_thickness, height - 3 * wall_thickness]);
+ // Support structure right side
+ translate([width, depth-depth/4, wall_thickness])
+ cube([wall_thickness, wall_thickness, height - 3 * wall_thickness]);
+ translate([width, depth-depth/4 * 2, wall_thickness])
+ cube([wall_thickness, wall_thickness, height - 3 * wall_thickness]);
+ translate([width, depth-depth/4 * 3, wall_thickness])
+ cube([wall_thickness, wall_thickness, height - 3 * wall_thickness]);
+ // Fixation of power supply
+ translate([wall_thickness,
+ depth - (wall_thickness + power_supply_depth),
+ wall_thickness])
+ cube([width, wall_thickness, wall_thickness]);
+}
+
+module Frame_with_Rails(width, depth, height, wall_thickness) {
+ union () {
+ Frame(width=width, depth=depth, height=height, wall_thickness=wall_thickness);
+ Guide_Rails(width=width, depth=depth, height=height, wall_thickness=wall_thickness);
+ }
+}
+
+// Holes for cords and power switch
+module Frame_with_Rails_and_Holes(width, depth, height, wall_thickness) {
+ difference() {
+ Frame_with_Rails(width=width, depth=depth, height=height,
+ wall_thickness=wall_thickness);
+ // Hole for power switch
+ translate([width/3 , 0, height /3])
+ cube([power_switch_width, wall_thickness, power_switch_height]);
+ // Hole for power cable
+ translate([0 , depth/11, height /4])
+ rotate([0, 90, 0]) cylinder(d=diameter_power_cable, h=wall_thickness+1,
+ center=false);
+ // Hole for data cable
+ translate([0 , depth/5.5, height /4])
+ rotate([0, 90, 0]) cylinder(d=diameter_data_cable, h=wall_thickness+1,
+ center=false);
+ }
+}
+
+module Frame_with_Air_Vents(width, depth, height, wall_thickness) {
+ difference() {
+ Frame_with_Rails_and_Holes(width=width, depth=depth, height=height,
+ wall_thickness=wall_thickness);
+ // Left side vents
+ for(vent_depth = [depth/40*11+wall_thickness: depth/40: depth-wall_thickness*2])
+ translate([0, vent_depth, 2*wall_thickness])
+ cube([wall_thickness*2, depth/80, height/3*2]);
+ // Right side vents
+ for(vent_depth = [depth/40*11+wall_thickness: depth/40: depth-wall_thickness*2])
+ translate([width, vent_depth, 2*wall_thickness])
+ cube([wall_thickness*2, depth/80, height/3*2]);
+ // Back side vents
+ for(vent_width = [wall_thickness*3: width/20: width-wall_thickness*2])
+ translate([vent_width, depth, 2*wall_thickness])
+ cube([width/50, wall_thickness*2, height/3*2]);
+ }
+}
+// Support structure for cord grip for power supply
+module Cord_Grip_for_Power_Supply(width, depth, height, wall_thickness,
+ diameter_power_cable,
+ diameter_cord_grip_screw) {
+// Cord grip for power supply
+ translate([wall_thickness,
+ depth/10 + diameter_power_cable/2 + wall_thickness,
+ wall_thickness])
+ difference () {
+ cube([width/5, wall_thickness, height-2*wall_thickness]);
+ translate([width/7,
+ wall_thickness,
+ height/2])
+ rotate([90, 0, 0]) cylinder(d=diameter_cord_grip_screw,
+ h=wall_thickness+2,
+ center=true);
+ }
+
+}
+
+// Mount for MCU
+module MCU_Mount(width, depth, mcu_width, mcu_depth, mcu_compartment_depth,
+ wall_thickness) {
+ translate([width-mcu_width+wall_thickness,
+ (mcu_compartment_depth-wall_thickness-mcu_depth)/1.5,
+ wall_thickness+mcu_height])
+ cube([mcu_width, mcu_depth, wall_thickness]);
+}
+
+
+// Initials and year
+module Signature(width, wall_thickness) {
+translate([width+wall_thickness*2, wall_thickness*2, wall_thickness*2]) rotate([90, 0, 90]) linear_extrude(1) text(text="gb 2020", size=5, font="Arial Black");
+}
+
+
+// Complete Box
+module Box(){
+ Frame_with_Air_Vents(width=inner_frame_width,
+ depth=inner_frame_depth,
+ height=inner_frame_height,
+ wall_thickness=wall_thickness);
+
+ Cord_Grip_for_Power_Supply(width=inner_frame_width,
+ depth=inner_frame_depth,
+ height=inner_frame_height,
+ wall_thickness=wall_thickness,
+ diameter_power_cable=diameter_power_cable,
+ diameter_cord_grip_screw=diameter_cord_grip_screw);
+
+ MCU_Mount(width=inner_frame_width, depth=inner_frame_depth,
+ mcu_width=mcu_width, mcu_depth=mcu_depth,
+ mcu_compartment_depth=mcu_compartment_depth,
+ wall_thickness=wall_thickness);
+
+ Signature(width=inner_frame_width, wall_thickness=wall_thickness);
+}
+
+// Lid for the box
+// (rails leave 0.5 mm on each side)
+module Lid(){
+translate([wall_thickness+0.5, 0, inner_frame_height-wall_thickness])
+ cube([inner_frame_width-1, inner_frame_depth+wall_thickness-1.5, wall_thickness]);
+translate([wall_thickness*2, 0, inner_frame_height])
+ cube([inner_frame_width-2*wall_thickness,
+ inner_frame_depth-1,
+ wall_thickness]);
+}
+
+
+// Power supply (used for debugging)
+module Power_Supply() {
+ translate([wall_thickness*2, mcu_compartment_depth, wall_thickness])
+ cube([power_supply_width, power_supply_depth, power_supply_height]);
+}
+
+// MCU (used for debugging)
+module MCU() {
+ translate([inner_frame_width-mcu_width+wall_thickness, (mcu_compartment_depth-mcu_depth-wall_thickness)/2, wall_thickness])
+ cube([mcu_width, mcu_depth, mcu_height]);
+}
+
+// Sanity check: Box, lid, power supply, and MCU must not overlap
+module Check_for_Overlaps() {
+ intersection() {
+ Box();
+ Lid();
+ }
+ intersection() {
+ Box();
+ Power_Supply();
+ }
+ intersection() {
+ Power_Supply();
+ Lid();
+ }
+ intersection() {
+ Box();
+ MCU();
+ }
+}
+
+module Render_All() {
+ Box();
+ translate([0, 0, 40])
+ Lid();
+}
+
+// Check_for_Overlaps();
+// Box();
+// Lid();
+Render_All();
+
diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt
@@ -0,0 +1,6 @@
+idf_component_register(
+ SRCS "main.c"
+ "led.c"
+ "led_strip.c"
+ "webserver.c"
+ INCLUDE_DIRS "")
diff --git a/main/Kconfig.projbuild b/main/Kconfig.projbuild
@@ -0,0 +1,102 @@
+menu "Blinkenstrip"
+
+config WS2811_DATA_PIN
+ int "LED Strip Signal Line"
+ default 4
+ range 2 35
+ help
+ Pin D2 on the ESP-WROOM-32 board is 2.
+ Pin D4 on the ESP-WROOM-32 board is 4.
+ ...
+
+config PIN_FACTORY_RESET_A
+ int "First pin for factory reset"
+ default 12
+ range 2 35
+ help
+ When factory reset pins A and B are connected on power-up,
+ the device resets to factory settings.
+
+config PIN_FACTORY_RESET_B
+ int "Second pin for factory reset"
+ default 13
+ range 2 35
+ help
+ When factory reset pins A and B are connected on power-up,
+ the device resets to factory settings.
+
+config APP_NAME
+ string "Name of the application"
+ default "Blinkenstrip"
+ help
+ The name of the application is shown on the web pages.
+
+
+config WIFI_AP
+ bool "Factory default: Run WIFI Access Point"
+ default y
+ help
+ If true, run our own access point. If false, connect as client to WIFI
+ network. This is the default value after factory reset. When the device
+ runs, this value can be configured via the web interface.
+
+config WIFI_SSID
+ string "Factory default: WIFI Network Name"
+ default "blinkenstrip"
+ help
+ SSID of your WIFI Network. This is the default value after factory reset.
+ When the device runs, this value can be configured via the web interface.
+
+config WIFI_PASSWORD
+ string "Factory default: WIFI WPA2 Password"
+ default "blinkenstrip"
+ help
+ Password of your WIFI Network. This is the default value after factory
+ reset. When the device runs, this value can be configured via the web
+ interface. MINIMAL PASSWORD LENGTH IS 8 CHARACTERS!
+
+config LED_STRIP_WIDTH
+ int "Factory default: Width of Matrix"
+ default 8
+ range 0 1024
+ help
+ This is the default value after factory reset. When the device runs, this
+ value can be configured via the web interface.
+
+config LED_STRIP_HEIGHT
+ int "Factory default: Height of Matrix"
+ default 8
+ range 0 1024
+ help
+ This is the default value after factory reset. When the device runs, this
+ value can be configured via the web interface.
+
+config LED_STRIP_SNAKE
+ bool "Factory default: Snake wiring?"
+ default n
+ help
+ LEDs may be wired lineary or in "snake wiring", i.e. first row
+ left-to-right, second row right-to-left, ...
+
+config WRAP_AROUND
+ bool "Factory default: Ends of LED strip connected?"
+ default n
+ help
+ Set this if the ends of the LED strip are connected to each other.
+
+config NUMBER_OF_WORMS
+ int "Factory default: Number of worms in mode \"Worm\""
+ default 1
+ range 0 255
+ help
+ Number of worms shown in mode "Worm".
+
+config MAX_BRIGHTNESS
+ int "Factory default: Maximal Brightness of LEDs"
+ default 32
+ range 0 255
+ help
+ This is the default value after factory reset. When the device runs, this
+ value can be configured via the web interface.
+
+endmenu # Blinkenstrip
diff --git a/main/component.mk b/main/component.mk
@@ -0,0 +1,5 @@
+#
+# "main" pseudo-component makefile.
+#
+# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
+
diff --git a/main/debug.h b/main/debug.h
@@ -0,0 +1,28 @@
+/*
+ Copyright © 2021 Gerd Beuster <gerd@frombelow.net>
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+#ifndef DEBUG_H
+#define DEBUG_H
+
+// Define debug levels and pick one.
+#define DEBUG 0
+#define INFO 1
+/* #define DEBUG_LEVEL DEBUG */
+#define DEBUG_LEVEL INFO
+
+#define DEBUG_MSG(level, x) \
+ do { if (level >= DEBUG_LEVEL) printf x; fflush(stdout);} while (0)
+
+#endif
diff --git a/main/html_templates/config.html b/main/html_templates/config.html
@@ -0,0 +1,53 @@
+"<!DOCTYPE html>\r\n"
+"<html lang=\"de\">\r\n"
+" <head>\r\n"
+" <meta charset=\"utf-8\">\r\n"
+" <title>%s (Configuration)</title>\r\n"
+" <style>\r\n"
+"button{width:10em;}\r\n"
+"@media only screen and (max-device-width: 1024px){\r\n"
+" html{font-size : 300%%; line-height: 100%%;}\r\n"
+" button{font-size : 200%%; width:10em;}\r\n"
+" input[type=text] {\r\n"
+" font-size: 150%%;\r\n"
+" }\r\n"
+" input[type=password] {\r\n"
+" font-size: 150%%;\r\n"
+" }\r\n"
+" input[type=radio] {\r\n"
+" width: 5em;\r\n"
+" height: 5ex;\r\n"
+" }\r\n"
+"}\r\n"
+" </style>\r\n"
+" </head>\r\n"
+" <body>\r\n"
+" <h1>%s (Configuration)</h1>\r\n"
+" <form action=\"/config.html\" method=\"POST\"> \r\n"
+" Name:<br />\r\n"
+" <input type=\"text\" name=\"app_name\" value=\"%s\"></input><br /><br />\r\n"
+" ESSID:<br />\r\n"
+" <input type=\"text\" name=\"essid\" value=\"%s\"></input><br /><br />\r\n"
+" Password (Min. 8 Characters!):<br />\r\n"
+" <input type=\"password\" name=\"password\" value=\"%s\"></input><br /><br />\r\n"
+" <input type=\"radio\" name=\"ap\" value=\"yes\" %s>Run Access Point</input><br /><br />\r\n"
+" <input type=\"radio\" name=\"ap\" value=\"no\" %s>Join Existing Network</input><br /><br />\r\n"
+" LED Matrix Width:<br /> <input type=\"text\" name=\"led_strip_w\" value=\"%d\"></input><br /><br />\r\n"
+" LED Matrix Height:<br /> <input type=\"text\" name=\"led_strip_h\" value=\"%d\"></input><br /><br />\r\n"
+" <input type=\"radio\" name=\"snake\" value=\"yes\" %s>Snake Wiring</input><br /><br />\r\n"
+" <input type=\"radio\" name=\"snake\" value=\"no\" %s>Linear Wiring</input><br /><br />\r\n"
+" <input type=\"radio\" name=\"wrap_around\" value=\"yes\" %s>\r\n"
+" Circular Wiring</input><br /><br />\r\n"
+" <input type=\"radio\" name=\"wrap_around\" value=\"no\" %s>\r\n"
+" Non-Circual Wiring</input><br /><br />\r\n"
+" Number of Worms:<br /> <input type=\"text\" name=\"number_of_worms\" value=\"%d\"></input><br /><br />\r\n"
+" Max. Brightness:<br /> <input type=\"text\" name=\"max_brightness\" value=\"%d\"></input><br /><br />\r\n"
+" <button type=\"submit\">Save & Reset!</button><br /><br />\r\n"
+" </form>\r\n"
+" \r\n"
+" \r\n"
+" \r\n"
+" \r\n"
+"<p><a href=\"/\"</a>LED Control</a></p>\r\n"
+" </body>\r\n"
+"</html>\r\n";
diff --git a/main/html_templates/index.html b/main/html_templates/index.html
@@ -0,0 +1,65 @@
+"<!DOCTYPE html>\r\n"
+"<html lang=\"de\">\r\n"
+" <head>\r\n"
+" <meta charset=\"utf-8\">\r\n"
+" <title>%s</title>\r\n"
+" <style>\r\n"
+"button{width:10em;}\r\n"
+"@media only screen and (max-device-width: 1024px){\r\n"
+"html{font-size : 300%%; line-height: 50%%;}\r\n"
+"button{font-size : 190%%; width:10em;}\r\n"
+".slider{\r\n"
+" height: 5ex;\r\n"
+" width: 40em;\r\n"
+" autoScaleSlider:false,\r\n"
+" autoHeight: false\r\n"
+"}\r\n"
+" </style>\r\n"
+" </head>\r\n"
+" <body>\r\n"
+" <h1>%s</h1>\r\n"
+" <form action=\"/\" method=\"POST\"> \r\n"
+" <div class=\"slidecontainer\">\r\n"
+" R <input type=\"range\" min=\"0\" max=\"%d\" value=\"0\" class=\"slider\" id=\"slider_red\">\r\n"
+" <label id=\"label_red\"></label><br />\r\n"
+" G <input type=\"range\" min=\"0\" max=\"%d\" value=\"0\" class=\"slider\" id=\"slider_green\">\r\n"
+" <label id=\"label_green\"></label><br />\r\n"
+" B <input type=\"range\" min=\"0\" max=\"%d\" value=\"0\" class=\"slider\" id=\"slider_blue\">\r\n"
+" <label id=\"label_blue\"></label><br />\r\n"
+" </div><br />\r\n"
+" <button type=\"submit\" name=\"mode\" value=\"sequence\">Sequence</button><br /><br />\r\n"
+" <button type=\"submit\" name=\"mode\" value=\"blink\">Blink</button><br /><br />\r\n"
+" <button type=\"submit\" name=\"mode\" value=\"running\">Running Dot</button><br /><br />\r\n"
+" <button type=\"submit\" name=\"mode\" value=\"cellular\">Cellular Automaton</button><br /><br />\r\n"
+" <button type=\"submit\" name=\"mode\" value=\"lines\">Changing Lines</button><br /><br />\r\n"
+" <button type=\"submit\" name=\"mode\" value=\"worm\">Worm</button><br /><br />\r\n"
+" <!-- <button type=\"submit\" name=\"mode\" value=\"experimental\">Experiment</button><br /><br /> -->\r\n"
+" </form>\r\n"
+" <p><a href=\"config.html\"</a>Configure</a></p>\r\n"
+" <!-- Update slider on the fly -->\r\n"
+" <script>\r\n"
+" var slider_red = document.getElementById(\"slider_red\");\r\n"
+" var label_red = document.getElementById(\"label_red\");\r\n"
+" var slider_green = document.getElementById(\"slider_green\");\r\n"
+" var label_green = document.getElementById(\"label_green\");\r\n"
+" var slider_blue = document.getElementById(\"slider_blue\");\r\n"
+" var label_blue = document.getElementById(\"label_blue\");\r\n"
+" label_red.innerHTML = slider_red.value;\r\n"
+" label_green.innerHTML = slider_green.value;\r\n"
+" label_blue.innerHTML = slider_blue.value;\r\n"
+" slider_red.oninput = function() {\r\n"
+" label_red.innerHTML = slider_red.value;\r\n"
+" label_green.innerHTML = slider_green.value;\r\n"
+" label_blue.innerHTML = slider_blue.value;\r\n"
+" var i = document.createElement(\"img\");\r\n"
+" // Didn't figure out how to do this with a POST request without\r\n"
+" // resorting to external libraries\r\n"
+" i.src = \"?mode=rgb&red=\" +\r\n"
+" slider_red.value + \"&green=\" + slider_green.value +\r\n"
+" \"&blue=\" + slider_blue.value;\r\n"
+" };\r\n"
+" slider_green.oninput = slider_red.oninput;\r\n"
+" slider_blue.oninput = slider_red.oninput;\r\n"
+" </script>\r\n"
+" </body>\r\n"
+"</html>\r\n";
diff --git a/main/html_templates/reset.html b/main/html_templates/reset.html
@@ -0,0 +1,22 @@
+"<!DOCTYPE html>\r\n"
+"<html lang=\"de\">\r\n"
+" <head>\r\n"
+" <meta charset=\"utf-8\">\r\n"
+" <meta http-equiv=\"refresh\" content=\"10; URL=/\">\r\n"
+" <title>Reset</title>\r\n"
+" <style>\r\n"
+"button{width:10em;}\r\n"
+"@media only screen and (max-device-width: 1024px){\r\n"
+"html{font-size : 300%%; line-height: 50%%;}\r\n"
+"button{font-size : 200%%; width:10em;}\r\n"
+"}\r\n"
+" </style>\r\n"
+" </head>\r\n"
+" <body>\r\n"
+" <h1>Reset</h1>\r\n"
+" <p>Restarting device ...</p>\r\n"
+" <p>You will be re-directed to the main page in 10 seconds.\r\n"
+" This will only work if your device and\r\n"
+" the web browser are on the same network.</p>\r\n"
+" </body>\r\n"
+"</html>\r\n";
diff --git a/main/led.c b/main/led.c
@@ -0,0 +1,538 @@
+/*
+ Function unicolor_rainbow based on
+ <https://www.esp32.com/viewtopic.php?t=589#> ((c) Lucas Bruder
+ <LBruder@me.com> 2016)
+
+ Everything else copyright © 2021 Gerd Beuster <gerd@frombelow.net>
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+
+/* {{{ Includes and definitions */
+
+#include "led.h"
+#include "debug.h"
+#include "freertos/task.h"
+#include "nvs_flash.h"
+#include "math.h"
+
+/* }}} */
+
+void store_state_if_changed() {
+ if(blinkenstrip_state_has_changed) {
+ nvs_handle flash_handle;
+ nvs_open("blinkenstrip", NVS_READWRITE, &flash_handle);
+ nvs_set_u8(flash_handle, "blinkenstrip_state", blinkenstrip_state);
+ nvs_set_u8(flash_handle, "led_col_red", led_color_rgb.red);
+ nvs_set_u8(flash_handle, "led_col_green", led_color_rgb.green);
+ nvs_set_u8(flash_handle, "led_col_blue", led_color_rgb.blue);
+ nvs_commit(flash_handle);
+ nvs_close(flash_handle);
+ DEBUG_MSG(DEBUG, ("Wrote new state (%d / (%d, %d, %d)) to flash.\n",
+ blinkenstrip_state, led_color_rgb.red, led_color_rgb.green, led_color_rgb.blue));
+ }
+}
+
+/* {{{ Unicolor patterns */
+
+void unicolor(struct led_color_t led_color){
+ store_state_if_changed();
+ for (uint32_t index = 0; index < led_strip_length; index++) {
+ led_strip_set_pixel_color(&led_strip, index, &led_color);
+ }
+ led_strip_show(&led_strip);
+ blinkenstrip_state_has_changed = false;
+}
+
+const uint8_t lights[360]={
+ 0, 0, 0, 0, 0, 1, 1, 2,
+ 2, 3, 4, 5, 6, 7, 8, 9,
+ 11, 12, 13, 15, 17, 18, 20, 22,
+ 24, 26, 28, 30, 32, 35, 37, 39,
+ 42, 44, 47, 49, 52, 55, 58, 60,
+ 63, 66, 69, 72, 75, 78, 81, 85,
+ 88, 91, 94, 97, 101, 104, 107, 111,
+ 114, 117, 121, 124, 127, 131, 134, 137,
+ 141, 144, 147, 150, 154, 157, 160, 163,
+ 167, 170, 173, 176, 179, 182, 185, 188,
+ 191, 194, 197, 200, 202, 205, 208, 210,
+ 213, 215, 217, 220, 222, 224, 226, 229,
+ 231, 232, 234, 236, 238, 239, 241, 242,
+ 244, 245, 246, 248, 249, 250, 251, 251,
+ 252, 253, 253, 254, 254, 255, 255, 255,
+ 255, 255, 255, 255, 254, 254, 253, 253,
+ 252, 251, 251, 250, 249, 248, 246, 245,
+ 244, 242, 241, 239, 238, 236, 234, 232,
+ 231, 229, 226, 224, 222, 220, 217, 215,
+ 213, 210, 208, 205, 202, 200, 197, 194,
+ 191, 188, 185, 182, 179, 176, 173, 170,
+ 167, 163, 160, 157, 154, 150, 147, 144,
+ 141, 137, 134, 131, 127, 124, 121, 117,
+ 114, 111, 107, 104, 101, 97, 94, 91,
+ 88, 85, 81, 78, 75, 72, 69, 66,
+ 63, 60, 58, 55, 52, 49, 47, 44,
+ 42, 39, 37, 35, 32, 30, 28, 26,
+ 24, 22, 20, 18, 17, 15, 13, 12,
+ 11, 9, 8, 7, 6, 5, 4, 3,
+ 2, 2, 1, 1, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0};
+
+void unicolor_rainbow(){
+
+ static int rgb_red=0;
+ static int rgb_green=120;
+ static int rgb_blue=240;
+
+ static struct led_color_t led_color = {
+ .red = 5,
+ .green = 0,
+ .blue = 0,
+ };
+
+ store_state_if_changed();
+ for (uint32_t index = 0; index < led_strip_length; index++) {
+ led_strip_set_pixel_color(&led_strip, index, &led_color);
+ }
+ led_strip_show(&led_strip);
+
+ led_color.red = lights[rgb_red] * max_brightness / 0xFF;
+ led_color.green = lights[rgb_green] * max_brightness / 0xFF;
+ led_color.blue = lights[rgb_blue] * max_brightness / 0xFF;
+
+ rgb_red += 1;
+ rgb_green += 1;
+ rgb_blue += 1;
+
+ if (rgb_red >= 360) rgb_red=0;
+ if (rgb_green >= 360) rgb_green=0;
+ if (rgb_blue >= 360) rgb_blue=0;
+
+ blinkenstrip_state_has_changed = false;
+}
+
+/* }}} */
+
+/* {{{ Random on/off */
+
+uint32_t get_random_number(uint32_t n){
+ uint32_t r;
+ do{
+ r = esp_random();
+ } while(r > (RAND_MAX - (RAND_MAX % n)));
+ return(r % n);
+}
+
+void random_on_off(){
+ if(blinkenstrip_state_has_changed){
+ // Initalize state
+ for (uint32_t index = 0; index < led_strip_length; index++) {
+ led_strip_set_pixel_color(&led_strip, index,
+ (struct led_color_t *) &led_color_blue);
+ }
+ store_state_if_changed();
+ blinkenstrip_state_has_changed = false;
+ }
+ else{
+ // Update state
+ // Copy over "old" strip
+ struct led_color_t c;
+ for (uint32_t index = 0; index < led_strip_length; index++) {
+ led_strip_get_pixel_color(&led_strip, index, &c);
+ led_strip_set_pixel_color(&led_strip, index, &c);
+ }
+ // Randomly switch an LED to green or blue.
+ uint32_t switch_led = get_random_number(led_strip_length);
+ if (get_random_number(2)){
+ led_strip_set_pixel_color(&led_strip, switch_led,
+ (struct led_color_t *) &led_color_blue);
+ }
+ else{
+ led_strip_set_pixel_color(&led_strip, switch_led,
+ (struct led_color_t *) &led_color_green);
+ }
+ }
+ led_strip_show(&led_strip);
+}
+
+/* }}} */
+
+/* {{{ Running dot */
+
+void running_dot(){
+ static uint32_t position = 0;
+ store_state_if_changed();
+ blinkenstrip_state_has_changed = false;
+ for (uint32_t index = 0; index < led_strip_length; index++) {
+ led_strip_set_pixel_color(&led_strip, index,
+ (struct led_color_t *) &led_color_blue);
+ }
+ led_strip_set_pixel_color(&led_strip, position,
+ (struct led_color_t *) &led_color_red);
+ position = (position + 1) % led_strip_length;
+ blinkenstrip_state_has_changed = false;
+ led_strip_show(&led_strip);
+};
+
+/* }}} */
+
+/* {{{ Cellular Automaton */
+
+int32_t xy2i(int32_t x, int32_t y) {
+ int32_t i = y * led_strip_w;
+ if ((y % 2) && snake) {
+ i += led_strip_w - x - 1;
+ }
+ else {
+ i += x;
+ }
+ return(i);
+}
+
+
+bool cell_is_on(struct led_strip_t led_strip, int32_t x, int32_t y){
+ static struct led_color_t c;
+ x = (x + led_strip_w) % led_strip_w;
+ y = (y + led_strip_h) % led_strip_h;
+ led_strip_get_pixel_color(&led_strip, xy2i(x, y),
+ &c);
+ return((c.red == cell_on.red) &&
+ (c.green == cell_on.green) &&
+ (c.blue == cell_on.blue));
+};
+
+void cellular_automaton(){
+ static uint8_t number_of_updates = 0;
+ static bool stable_pattern = false;
+ // Since we get a stable pattern after sometime, we restart with a
+ // new random cellular automaton after a fixed number of steps or
+ // when we detect a stable situation.
+ store_state_if_changed();
+ if(blinkenstrip_state_has_changed || stable_pattern ||
+ (number_of_updates == 100)) {
+ // Initialize with random values
+ for (uint32_t index = 0; index < led_strip_length; index++) {
+ if (get_random_number(2)){
+ led_strip_set_pixel_color(&led_strip, index,
+ (struct led_color_t *) &cell_on);
+ }
+ else{
+ led_strip_set_pixel_color(&led_strip, index,
+ (struct led_color_t *) &cell_off);
+ }
+ }
+ led_strip_show(&led_strip);
+ number_of_updates = 0;
+ blinkenstrip_state_has_changed = false;
+ stable_pattern = false;
+ }
+ // Update automaton
+ if((!stable_pattern) && (number_of_updates++ < 100)){
+ // Cycle over all cells
+ for (int32_t y = 0; y < led_strip_h; y++){
+ for (int32_t x = 0; x < led_strip_w; x++){
+ // Count neighbors
+ uint8_t neighbors = 0;
+ for (int32_t y_neighbor = 0; y_neighbor < 3; y_neighbor++){
+ for (int32_t x_neighbor = 0; x_neighbor < 3; x_neighbor++){
+ if (((x_neighbor != 1) || (y_neighbor != 1)) &&
+ (cell_is_on(led_strip, x-x_neighbor+1, y-y_neighbor+1))){
+ neighbors++;
+ }
+ }
+ }
+ // Update cell
+ uint32_t index = xy2i(x, y);
+ if (neighbors < 2) {
+ led_strip_set_pixel_color(&led_strip, index,
+ (struct led_color_t *) &cell_off);
+ }
+ else if (neighbors > 3) {
+ led_strip_set_pixel_color(&led_strip, index,
+ (struct led_color_t *) &cell_off);
+ }
+ else if (neighbors == 3) {
+ led_strip_set_pixel_color(&led_strip, index,
+ (struct led_color_t *) &cell_on);
+ }
+ else {
+ struct led_color_t c;
+ led_strip_get_pixel_color(&led_strip, index, &c);
+ led_strip_set_pixel_color(&led_strip, index, &c);
+ }
+ }
+ }
+ // Check if pattern has changed since last update.
+ // (We will exit the loop if the pattern becomes stable)
+ stable_pattern = true;
+ for(uint32_t i = 0; (i < led_strip_length) && stable_pattern; i++){
+ stable_pattern =
+ (led_strip.led_strip_buf_1[i].red ==
+ led_strip.led_strip_buf_2[i].red) &&
+ (led_strip.led_strip_buf_1[i].green ==
+ led_strip.led_strip_buf_2[i].green) &&
+ (led_strip.led_strip_buf_1[i].blue ==
+ led_strip.led_strip_buf_2[i].blue);
+ }
+ led_strip_show(&led_strip);
+ vTaskDelay(600 / portTICK_PERIOD_MS);
+ }
+}
+
+/* }}} */
+
+/* {{{ Line-by-line switch */
+
+void set_row_color(struct led_color_t led_color, uint32_t row) {
+ for (uint32_t index = 0; index < led_strip_w; index++) {
+ led_strip_set_pixel_color(&led_strip, xy2i(index, row),
+ &led_color);
+ }
+}
+
+void set_column_color(struct led_color_t led_color, uint32_t column) {
+ for (uint32_t index = 0; index < led_strip_h; index++) {
+ led_strip_set_pixel_color(&led_strip, xy2i(column, index),
+ &led_color);
+ }
+}
+
+void line_by_line() {
+ static bool change_row = true; // Switch in rows or in columns?
+ static void (*switch_command)(struct led_color_t led_color, uint32_t row);
+ const uint8_t color_cycle[] = {
+ max_brightness, max_brightness, 0 // Yellow
+ , 0, max_brightness, max_brightness // Turquoise
+ , 0, 0, max_brightness // Blue
+ };
+ const uint8_t color_cycle_length = sizeof(color_cycle);
+
+ static uint8_t pos_in_color_cycle = 0;
+ static uint32_t switch_line = 0;
+ static struct led_color_t old_color;
+ static struct led_color_t new_color;
+ store_state_if_changed();
+ if (switch_line == 0) {
+ old_color.red = color_cycle[pos_in_color_cycle];
+ old_color.green = color_cycle[pos_in_color_cycle+1];
+ old_color.blue = color_cycle[pos_in_color_cycle+2];
+ pos_in_color_cycle = (pos_in_color_cycle + 3) % color_cycle_length;
+ new_color.red = color_cycle[pos_in_color_cycle];
+ new_color.green = color_cycle[pos_in_color_cycle+1];
+ new_color.blue = color_cycle[pos_in_color_cycle+2];
+ change_row = !change_row;
+ if(change_row){
+ switch_command = set_row_color;
+ }
+ else{
+ switch_command = set_column_color;
+ }
+ }
+ for (uint8_t row = 0; row < led_strip_h; row++) {
+ if (row > switch_line) {
+ (*switch_command)(old_color, row);
+ }
+ else if (row == switch_line) {
+ (*switch_command)(led_color_red, row);
+ }
+ else {
+ (*switch_command)(new_color, row);
+ }
+ }
+ switch_line = (switch_line + 1) % led_strip_h;
+ led_strip_show(&led_strip);
+ blinkenstrip_state_has_changed = false;
+}
+
+/* }}} */
+
+/* {{{ Worm */
+
+// Positions and running directions of worms (allocated in main_led_task)
+int8_t *worm_direction;
+uint16_t *worm_pos;
+
+void worm() {
+ // Percentage of max_brightness relative to full brightness
+ float mb_perc = ((float) max_brightness) / 0xFF;
+ /* const float mb_perc = 1; */
+ struct led_color_t scanner_pattern[] =
+ {{round(0x00*mb_perc), round(0x00*mb_perc), round(0x00*mb_perc)},
+ {round(0x00*mb_perc), round(0x20*mb_perc), round(0x00*mb_perc)},
+ {round(0x20*mb_perc), round(0x20*mb_perc), round(0x00*mb_perc)},
+ {round(0xFF*mb_perc), round(0x00*mb_perc), round(0x00*mb_perc)},
+ {round(0xFF*mb_perc), round(0x00*mb_perc), round(0x00*mb_perc)},
+ {round(0xFF*mb_perc), round(0x00*mb_perc), round(0x00*mb_perc)},
+ {round(0xFF*mb_perc), round(0x00*mb_perc), round(0x00*mb_perc)},
+ {round(0xFF*mb_perc), round(0x00*mb_perc), round(0x00*mb_perc)},
+ {round(0x20*mb_perc), round(0x20*mb_perc), round(0x00*mb_perc)},
+ {round(0x00*mb_perc), round(0x20*mb_perc), round(0x00*mb_perc)},
+ {round(0x00*mb_perc), round(0x00*mb_perc), round(0x00*mb_perc)}};
+ const uint8_t scanner_pattern_length = (sizeof(scanner_pattern) /
+ sizeof(struct led_color_t));
+ store_state_if_changed();
+ if(blinkenstrip_state_has_changed){
+ // Initalize state
+ for (uint32_t index = 0; index < led_strip_length; index++) {
+ led_strip_set_pixel_color(&led_strip, index,
+ (struct led_color_t *) &led_color_black);
+ }
+ for (uint8_t w = 0; w < number_of_worms; w++) {
+ worm_direction[w] = get_random_number(2)*2-1;
+ worm_pos[w] = get_random_number(led_strip_length-scanner_pattern_length);
+ }
+ blinkenstrip_state_has_changed = false;
+ }
+ else{
+ // Update state
+ // Copy over "old" strip
+ struct led_color_t c;
+ for (uint32_t index = 0; index < led_strip_length; index++) {
+ led_strip_get_pixel_color(&led_strip, index, &c);
+ led_strip_set_pixel_color(&led_strip, index, &c);
+ }
+ // Worms
+ for (uint8_t w = 0; w < number_of_worms; w++) {
+ for (uint8_t i = 0; i < scanner_pattern_length; i++) {
+ led_strip_set_pixel_color(&led_strip,
+ (worm_pos[w]+i) % led_strip_length,
+ &scanner_pattern[i]);
+ }
+ if (!get_random_number(50)) {
+ worm_direction[w] *= -1;
+ }
+ if (!wrap_around) {
+ // Switch direction if we hit the end of the strip
+ if (worm_pos[w] == 0){
+ worm_direction[w] = 1;
+ }
+ if ((worm_pos[w] + scanner_pattern_length) == led_strip_length) {
+ worm_direction[w] = -1;
+ }
+ }
+ worm_pos[w] = (worm_pos[w] + worm_direction[w]) % led_strip_length;
+ };
+ // Randomly switch an LED to blue
+ uint32_t switch_led = get_random_number(led_strip_length);
+ if (!get_random_number(4)){
+ led_strip_set_pixel_color(&led_strip, switch_led,
+ (struct led_color_t *) &led_color_blue);
+ }
+ }
+ led_strip_show(&led_strip);
+}
+
+/* }}} */
+
+void experiment() {
+ /* empty */
+}
+
+/* {{{ main */
+
+void led_controller(void *args) {
+
+ // Define standard colors
+ led_color_red.red = max_brightness;
+ led_color_red.green = 0x00;
+ led_color_red.blue = 0x00;
+ led_color_green.red = 0x00;
+ led_color_green.green = max_brightness;
+ led_color_green.blue = 0x00;
+ led_color_blue.red = 0x00;
+ led_color_blue.green = 0x00;
+ led_color_blue.blue = max_brightness;
+ led_color_yellow.red = max_brightness;
+ led_color_yellow.green = max_brightness;
+ led_color_yellow.blue = 0x00;
+ led_color_white.red = max_brightness;
+ led_color_white.green = max_brightness;
+ led_color_white.blue = max_brightness;
+ led_color_black.red = 0x00;
+ led_color_black.green = 0x00;
+ led_color_black.blue = 0x00;
+
+ // Define colors for cellular automaton
+ cell_on.red = 0x00;
+ cell_on.green = max_brightness;
+ cell_on.blue = 0x00;
+ cell_off.red = 0x00;
+ cell_off.green = 0x00;
+ cell_off.blue = max_brightness / 2;
+
+ // Initialize data structures for worm
+ worm_direction = calloc(number_of_worms, sizeof(int8_t)); // Yes, size is 1 byte ...
+ worm_pos = calloc(number_of_worms, sizeof(uint16_t)); // Yes, size is 2 bytes ...
+
+ // Initialize data structures for LED strip
+ led_strip_length = led_strip_w * led_strip_h;
+ led_strip_buf_1 = malloc(led_strip_length * sizeof(struct led_color_t));
+ led_strip_buf_2 = malloc(led_strip_length * sizeof(struct led_color_t));
+ led_strip.rgb_led_type = RGB_LED_TYPE_WS2812;
+ led_strip.rmt_channel = RMT_CHANNEL_1;
+ led_strip.rmt_interrupt_num = LED_STRIP_RMT_INTR_NUM;
+ led_strip.gpio = CONFIG_WS2811_DATA_PIN;
+ led_strip.led_strip_buf_1 = led_strip_buf_1;
+ led_strip.led_strip_buf_2 = led_strip_buf_2;
+ led_strip.led_strip_length = led_strip_length;
+
+ blinkenstrip_state_changed_semaphore = xSemaphoreCreateBinary();
+ xSemaphoreGive(blinkenstrip_state_changed_semaphore);
+ assert(blinkenstrip_state_changed_semaphore);
+ led_strip.access_semaphore = xSemaphoreCreateBinary();
+ assert(led_strip.access_semaphore);
+ bool led_init_ok = led_strip_init(&led_strip);
+ assert(led_init_ok);
+
+ blinkenstrip_state_has_changed = true;
+ while (true) {
+ xSemaphoreTake(blinkenstrip_state_changed_semaphore, portMAX_DELAY);
+ switch(blinkenstrip_state){
+ case BLINKENSTRIP_STATE_UNICOLOR_RAINBOW:
+ unicolor_rainbow(); break;
+ case BLINKENSTRIP_STATE_RANDOM_ON_OFF:
+ random_on_off(); break;
+ case BLINKENSTRIP_STATE_RUNNING_DOT:
+ running_dot(); break;
+ case BLINKENSTRIP_STATE_CELLULAR_AUTOMATON:
+ cellular_automaton(); break;
+ case BLINKENSTRIP_STATE_LINE_BY_LINE:
+ line_by_line(); break;
+ case BLINKENSTRIP_STATE_WORM:
+ worm(); break;
+ case BLINKENSTRIP_STATE_UNICOLOR_RGB:
+ if(blinkenstrip_state_has_changed)
+ unicolor(led_color_rgb);
+ break;
+ case BLINKENSTRIP_STATE_EXPERIMENTAL:
+ experiment(); break;
+ }
+ xSemaphoreGive(blinkenstrip_state_changed_semaphore);
+ vTaskDelay(60 / portTICK_PERIOD_MS);
+ }
+}
+
+/* }}} */
diff --git a/main/led.h b/main/led.h
@@ -0,0 +1,76 @@
+/*
+ Copyright © 2021 Gerd Beuster <gerd@frombelow.net>
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+#ifndef LED_H
+#define LED_H
+
+#include "led_strip.h"
+
+#define LED_STRIP_RMT_INTR_NUM 19
+
+uint16_t led_strip_w;
+uint16_t led_strip_h;
+uint8_t max_brightness;
+// How are the LEDs wired, linearly, or in "snake mode", i.e. first
+// line left-to-right, second line right-to-left, ...
+uint8_t snake;
+// Whether the ends of the LED strip are connected to each other
+uint8_t wrap_around;
+// Number of worms in BLINKENSTRIP_STATE_WORM
+uint8_t number_of_worms;
+
+// Determine which pattern we show.
+volatile uint8_t blinkenstrip_state;
+// Toggled whenever the user triggered a state change
+// via the webserver.
+volatile bool blinkenstrip_state_has_changed;
+SemaphoreHandle_t blinkenstrip_state_changed_semaphore;
+#define BLINKENSTRIP_STATE_UNICOLOR_RAINBOW 0x05
+#define BLINKENSTRIP_STATE_RANDOM_ON_OFF 0x06
+#define BLINKENSTRIP_STATE_RUNNING_DOT 0x07
+#define BLINKENSTRIP_STATE_CELLULAR_AUTOMATON 0x08
+#define BLINKENSTRIP_STATE_LINE_BY_LINE 0x09
+#define BLINKENSTRIP_STATE_UNICOLOR_RGB 0x0A
+#define BLINKENSTRIP_STATE_WORM 0x0B
+#define BLINKENSTRIP_STATE_EXPERIMENTAL 0xFF
+
+// Data structures for LED strip
+uint16_t led_strip_length;
+struct led_color_t *led_strip_buf_1;
+struct led_color_t *led_strip_buf_2;
+struct led_strip_t led_strip;
+
+// Standard colors
+struct led_color_t led_color_red;
+struct led_color_t led_color_green;
+struct led_color_t led_color_blue;
+struct led_color_t led_color_yellow;
+struct led_color_t led_color_white;
+struct led_color_t led_color_black;
+
+// All channels set by user
+struct led_color_t led_color_rgb;
+
+// Colors for cellular automaton
+struct led_color_t cell_on;
+struct led_color_t cell_off;
+
+// This function is run in its won task and runs the LED show.
+void led_controller(void *args);
+
+// Utiltiy function: Get random number in range 0..n-1.
+uint32_t get_random_number(uint32_t n);
+
+#endif
diff --git a/main/led_strip.c b/main/led_strip.c
@@ -0,0 +1,461 @@
+/* ----------------------------------------------------------------------------
+ File: led_strip.c
+ Author(s): Lucas Bruder <LBruder@me.com>
+ Date Created: 11/23/2016
+ Last modified: 11/26/2016
+
+ Description: LED Library for driving various led strips on ESP32.
+
+ This library uses double buffering to display the LEDs.
+ If the driver is showing buffer 1, any calls to led_strip_set_pixel_color
+ will write to buffer 2. When it's time to drive the pixels on the strip, it
+ refers to buffer 1.
+ When led_strip_show is called, it will switch to displaying the pixels
+ from buffer 2 and will clear buffer 1. Any writes will now happen on buffer 1
+ and the task will look at buffer 2 for refreshing the LEDs
+ ------------------------------------------------------------------------- */
+
+/* Some minor changes by Gerd Beuster (gb) to work with blinkenstrip. */
+
+#include "led_strip.h"
+#include "freertos/task.h"
+#include <string.h>
+
+// gb: Need handle to web server task
+#include "webserver.h"
+// gb: LED_STRIP_TASK_SIZE was to small. Increased size in order
+// to avoid buffer overflows.
+/* #define LED_STRIP_TASK_SIZE (512) */
+#define LED_STRIP_TASK_SIZE (1024)
+#define LED_STRIP_TASK_PRIORITY (configMAX_PRIORITIES - 1)
+
+#define LED_STRIP_REFRESH_PERIOD_MS (30U) // TODO: add as parameter to led_strip_init
+
+#define LED_STRIP_NUM_RMT_ITEMS_PER_LED (24U) // Assumes 24 bit color for each led
+
+// RMT Clock source is @ 80 MHz. Dividing it by 8 gives us 10 MHz frequency, or 100ns period.
+#define LED_STRIP_RMT_CLK_DIV (8)
+
+/****************************
+ WS2812 Timing
+ ****************************/
+#define LED_STRIP_RMT_TICKS_BIT_1_HIGH_WS2812 9 // 900ns (900ns +/- 150ns per datasheet)
+#define LED_STRIP_RMT_TICKS_BIT_1_LOW_WS2812 3 // 300ns (350ns +/- 150ns per datasheet)
+#define LED_STRIP_RMT_TICKS_BIT_0_HIGH_WS2812 3 // 300ns (350ns +/- 150ns per datasheet)
+#define LED_STRIP_RMT_TICKS_BIT_0_LOW_WS2812 9 // 900ns (900ns +/- 150ns per datasheet)
+
+/****************************
+ SK6812 Timing
+ ****************************/
+#define LED_STRIP_RMT_TICKS_BIT_1_HIGH_SK6812 6
+#define LED_STRIP_RMT_TICKS_BIT_1_LOW_SK6812 6
+#define LED_STRIP_RMT_TICKS_BIT_0_HIGH_SK6812 3
+#define LED_STRIP_RMT_TICKS_BIT_0_LOW_SK6812 9
+
+/****************************
+ APA106 Timing
+ ****************************/
+#define LED_STRIP_RMT_TICKS_BIT_1_HIGH_APA106 14 // 1.36us +/- 150ns per datasheet
+#define LED_STRIP_RMT_TICKS_BIT_1_LOW_APA106 3 // 350ns +/- 150ns per datasheet
+#define LED_STRIP_RMT_TICKS_BIT_0_HIGH_APA106 3 // 350ns +/- 150ns per datasheet
+#define LED_STRIP_RMT_TICKS_BIT_0_LOW_APA106 14 // 1.36us +/- 150ns per datasheet
+
+// Function pointer for generating waveforms based on different LED drivers
+typedef void (*led_fill_rmt_items_fn)(struct led_color_t *led_strip_buf, rmt_item32_t *rmt_items, uint32_t led_strip_length);
+
+static inline void led_strip_fill_item_level(rmt_item32_t* item, int high_ticks, int low_ticks)
+{
+ item->level0 = 1;
+ item->duration0 = high_ticks;
+ item->level1 = 0;
+ item->duration1 = low_ticks;
+}
+
+static inline void led_strip_rmt_bit_1_sk6812(rmt_item32_t* item)
+{
+ led_strip_fill_item_level(item, LED_STRIP_RMT_TICKS_BIT_1_HIGH_SK6812, LED_STRIP_RMT_TICKS_BIT_1_LOW_SK6812);
+}
+
+static inline void led_strip_rmt_bit_0_sk6812(rmt_item32_t* item)
+{
+ led_strip_fill_item_level(item, LED_STRIP_RMT_TICKS_BIT_0_HIGH_SK6812, LED_STRIP_RMT_TICKS_BIT_0_LOW_SK6812);
+}
+
+static void led_strip_fill_rmt_items_sk6812(struct led_color_t *led_strip_buf, rmt_item32_t *rmt_items, uint32_t led_strip_length)
+{
+ uint32_t rmt_items_index = 0;
+ for (uint32_t led_index = 0; led_index < led_strip_length; led_index++) {
+ struct led_color_t led_color = led_strip_buf[led_index];
+
+ for (uint8_t bit = 8; bit != 0; bit--) {
+ uint8_t bit_set = (led_color.green >> (bit - 1)) & 1;
+ if(bit_set) {
+ led_strip_rmt_bit_1_sk6812(&(rmt_items[rmt_items_index]));
+ } else {
+ led_strip_rmt_bit_0_sk6812(&(rmt_items[rmt_items_index]));
+ }
+ rmt_items_index++;
+ }
+ for (uint8_t bit = 8; bit != 0; bit--) {
+ uint8_t bit_set = (led_color.red >> (bit - 1)) & 1;
+ if(bit_set) {
+ led_strip_rmt_bit_1_sk6812(&(rmt_items[rmt_items_index]));
+ } else {
+ led_strip_rmt_bit_0_sk6812(&(rmt_items[rmt_items_index]));
+ }
+ rmt_items_index++;
+ }
+ for (uint8_t bit = 8; bit != 0; bit--) {
+ uint8_t bit_set = (led_color.blue >> (bit - 1)) & 1;
+ if(bit_set) {
+ led_strip_rmt_bit_1_sk6812(&(rmt_items[rmt_items_index]));
+ } else {
+ led_strip_rmt_bit_0_sk6812(&(rmt_items[rmt_items_index]));
+ }
+ rmt_items_index++;
+ }
+ }
+}
+
+static inline void led_strip_rmt_bit_1_ws2812(rmt_item32_t* item)
+{
+ led_strip_fill_item_level(item, LED_STRIP_RMT_TICKS_BIT_1_HIGH_WS2812, LED_STRIP_RMT_TICKS_BIT_1_LOW_WS2812);
+}
+
+static inline void led_strip_rmt_bit_0_ws2812(rmt_item32_t* item)
+{
+ led_strip_fill_item_level(item, LED_STRIP_RMT_TICKS_BIT_0_HIGH_WS2812, LED_STRIP_RMT_TICKS_BIT_0_LOW_WS2812);
+}
+
+static void led_strip_fill_rmt_items_ws2812(struct led_color_t *led_strip_buf, rmt_item32_t *rmt_items, uint32_t led_strip_length)
+{
+ uint32_t rmt_items_index = 0;
+ for (uint32_t led_index = 0; led_index < led_strip_length; led_index++) {
+ struct led_color_t led_color = led_strip_buf[led_index];
+
+ for (uint8_t bit = 8; bit != 0; bit--) {
+ uint8_t bit_set = (led_color.green >> (bit - 1)) & 1;
+ if(bit_set) {
+ led_strip_rmt_bit_1_ws2812(&(rmt_items[rmt_items_index]));
+ } else {
+ led_strip_rmt_bit_0_ws2812(&(rmt_items[rmt_items_index]));
+ }
+ rmt_items_index++;
+ }
+ for (uint8_t bit = 8; bit != 0; bit--) {
+ uint8_t bit_set = (led_color.red >> (bit - 1)) & 1;
+ if(bit_set) {
+ led_strip_rmt_bit_1_ws2812(&(rmt_items[rmt_items_index]));
+ } else {
+ led_strip_rmt_bit_0_ws2812(&(rmt_items[rmt_items_index]));
+ }
+ rmt_items_index++;
+ }
+ for (uint8_t bit = 8; bit != 0; bit--) {
+ uint8_t bit_set = (led_color.blue >> (bit - 1)) & 1;
+ if(bit_set) {
+ led_strip_rmt_bit_1_ws2812(&(rmt_items[rmt_items_index]));
+ } else {
+ led_strip_rmt_bit_0_ws2812(&(rmt_items[rmt_items_index]));
+ }
+ rmt_items_index++;
+ }
+ }
+}
+
+static inline void led_strip_rmt_bit_1_apa106(rmt_item32_t* item)
+{
+ led_strip_fill_item_level(item, LED_STRIP_RMT_TICKS_BIT_1_HIGH_APA106, LED_STRIP_RMT_TICKS_BIT_1_LOW_APA106);
+}
+
+static inline void led_strip_rmt_bit_0_apa106(rmt_item32_t* item)
+{
+ led_strip_fill_item_level(item, LED_STRIP_RMT_TICKS_BIT_0_HIGH_APA106, LED_STRIP_RMT_TICKS_BIT_0_LOW_APA106);
+}
+
+static void led_strip_fill_rmt_items_apa106(struct led_color_t *led_strip_buf, rmt_item32_t *rmt_items, uint32_t led_strip_length)
+{
+ uint32_t rmt_items_index = 0;
+ for (uint32_t led_index = 0; led_index < led_strip_length; led_index++) {
+ struct led_color_t led_color = led_strip_buf[led_index];
+
+ for (uint8_t bit = 8; bit != 0; bit--) {
+ uint8_t bit_set = (led_color.red >> (bit - 1)) & 1;
+ if(bit_set) {
+ led_strip_rmt_bit_1_apa106(&(rmt_items[rmt_items_index]));
+ } else {
+ led_strip_rmt_bit_0_apa106(&(rmt_items[rmt_items_index]));
+ }
+ rmt_items_index++;
+ }
+ for (uint8_t bit = 8; bit != 0; bit--) {
+ uint8_t bit_set = (led_color.green >> (bit - 1)) & 1;
+ if(bit_set) {
+ led_strip_rmt_bit_1_apa106(&(rmt_items[rmt_items_index]));
+ } else {
+ led_strip_rmt_bit_0_apa106(&(rmt_items[rmt_items_index]));
+ }
+ rmt_items_index++;
+ }
+ for (uint8_t bit = 8; bit != 0; bit--) {
+ uint8_t bit_set = (led_color.blue >> (bit - 1)) & 1;
+ if(bit_set) {
+ led_strip_rmt_bit_1_apa106(&(rmt_items[rmt_items_index]));
+ } else {
+ led_strip_rmt_bit_0_apa106(&(rmt_items[rmt_items_index]));
+ }
+ rmt_items_index++;
+ }
+ }
+}
+
+static void led_strip_task(void *arg)
+{
+ struct led_strip_t *led_strip = (struct led_strip_t *)arg;
+ led_fill_rmt_items_fn led_make_waveform = NULL;
+ bool make_new_rmt_items = true;
+ bool prev_showing_buf_1 = !led_strip->showing_buf_1;
+
+ size_t num_items_malloc = (LED_STRIP_NUM_RMT_ITEMS_PER_LED * led_strip->led_strip_length);
+ rmt_item32_t *rmt_items = (rmt_item32_t*) malloc(sizeof(rmt_item32_t) * num_items_malloc);
+ if (!rmt_items) {
+ vTaskDelete(NULL);
+ }
+
+ switch (led_strip->rgb_led_type) {
+ case RGB_LED_TYPE_WS2812:
+ led_make_waveform = led_strip_fill_rmt_items_ws2812;
+ break;
+
+ case RGB_LED_TYPE_SK6812:
+ led_make_waveform = led_strip_fill_rmt_items_sk6812;
+ break;
+
+ case RGB_LED_TYPE_APA106:
+ led_make_waveform = led_strip_fill_rmt_items_apa106;
+ break;
+
+ default:
+ // Will avoid keeping it point to NULL
+ led_make_waveform = led_strip_fill_rmt_items_ws2812;
+ break;
+ };
+
+ for(;;) {
+ // Next line added by gb: Suspend web server task to avoid flicker.
+ vTaskSuspend(http_server_task);
+ // Changed by gb
+ // rmt_wait_tx_done(led_strip->rmt_channel);
+ rmt_wait_tx_done(led_strip->rmt_channel, portMAX_DELAY);
+ xSemaphoreTake(led_strip->access_semaphore, portMAX_DELAY);
+
+ /*
+ * If buf 1 was previously being shown and now buf 2 is being shown,
+ * it should update the new rmt items array. If buf 2 was previous being shown
+ * and now buf 1 is being shown, it should update the new rmt items array.
+ * Otherwise, no need to update the array
+ */
+ if ((prev_showing_buf_1 == true) && (led_strip->showing_buf_1 == false)) {
+ make_new_rmt_items = true;
+ } else if ((prev_showing_buf_1 == false) && (led_strip->showing_buf_1 == true)) {
+ make_new_rmt_items = true;
+ } else {
+ make_new_rmt_items = false;
+ }
+
+ if (make_new_rmt_items) {
+ if (led_strip->showing_buf_1) {
+ led_make_waveform(led_strip->led_strip_buf_1, rmt_items, led_strip->led_strip_length);
+ } else {
+ led_make_waveform(led_strip->led_strip_buf_2, rmt_items, led_strip->led_strip_length);
+ }
+ }
+
+ rmt_write_items(led_strip->rmt_channel, rmt_items, num_items_malloc, false);
+ prev_showing_buf_1 = led_strip->showing_buf_1;
+ xSemaphoreGive(led_strip->access_semaphore);
+ // Next line added by gb
+ vTaskResume(http_server_task);
+ vTaskDelay(LED_STRIP_REFRESH_PERIOD_MS / portTICK_PERIOD_MS);
+ }
+
+ if (rmt_items) {
+ free(rmt_items);
+ }
+ vTaskDelete(NULL);
+}
+
+static bool led_strip_init_rmt(struct led_strip_t *led_strip)
+{
+ rmt_config_t rmt_cfg = {
+ .rmt_mode = RMT_MODE_TX,
+ .channel = led_strip->rmt_channel,
+ .clk_div = LED_STRIP_RMT_CLK_DIV,
+ .gpio_num = led_strip->gpio,
+ .mem_block_num = 1,
+ .tx_config = {
+ .loop_en = false,
+ .carrier_freq_hz = 100, // Not used, but has to be set to avoid divide by 0 err
+ .carrier_duty_percent = 50,
+ .carrier_level = RMT_CARRIER_LEVEL_LOW,
+ .carrier_en = false,
+ .idle_level = RMT_IDLE_LEVEL_LOW,
+ .idle_output_en = true,
+ }
+ };
+
+ esp_err_t cfg_ok = rmt_config(&rmt_cfg);
+ if (cfg_ok != ESP_OK) {
+ return false;
+ }
+ esp_err_t install_ok = rmt_driver_install(rmt_cfg.channel, 0, 0);
+ if (install_ok != ESP_OK) {
+ return false;
+ }
+
+ return true;
+}
+
+bool led_strip_init(struct led_strip_t *led_strip)
+{
+ TaskHandle_t led_strip_task_handle;
+
+ if ((led_strip == NULL) ||
+ (led_strip->rmt_channel == RMT_CHANNEL_MAX) ||
+ (led_strip->gpio > GPIO_NUM_33) || // only inputs above 33
+ (led_strip->led_strip_buf_1 == NULL) ||
+ (led_strip->led_strip_buf_2 == NULL) ||
+ (led_strip->led_strip_length == 0) ||
+ (led_strip->access_semaphore == NULL)) {
+ return false;
+ }
+
+ if(led_strip->led_strip_buf_1 == led_strip->led_strip_buf_2) {
+ return false;
+ }
+
+ memset(led_strip->led_strip_buf_1, 0, sizeof(struct led_color_t) * led_strip->led_strip_length);
+ memset(led_strip->led_strip_buf_2, 0, sizeof(struct led_color_t) * led_strip->led_strip_length);
+
+ bool init_rmt = led_strip_init_rmt(led_strip);
+ if (!init_rmt) {
+ return false;
+ }
+
+ xSemaphoreGive(led_strip->access_semaphore);
+ BaseType_t task_created = xTaskCreate(led_strip_task,
+ "led_strip_task",
+ LED_STRIP_TASK_SIZE,
+ led_strip,
+ LED_STRIP_TASK_PRIORITY,
+ &led_strip_task_handle
+ );
+
+ if (!task_created) {
+ return false;
+ }
+
+ return true;
+}
+
+bool led_strip_set_pixel_color(struct led_strip_t *led_strip, uint32_t pixel_num, struct led_color_t *color)
+{
+ bool set_led_success = true;
+
+ if ((!led_strip) || (!color) || (pixel_num > led_strip->led_strip_length)) {
+ return false;
+ }
+
+ if (led_strip->showing_buf_1) {
+ led_strip->led_strip_buf_2[pixel_num] = *color;
+ } else {
+ led_strip->led_strip_buf_1[pixel_num] = *color;
+ }
+
+ return set_led_success;
+}
+
+bool led_strip_set_pixel_rgb(struct led_strip_t *led_strip, uint32_t pixel_num, uint8_t red, uint8_t green, uint8_t blue)
+{
+ bool set_led_success = true;
+
+ if ((!led_strip) || (pixel_num > led_strip->led_strip_length)) {
+ return false;
+ }
+
+ if (led_strip->showing_buf_1) {
+ led_strip->led_strip_buf_2[pixel_num].red = red;
+ led_strip->led_strip_buf_2[pixel_num].green = green;
+ led_strip->led_strip_buf_2[pixel_num].blue = blue;
+ } else {
+ led_strip->led_strip_buf_1[pixel_num].red = red;
+ led_strip->led_strip_buf_1[pixel_num].green = green;
+ led_strip->led_strip_buf_1[pixel_num].blue = blue;
+ }
+
+ return set_led_success;
+}
+
+bool led_strip_get_pixel_color(struct led_strip_t *led_strip, uint32_t pixel_num, struct led_color_t *color)
+{
+ bool get_success = true;
+
+ if ((!led_strip) ||
+ (pixel_num > led_strip->led_strip_length) ||
+ (!color)) {
+ color = NULL;
+ return false;
+ }
+
+ if (led_strip->showing_buf_1) {
+ *color = led_strip->led_strip_buf_1[pixel_num];
+ } else {
+ *color = led_strip->led_strip_buf_2[pixel_num];
+ }
+
+ return get_success;
+}
+
+/**
+ * Updates the led buffer to be shown
+ */
+bool led_strip_show(struct led_strip_t *led_strip)
+{
+ bool success = true;
+
+ if (!led_strip) {
+ return false;
+ }
+
+ xSemaphoreTake(led_strip->access_semaphore, portMAX_DELAY);
+ if (led_strip->showing_buf_1) {
+ led_strip->showing_buf_1 = false;
+ memset(led_strip->led_strip_buf_1, 0, sizeof(struct led_color_t) * led_strip->led_strip_length);
+ } else {
+ led_strip->showing_buf_1 = true;
+ memset(led_strip->led_strip_buf_2, 0, sizeof(struct led_color_t) * led_strip->led_strip_length);
+ }
+ xSemaphoreGive(led_strip->access_semaphore);
+
+ return success;
+}
+
+/**
+ * Clears the LED strip
+ */
+bool led_strip_clear(struct led_strip_t *led_strip)
+{
+ bool success = true;
+
+ if (!led_strip) {
+ return false;
+ }
+
+ if (led_strip->showing_buf_1) {
+ memset(led_strip->led_strip_buf_2, 0, sizeof(struct led_color_t) * led_strip->led_strip_length);
+ } else {
+ memset(led_strip->led_strip_buf_1, 0, sizeof(struct led_color_t) * led_strip->led_strip_length);
+ }
+
+ return success;
+}
diff --git a/main/led_strip.h b/main/led_strip.h
@@ -0,0 +1,97 @@
+/* ---------------------------------------------------------------------------
+ File: led_strip.h
+ Author(s): Lucas Bruder <LBruder@me.com>
+ Date Created: 11/23/2016
+ Last modified: 11/26/2016
+
+ Description:
+ This library can drive led strips through the RMT module on the ESP32.
+ ------------------------------------------------------------------------ */
+
+#ifndef LED_STRIP_H
+#define LED_STRIP_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <driver/rmt.h>
+#include "freertos/semphr.h"
+
+#include <stddef.h>
+
+enum rgb_led_type_t {
+ RGB_LED_TYPE_WS2812 = 0,
+ RGB_LED_TYPE_SK6812 = 1,
+ RGB_LED_TYPE_APA106 = 2,
+
+ RGB_LED_TYPE_MAX,
+};
+
+/**
+ * RGB LED colors
+ */
+struct led_color_t {
+ uint8_t red;
+ uint8_t green;
+ uint8_t blue;
+};
+
+struct led_strip_t {
+ enum rgb_led_type_t rgb_led_type;
+ uint32_t led_strip_length;
+
+ // RMT peripheral settings
+ rmt_channel_t rmt_channel;
+
+ /*
+ * Interrupt table is located in soc.h
+ * As of 11/27/16, reccomended interrupts are:
+ * 9, 12, 13, 17, 18, 19, 20, 21 or 23
+ * Ensure that the same interrupt number isn't used twice
+ * across all libraries
+ */
+ int rmt_interrupt_num;
+
+ gpio_num_t gpio; // Must be less than GPIO_NUM_33
+
+ // Double buffering elements
+ bool showing_buf_1;
+ struct led_color_t *led_strip_buf_1;
+ struct led_color_t *led_strip_buf_2;
+
+ SemaphoreHandle_t access_semaphore;
+};
+
+bool led_strip_init(struct led_strip_t *led_strip);
+
+/**
+ * Sets the pixel at pixel_num to color.
+ */
+bool led_strip_set_pixel_color(struct led_strip_t *led_strip, uint32_t pixel_num, struct led_color_t *color);
+bool led_strip_set_pixel_rgb(struct led_strip_t *led_strip, uint32_t pixel_num, uint8_t red, uint8_t green, uint8_t blue);
+/**
+ * Get the pixel color at pixel_num for the led strip that is currently being shown!
+ * NOTE: If you call set_pixel_color then get_pixel_color for the same pixel_num, you will not
+ * get back the same pixel value. This gets you the color of the pixel currently being shown, not the one
+ * being updated
+ *
+ * If there is an invalid argument, color will point to NULL and this function will return false.
+ */
+bool led_strip_get_pixel_color(struct led_strip_t *led_strip, uint32_t pixel_num, struct led_color_t *color);
+
+/**
+ * Updates the led buffer to be shown using double buffering.
+ */
+bool led_strip_show(struct led_strip_t *led_strip);
+
+/**
+ * Clears the LED strip.
+ */
+bool led_strip_clear(struct led_strip_t *led_strip);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // LED_STRIP_H
diff --git a/main/main.c b/main/main.c
@@ -0,0 +1,387 @@
+/*
+ Copyright © 2021 Gerd Beuster <gerd@frombelow.net>
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+
+*/
+
+/* {{{ Includes and definitions */
+
+#include "main.h"
+#include "debug.h"
+#include "nvs_flash.h"
+#include "led.h"
+#include <string.h>
+
+#define delay(ms) (vTaskDelay(ms/portTICK_RATE_MS))
+
+
+/* }}} */
+
+/* {{{ Processing of HTML pages */
+
+/* Page templates */
+
+const static char http_html_hdr[] =
+ "HTTP/1.1 200 OK\r\nContent-type: text/html\r\n\r\n";
+
+const static char http_index_template_html[] =
+#include "html_templates/index.html"
+
+const static char http_config_template_html[] =
+#include "html_templates/config.html"
+
+const static char http_reset_html[] =
+#include "html_templates/reset.html"
+
+/* Request handlers */
+
+static void handler_index_html(struct netconn *conn, char *base_url_begin,
+ char *base_url_end, char *parameters){
+ /* Send the HTML header
+ * subtract 1 from the size, since we don't send the \0 in the string
+ * NETCONN_NOCOPY: our data is const static, so no need to copy it
+ */
+ netconn_write(conn, http_html_hdr, sizeof(http_html_hdr)-1, NETCONN_NOCOPY);
+ // Process request (update state)
+ char *val_start, *val_end;
+ if(url_key_value(parameters, "mode", &val_start, &val_end)){
+ uint8_t val_length = val_end-val_start;
+ xSemaphoreTake(blinkenstrip_state_changed_semaphore, portMAX_DELAY);
+ blinkenstrip_state_has_changed = true;
+ if(strncmp("sequence", val_start, val_length) == 0)
+ blinkenstrip_state = BLINKENSTRIP_STATE_UNICOLOR_RAINBOW;
+ else if(strncmp("blink", val_start, val_length) == 0)
+ blinkenstrip_state = BLINKENSTRIP_STATE_RANDOM_ON_OFF;
+ else if(strncmp("running", val_start, val_length) == 0)
+ blinkenstrip_state = BLINKENSTRIP_STATE_RUNNING_DOT;
+ else if(strncmp("cellular", val_start, val_length) == 0)
+ blinkenstrip_state = BLINKENSTRIP_STATE_CELLULAR_AUTOMATON;
+ else if(strncmp("lines", val_start, val_length) == 0)
+ blinkenstrip_state = BLINKENSTRIP_STATE_LINE_BY_LINE;
+ else if(strncmp("worm", val_start, val_length) == 0)
+ blinkenstrip_state = BLINKENSTRIP_STATE_WORM;
+ else if(strncmp("rgb", val_start, val_length) == 0){
+ blinkenstrip_state = BLINKENSTRIP_STATE_UNICOLOR_RGB;
+ if(url_key_value(parameters, "red", &val_start, &val_end)){
+ *val_end ='\0';
+ led_color_rgb.red = max_brightness < atol(val_start) ?
+ max_brightness : atol(val_start);
+ *val_end ='&';
+ }
+ if(url_key_value(parameters, "green", &val_start, &val_end)){
+ *val_end ='\0';
+ led_color_rgb.green = max_brightness < atol(val_start) ?
+ max_brightness : atol(val_start);
+ *val_end ='&';
+ }
+ if(url_key_value(parameters, "blue", &val_start, &val_end)){
+ *val_end ='\0';
+ led_color_rgb.blue = max_brightness < atol(val_start) ?
+ max_brightness : atol(val_start);
+ *val_end ='&';
+ }
+ }
+ else if(strncmp("experimental", val_start, val_length) == 0)
+ blinkenstrip_state = BLINKENSTRIP_STATE_EXPERIMENTAL;
+ xSemaphoreGive(blinkenstrip_state_changed_semaphore);
+ }
+ /* Return index page based on index_template_html. We have to fill
+ out the template. We have to set the maximal values for the RGB
+ sliders in the template. We allocating memory, we add 3 bytes to
+ the length of the buffer for this. (Numbers may have up to 3
+ digits, but two are already reserved because of the "%d"s in the
+ template). We also add space for the name of the app. */
+ size_t http_index_html_len = (sizeof(http_index_template_html) +
+ sizeof(app_name)*3 + 3);
+ char *http_index_html = malloc(http_index_html_len);
+ snprintf(http_index_html, http_index_html_len, http_index_template_html,
+ app_name, app_name,
+ max_brightness, max_brightness, max_brightness);
+ netconn_write(conn, http_index_html, strlen(http_index_html)-1,
+ NETCONN_NOCOPY);
+ free(http_index_html);
+}
+
+static void handler_config_html(struct netconn *conn, char *base_url_begin,
+ char *base_url_end, char *parameters){
+ /* Request for config.html */
+ netconn_write(conn, http_html_hdr, sizeof(http_html_hdr)-1, NETCONN_NOCOPY);
+ /* Check if the user supplied values. If yes, we store the
+ values and reset. If not, we show config.html. */
+ char *val_start, *val_end;
+ if(url_key_value(parameters, "app_name", &val_start, &val_end)){
+ /* We got values. Store them and reset. */
+ nvs_flash_init();
+ nvs_handle flash_handle;
+ nvs_open("blinkenstrip", NVS_READWRITE, &flash_handle);
+ *(val_end) = '\0'; // Terminate string
+ nvs_set_str(flash_handle, "app_name", val_start);
+ *(val_end) = '&'; // Restore parameters
+ url_key_value(parameters, "essid", &val_start, &val_end);
+ *(val_end) = '\0'; // Terminate string
+ nvs_set_str(flash_handle, "wifi_essid", val_start);
+ *(val_end) = '&'; // Restore parameters
+ url_key_value(parameters, "password", &val_start, &val_end);
+ *(val_end) = '\0'; // Terminate string
+ if(strlen(val_start) >= 8) {
+ nvs_set_str(flash_handle, "wifi_password", val_start);
+ };
+ *(val_end) = '&'; // Restore parameters
+ url_key_value(parameters, "ap", &val_start, &val_end);
+ *(val_end) = '\0'; // Terminate string
+ nvs_set_u8(flash_handle, "wifi_ap", !strcmp(val_start, "yes"));
+ *(val_end) = '&'; // Restore parameters
+ url_key_value(parameters, "led_strip_w", &val_start, &val_end);
+ *(val_end) = '\0'; // Terminate string
+ nvs_set_u16(flash_handle, "led_strip_w", atol(val_start));
+ *(val_end) = '&'; // Restore parameters
+ url_key_value(parameters, "led_strip_h", &val_start, &val_end);
+ *(val_end) = '\0'; // Terminate string
+ nvs_set_u16(flash_handle, "led_strip_h", atol(val_start));
+ *(val_end) = '&'; // Restore parameters
+ url_key_value(parameters, "snake", &val_start, &val_end);
+ *(val_end) = '\0'; // Terminate string
+ nvs_set_u8(flash_handle, "snake", !strcmp(val_start, "yes"));
+ *(val_end) = '&'; // Restore parameters
+ url_key_value(parameters, "wrap_around", &val_start, &val_end);
+ *(val_end) = '\0'; // Terminate string
+ nvs_set_u8(flash_handle, "wrap_around", !strcmp(val_start, "yes"));
+ *(val_end) = '&'; // Restore parameters
+ url_key_value(parameters, "number_of_worms", &val_start, &val_end);
+ *(val_end) = '\0'; // Terminate string
+ nvs_set_u8(flash_handle, "number_of_worms", (uint8_t) atol(val_start));
+ *(val_end) = '&'; // Restore parameters
+ url_key_value(parameters, "max_brightness", &val_start, &val_end);
+ *(val_end) = '\0'; // Terminate string
+ nvs_set_u8(flash_handle, "max_brightness", (uint8_t) atol(val_start));
+ *(val_end) = '&'; // Restore parameters
+
+ nvs_commit(flash_handle);
+ nvs_close(flash_handle);
+ /* Perform reset. Since we reset, it is not necessary to
+ update our internal configuration variables, since this
+ will be done once we start again after the reset. */
+ netconn_write(conn, http_reset_html, strlen(http_reset_html)-1,
+ NETCONN_NOCOPY);
+ /* Since we want to perform a reset, we have to close the
+ connection right here. */
+ netconn_close(conn);
+ delay(1000);
+ esp_restart();
+ }
+ else {
+ /* Return config page based on config_template_html. We have to
+ fill out the template. This requires creation of a buffer of
+ apropriate size. The numbers in the size calculation are: 3
+ characters each for number of horizontal leds, vertical leds,
+ number of worms, and max brightness, plus length of string
+ "checked" *4 which will be a parameter for radio buttons. We
+ subtract 7*2 bytes for the format variables ("%s") in the
+ template file. */
+ size_t http_config_html_len = (sizeof(http_config_template_html) +
+ sizeof(app_name)*3 +
+ sizeof(wifi_essid) +
+ sizeof(wifi_password) +
+ strlen("checked")*4 + 4*3 - 7*2);
+ char *http_config_html = malloc(http_config_html_len);
+ snprintf(http_config_html, http_config_html_len,
+ http_config_template_html, app_name, app_name, app_name,
+ wifi_essid, wifi_password,
+ wifi_ap ? "checked" : "", wifi_ap ? "" : "checked",
+ led_strip_w, led_strip_h,
+ snake ? "checked" : "", snake ? "" : "checked",
+ wrap_around ? "checked" : "", wrap_around ? "" : "checked",
+ number_of_worms,
+ max_brightness);
+ netconn_write(conn, http_config_html, strlen(http_config_html)-1,
+ NETCONN_NOCOPY);
+ free(http_config_html);
+ }
+}
+
+/* }}} */
+
+/* {{{ main */
+
+void read_configuration_from_flash(){
+ // All global configuration variables are defined in led.h (some
+ // refactoring required ...)
+ nvs_flash_init();
+ nvs_handle flash_handle;
+ nvs_open("blinkenstrip", NVS_READWRITE, &flash_handle);
+ size_t required_size;
+ bool flash_changed = false;
+ esp_err_t err = nvs_get_str(flash_handle, "app_name", NULL, &required_size);
+ if(err != ESP_OK){
+ /* Not yet initialized. Use default. */
+ nvs_set_str(flash_handle, "app_name", CONFIG_APP_NAME);
+ nvs_get_str(flash_handle, "app_name", NULL, &required_size);
+ flash_changed = true;
+ }
+ app_name = malloc(required_size);
+ nvs_get_str(flash_handle, "app_name", app_name, &required_size);
+ err = nvs_get_str(flash_handle, "wifi_essid", NULL, &required_size);
+ if(err != ESP_OK){
+ /* Not yet initialized. Use default. */
+ nvs_set_str(flash_handle, "wifi_essid", CONFIG_WIFI_SSID);
+ nvs_get_str(flash_handle, "wifi_essid", NULL, &required_size);
+ flash_changed = true;
+ }
+ wifi_essid = malloc(required_size);
+ nvs_get_str(flash_handle, "wifi_essid", wifi_essid, &required_size);
+ err = nvs_get_str(flash_handle, "wifi_password", NULL, &required_size);
+ wifi_password = malloc(required_size);
+ if(err != ESP_OK){
+ /* Not yet initialized. Use default. */
+ nvs_set_str(flash_handle, "wifi_password", CONFIG_WIFI_PASSWORD);
+ nvs_get_str(flash_handle, "wifi_password", NULL, &required_size);
+ flash_changed = true;
+ }
+ nvs_get_str(flash_handle, "wifi_password", wifi_password, &required_size);
+ if(nvs_get_u8(flash_handle, "wifi_ap", &wifi_ap) != ESP_OK){
+ /* Not yet initialized. Use default. */
+ #ifdef CONFIG_WIFI_AP
+ wifi_ap = true;
+ #else
+ wifi_ap = false;
+ #endif
+ nvs_set_u8(flash_handle, "wifi_ap", wifi_ap);
+ flash_changed = true;
+ }
+ if(nvs_get_u16(flash_handle, "led_strip_w", &led_strip_w) != ESP_OK){
+ /* Not yet initialized. Use default. */
+ led_strip_w = CONFIG_LED_STRIP_WIDTH;
+ nvs_set_u16(flash_handle, "led_strip_w", led_strip_w);
+ flash_changed = true;
+ }
+ if(nvs_get_u16(flash_handle, "led_strip_h", &led_strip_h) != ESP_OK){
+ /* Not yet initialized. Use default. */
+ led_strip_h = CONFIG_LED_STRIP_HEIGHT;
+ nvs_set_u16(flash_handle, "led_strip_h", led_strip_h);
+ flash_changed = true;
+ }
+ if(nvs_get_u8(flash_handle, "snake", &snake) != ESP_OK){
+ /* Not yet initialized. Use default. */
+ #ifdef CONFIG_LED_STRIP_SNAKE
+ snake = true;
+ #else
+ snake = false;
+ #endif
+ nvs_set_u8(flash_handle, "snake", snake);
+ flash_changed = true;
+ }
+ if(nvs_get_u8(flash_handle, "wrap_around", &wrap_around) != ESP_OK){
+ /* Not yet initialized. Use default. */
+ #ifdef CONFIG_LED_STRIP_WRAP_AROUND
+ wrap_around = true;
+ #else
+ wrap_around = false;
+ #endif
+ nvs_set_u8(flash_handle, "wrap_around", wrap_around);
+ flash_changed = true;
+ }
+ if(nvs_get_u8(flash_handle, "number_of_worms", &number_of_worms) != ESP_OK){
+ /* Not yet initialized. Use default. */
+ number_of_worms = CONFIG_NUMBER_OF_WORMS;
+ nvs_set_u8(flash_handle, "number_of_worms", number_of_worms);
+ flash_changed = true;
+ }
+ if(nvs_get_u8(flash_handle, "max_brightness", &max_brightness) != ESP_OK){
+ /* Not yet initialized. Use default. */
+ max_brightness = CONFIG_MAX_BRIGHTNESS;
+ nvs_set_u8(flash_handle, "max_brightness", max_brightness);
+ flash_changed = true;
+ }
+ if(nvs_get_u8(flash_handle, "blinkenstrip_state", (uint8_t *) &blinkenstrip_state) != ESP_OK){
+ /* Not yet initialized. Use default. */
+ blinkenstrip_state = BLINKENSTRIP_STATE_RANDOM_ON_OFF;
+ nvs_set_u8(flash_handle, "blinkenstrip_state", blinkenstrip_state);
+ flash_changed = true;
+ }
+ if(nvs_get_u8(flash_handle, "led_col_red", &led_color_rgb.red) != ESP_OK){
+ /* Not yet initialized. Use default. */
+ led_color_rgb.red = 0xFF;
+ nvs_set_u8(flash_handle, "led_col_red", led_color_rgb.red);
+ flash_changed = true;
+ }
+ if(nvs_get_u8(flash_handle, "led_col_green", &led_color_rgb.green) != ESP_OK){
+ /* Not yet initialized. Use default. */
+ led_color_rgb.green = 0xFF;
+ nvs_set_u8(flash_handle, "led_col_green", led_color_rgb.green);
+ flash_changed = true;
+ }
+ if(nvs_get_u8(flash_handle, "led_col_blue", &led_color_rgb.blue) != ESP_OK){
+ /* Not yet initialized. Use default. */
+ led_color_rgb.blue = 0xFF;
+ nvs_set_u8(flash_handle, "led_col_blue", led_color_rgb.blue);
+ flash_changed = true;
+ }
+ if(flash_changed){
+ nvs_commit(flash_handle);
+ }
+ nvs_close(flash_handle);
+ DEBUG_MSG(DEBUG, ("app_name: %s\n", app_name));
+ DEBUG_MSG(DEBUG, ("wifi_essid: %s\n", wifi_essid));
+ DEBUG_MSG(DEBUG, ("wifi_password: %s\n", wifi_password));
+ DEBUG_MSG(DEBUG, ("wifi_ap: %d\n", wifi_ap));
+ DEBUG_MSG(DEBUG, ("led_strip_w: %d\n", led_strip_w));
+ DEBUG_MSG(DEBUG, ("led_strip_h: %d\n", led_strip_h));
+ DEBUG_MSG(DEBUG, ("snake: %d\n", snake));
+ DEBUG_MSG(DEBUG, ("wrap_around: %d\n", wrap_around));
+ DEBUG_MSG(DEBUG, ("number_of_worms: %d\n", number_of_worms));
+ DEBUG_MSG(DEBUG, ("max_brightness: %d\n", max_brightness));
+ DEBUG_MSG(DEBUG, ("blinkenstrip_state: %d (%d, %d, %d)\n", blinkenstrip_state,
+ led_color_rgb.red, led_color_rgb.green, led_color_rgb.blue));
+}
+
+// Factory reset is triggered if factory reset pins are connected.
+void check_for_factory_reset(){
+ gpio_pad_select_gpio(CONFIG_PIN_FACTORY_RESET_A);
+ gpio_set_direction(CONFIG_PIN_FACTORY_RESET_A, GPIO_MODE_OUTPUT);
+ gpio_set_level(CONFIG_PIN_FACTORY_RESET_A, 1);
+ gpio_pad_select_gpio(CONFIG_PIN_FACTORY_RESET_B);
+ gpio_pullup_dis(CONFIG_PIN_FACTORY_RESET_B);
+ gpio_set_direction(CONFIG_PIN_FACTORY_RESET_B, GPIO_MODE_INPUT);
+ if(gpio_get_level(CONFIG_PIN_FACTORY_RESET_B)){
+ DEBUG_MSG(DEBUG, ("Reset pin is HIGH!\n"));
+ /* RESET pins are connect. Clear NV memory! */
+ nvs_flash_init();
+ nvs_handle flash_handle;
+ nvs_open("blinkenstrip", NVS_READWRITE, &flash_handle);
+ nvs_erase_all(flash_handle);
+ nvs_commit(flash_handle);
+ nvs_close(flash_handle);
+ DEBUG_MSG(DEBUG, ("NV memory cleared!\n"));
+ delay(5000);
+ }
+ else{
+ DEBUG_MSG(DEBUG, ("Reset pin is LOW!\n"));
+ gpio_set_level(CONFIG_PIN_FACTORY_RESET_A, 0);
+ }
+}
+
+int app_main(void) {
+ check_for_factory_reset();
+ read_configuration_from_flash();
+
+ initialise_wifi(wifi_ap, wifi_essid, wifi_password);
+
+ xTaskCreate(&http_server, "http_server", 4096, NULL, 5, &http_server_task);
+ vTaskSuspend(http_server_task);
+ xTaskCreate(&led_controller, "main_led_task", 4096, NULL, 5, NULL);
+ return 0;
+}
+
+/* }}} */
diff --git a/main/main.h b/main/main.h
@@ -0,0 +1,42 @@
+/*
+ Copyright © 2021 Gerd Beuster <gerd@frombelow.net>
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+#ifndef MAIN_H
+#define MAIN_H
+
+#include "webserver.h"
+
+char *app_name;
+// WIFI configuration
+uint8_t wifi_ap;
+char *wifi_essid;
+char *wifi_password;
+
+/* Register page handlers */
+static void handler_index_html(struct netconn *conn, char *base_url_begin,
+ char *base_url_end, char *parameters);
+static void handler_config_html(struct netconn *conn, char *base_url_begin,
+ char *base_url_end, char *parameters);
+// These constants are used in webserver.c to dispatch requests.
+const page_handler_t page_handlers[] = {
+ { .handler = &handler_index_html, .url = "/" }
+ , { .handler = &handler_config_html, .url = "/config.html" }
+};
+const uint8_t number_of_page_handlers = (sizeof(page_handlers) /
+ sizeof(page_handlers[0]));
+
+/* Check internal voltage level. */
+extern int rom_phy_get_vdd33();
+#endif
diff --git a/main/webserver.c b/main/webserver.c
@@ -0,0 +1,306 @@
+/*
+ Wifi connection & web server
+
+ The wifi code is nearly identicial to the examples
+ examples/wifi/getting_started/station/main/station_example_main.c
+ and examples/wifi/getting_started/softAP/main/softap_example_main.c
+ which are part of the esp-idf distribution. This example code is in
+ the Public Domain (or CC0 licensed, at your option.)
+
+ Functions http_server_netconn_serve and http_server are (partly)
+ taken from
+ https://github.com/cmmakerclub/esp32-webserver/blob/master/main/main.c,
+ copyright (C) 2016 Espressif Systems, licensed under the Apache
+ License 2.0.
+
+ Everything else:
+
+ Copyright © 2021 Gerd Beuster <gerd@frombelow.net>
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+
+#include <string.h>
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+#include "freertos/event_groups.h"
+#include "esp_system.h"
+#include "esp_wifi.h"
+#include "esp_event.h"
+#include "esp_log.h"
+#include "nvs_flash.h"
+
+#include "lwip/err.h"
+#include "lwip/sys.h"
+
+#include "webserver.h"
+#include "debug.h"
+
+
+/* FreeRTOS event group to signal when we are connected*/
+static EventGroupHandle_t s_wifi_event_group;
+
+/* The event group allows multiple bits for each event, but we only care about two events:
+ * - we are connected to the AP with an IP
+ * - we failed to connect after the maximum amount of retries */
+#define WIFI_CONNECTED_BIT BIT0
+#define WIFI_FAIL_BIT BIT1
+
+static const char *TAG = "webserver";
+
+static void event_handler_join_network(void* arg, esp_event_base_t event_base,
+ int32_t event_id, void* event_data)
+{
+ if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
+ esp_wifi_connect();
+ } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
+ esp_wifi_connect();
+ } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
+ ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data;
+ ESP_LOGI(TAG, "got ip:" IPSTR, IP2STR(&event->ip_info.ip));
+ xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
+ }
+}
+
+static void event_handler_run_ap(void* arg, esp_event_base_t event_base,
+ int32_t event_id, void* event_data)
+{
+ if (event_id == WIFI_EVENT_AP_STACONNECTED) {
+ wifi_event_ap_staconnected_t* event = (wifi_event_ap_staconnected_t*) event_data;
+ ESP_LOGI(TAG, "station "MACSTR" join, AID=%d",
+ MAC2STR(event->mac), event->aid);
+ } else if (event_id == WIFI_EVENT_AP_STADISCONNECTED) {
+ wifi_event_ap_stadisconnected_t* event = (wifi_event_ap_stadisconnected_t*) event_data;
+ ESP_LOGI(TAG, "station "MACSTR" leave, AID=%d",
+ MAC2STR(event->mac), event->aid);
+ }
+}
+
+void initialise_wifi(uint8_t wifi_ap, char *wifi_essid,
+ char *wifi_password)
+{
+ s_wifi_event_group = xEventGroupCreate();
+ ESP_ERROR_CHECK(esp_netif_init());
+ ESP_ERROR_CHECK(esp_event_loop_create_default());
+ DEBUG_MSG(DEBUG, ("wifi_ap is %d\n", wifi_ap));
+ if(wifi_ap){
+ DEBUG_MSG(DEBUG, ("Starting wifi access point.\n"));
+ // Run our own WIFI access point.
+ esp_netif_create_default_wifi_ap();
+ wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
+ ESP_ERROR_CHECK(esp_wifi_init(&cfg));
+ ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID,
+ &event_handler_run_ap, NULL));
+ wifi_config_t wifi_config = {
+ .ap = {
+ .channel = 0,
+ .max_connection = 4,
+ .authmode = WIFI_AUTH_WPA_WPA2_PSK
+ },
+ };
+ if (strlen((char *) wifi_password) == 0) {
+ wifi_config.ap.authmode = WIFI_AUTH_OPEN;
+ }
+ else {
+ strncpy((char *) wifi_config.ap.password, wifi_password, 64);
+ }
+ strncpy((char *) wifi_config.ap.ssid, wifi_essid, 32);
+ ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_AP));
+ ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_AP, &wifi_config));
+ ESP_ERROR_CHECK(esp_wifi_start());
+ ESP_LOGI(TAG, "wifi_init_softap finished. SSID:%s password:%s",
+ wifi_config.ap.ssid, wifi_config.ap.password);
+ }
+ else{
+ // Connect to existing WIFI network.
+ esp_netif_create_default_wifi_sta();
+ wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
+ ESP_ERROR_CHECK(esp_wifi_init(&cfg));
+ ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID,
+ &event_handler_join_network,
+ NULL));
+ ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP,
+ &event_handler_join_network,
+ NULL));
+ wifi_config_t wifi_config = {
+ .sta = {
+ /* Setting a password implies
+ * station will connect to
+ * all security modes
+ * including WEP/WPA.
+ * However these modes are
+ * deprecated and not
+ * advisable to be
+ * used. Incase your Access
+ * point doesn't support
+ * WPA2, these mode can be
+ * enabled by commenting
+ * below line */
+ .threshold.authmode = WIFI_AUTH_WPA2_PSK,
+ .pmf_cfg = {
+ .capable = true,
+ .required = false
+ },
+ },
+ };
+ strncpy((char *) wifi_config.sta.ssid, wifi_essid, 32);
+ strncpy((char *) wifi_config.sta.password, wifi_password, 64);
+ ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA) );
+ ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config) );
+ ESP_ERROR_CHECK(esp_wifi_start() );
+
+ ESP_LOGI(TAG, "wifi_init_sta finished.");
+
+ /* Waiting until either the connection is established
+ * (WIFI_CONNECTED_BIT) or connection failed for the maximum
+ * number of re-tries (WIFI_FAIL_BIT). The bits are set by
+ * event_handler_join_network() (see above) */
+ EventBits_t bits = xEventGroupWaitBits(s_wifi_event_group,
+ WIFI_CONNECTED_BIT | WIFI_FAIL_BIT,
+ pdFALSE,
+ pdFALSE,
+ portMAX_DELAY);
+
+ /* xEventGroupWaitBits() returns the bits before the call
+ * returned, hence we can test which event actually happened. */
+ if (bits & WIFI_CONNECTED_BIT) {
+ ESP_LOGI(TAG, "connected to ap SSID:%s password:%s",
+ wifi_essid, wifi_password);
+ } else if (bits & WIFI_FAIL_BIT) {
+ ESP_LOGI(TAG, "Failed to connect to SSID:%s, password:%s",
+ wifi_essid, wifi_password);
+ } else {
+ ESP_LOGE(TAG, "UNEXPECTED EVENT");
+ }
+
+ ESP_ERROR_CHECK(esp_event_handler_unregister(IP_EVENT, IP_EVENT_STA_GOT_IP,
+ &event_handler_join_network));
+ ESP_ERROR_CHECK(esp_event_handler_unregister(WIFI_EVENT, ESP_EVENT_ANY_ID,
+ &event_handler_join_network));
+ vEventGroupDelete(s_wifi_event_group);
+ }
+}
+
+
+
+/* {{{ Webserver */
+
+// Retrieve value of parameter key starting at begin.
+// Begin and end of the value string are stored in value_begin and value_end.
+// The function returns true if the key was found, false otherwise.
+bool url_key_value(char *begin, const char *key,
+ char **value_begin, char **value_end) {
+ if ((begin == NULL) || (begin[0] == '\0') || (begin[0] == ' ')) {
+ // End of GET request reached and key not found.
+ return (false);
+ }
+ else {
+ if ((strncmp(key, begin, strlen(key)) == 0) &&
+ (*(begin+strlen(key)) == '=')) {
+ // Key found! Return value!
+ *value_begin = begin+strlen(key)+1;
+ *value_end = *value_begin + strcspn(*value_begin, " &");
+ return true;
+ }
+ else {
+ // Continue search recursively
+ begin = begin + strcspn(begin, " &");
+ if((begin[0] != '\0') && (begin[0] != ' ')) {
+ begin++;
+ }
+ return url_key_value(begin, key, value_begin, value_end);
+ }
+ }
+}
+
+// Get URL without any parameters.
+void base_url(char *url_with_parameters,
+ char **base_url_begin, char **base_url_end,
+ char **parameters_start) {
+ bool get_request = !strncmp(url_with_parameters, "GET", 3);
+ if(get_request){
+ *base_url_begin = url_with_parameters + 4;
+ *base_url_end = *base_url_begin + strcspn(*base_url_begin, " ?");
+ *parameters_start = *base_url_end+1;
+ }
+ else{
+ *base_url_begin = url_with_parameters + 5;
+ *base_url_end = *base_url_begin + strcspn(*base_url_begin, " ?");
+ *parameters_start = strstr(*base_url_end, "\r\n\r\n")+4;
+ }
+}
+
+static void http_server_netconn_serve(struct netconn *conn)
+{
+ struct netbuf *inbuf;
+ char *buf;
+ u16_t buflen;
+ err_t err;
+
+ /* Read the data from the port, blocking if nothing yet there.
+ We assume the request (the part we care about) is in one netbuf */
+ err = netconn_recv(conn, &inbuf);
+
+ if (err == ERR_OK) {
+ netbuf_data(inbuf, (void**)&buf, &buflen);
+ // Terminate buffer string.
+ *(buf+buflen) ='\0';
+ DEBUG_MSG(DEBUG, ("Receveid HTTP request:\n%s\n", buf));
+ char *base_url_begin, *base_url_end, *parameters_start;
+ base_url(buf, &base_url_begin, &base_url_end, ¶meters_start);
+ // Dispatch page handler
+ uint8_t i = 0;
+ while ((i < number_of_page_handlers) &&
+ strncmp(page_handlers[i].url, base_url_begin,
+ base_url_end-base_url_begin)){
+ i++;
+ };
+ if(i == number_of_page_handlers){
+ // No handler for this URL. Return error page.
+ const char error404[] =
+ "HTTP/1.1 404 Not Found\r\n\r\n<html><head><title>Not Found!</title></head><body><h1>Not Found!</body></html>\r\n";
+ netconn_write(conn, error404, sizeof(error404)-1, NETCONN_NOCOPY);
+ }
+ else{
+ // Serve page
+ (page_handlers[i].handler)(conn, base_url_begin, base_url_end,
+ parameters_start);
+ }
+ }
+ /* Close the connection (server closes in HTTP) */
+ netconn_close(conn);
+
+ /* Delete the buffer (netconn_recv gives us ownership,
+ so we have to make sure to deallocate the buffer) */
+ netbuf_delete(inbuf);
+}
+
+void http_server(void *pvParameters)
+{
+ struct netconn *conn, *newconn;
+ err_t err;
+ conn = netconn_new(NETCONN_TCP);
+ netconn_bind(conn, NULL, 80);
+ netconn_listen(conn);
+ do {
+ err = netconn_accept(conn, &newconn);
+ if (err == ERR_OK) {
+ http_server_netconn_serve(newconn);
+ netconn_delete(newconn);
+ }
+ } while(err == ERR_OK);
+ netconn_close(conn);
+ netconn_delete(conn);
+}
+
+/* }}} */
diff --git a/main/webserver.h b/main/webserver.h
@@ -0,0 +1,50 @@
+/*
+ Copyright © 2021 Gerd Beuster <gerd@frombelow.net>
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+#ifndef WEBSERVER_H
+#define WEBSERVER_H
+
+#include "esp_event.h"
+#include "lwip/api.h"
+
+// Data structure for HTML request handlers
+typedef struct {
+ void (*handler)(struct netconn *conn, char *base_url_begin,
+ char *base_url_end, char *parameters);
+ const char *url;
+} page_handler_t;
+
+// The actual HTML request handlers are application
+// specific. Therefore the are defined externally.
+extern const page_handler_t page_handlers[];
+extern const uint8_t number_of_page_handlers;
+
+// Functions to get web server running.
+void initialise_wifi(uint8_t wifi_ap, char *wifi_essid, char *wifi_password);
+void http_server(void *pvParameters);
+
+/* Functions for parsing HTTP requests */
+bool url_key_value(char *begin, const char *key,
+ char **value_begin, char **value_end);
+void base_url(char *url_with_parameters,
+ char **base_url_begin, char **base_url_end,
+ char **parameters_start);
+
+
+// Updating LEDs is extrem time sensitive.
+// We have to suspend the http server during updates.
+TaskHandle_t http_server_task;
+
+#endif