Rudra Lad
Published © MIT

NodeMCU ESP8266 NeoPixel Control web server

A simple web server hosted on ESP8266 to control effects of NeoPixel strip, using NodeMCU Lua Firmware

IntermediateFull instructions provided5 hours2,955
NodeMCU ESP8266 NeoPixel Control web server

Things used in this project

Hardware components

Wemos D1 Mini
Espressif Wemos D1 Mini
×1
LED Strip, NeoPixel Digital RGB
LED Strip, NeoPixel Digital RGB
×1
Capacitor 100 µF
Capacitor 100 µF
×1
Resistor 221 ohm
Resistor 221 ohm
×1
Perfboard PCB
×1

Software apps and online services

NodeMCU firmware
NodeMCU firmware
Esplorer IDE
Nodemcu cloud build service
NodeMCU PyFlasher
Python 3

Hand tools and fabrication machines

Soldering iron (generic)
Soldering iron (generic)
Solder Wire, Lead Free
Solder Wire, Lead Free

Story

Read more

Code

neopixel_control_STA_GET.lua

Lua
The main application file
wifi.setmode(wifi.STATION)  -- set wi-fi mode to station
wifi.sta.connect()          -- connect to router

-- state variables
str_mode = "p0"
str_color = "ff0000"
str_freq = "50"
r1 = 255
b1 = 0
g1 = 0
freq_ms = 50

-- declared in node_config.lua
--leds = 5
--channels = 3        -- RGB only, no W

np = require "neopixel"
np.pixel_init(leds, channels)

samples = 55
pos = 1
color = 1
mode = np.fill_pixel
dir = np.forward_shift

frame = pixbuf.newBuffer(leds, channels)
frame:fill(0, 0, 0)

rainbow = pixbuf.newBuffer(samples, channels)
rainbow:fill(0, 0, 0)

np.rainbow_buffer(rainbow)

animator = tmr.create()
animator:register(100, tmr.ALARM_SINGLE, np.all_off)

function all_leds_off()
    frame:fill(0, 0, 0)
    ws2812.write(frame)
end

function fill_all()
    frame:fill(g1, r1, b1)
    ws2812.write(frame)
end

function color_scroll()
    np.rotate(frame, 1, np.backward_shift)
    ws2812.write(frame)
end

function color_wheel()
    frame:fill(rainbow:get(pos))
    ws2812.write(frame)
    pos = ( pos % samples ) + 1
end

function color_swirl()
    np.rotate(rainbow, 1, np.forward_shift)
    frame = rainbow:sub(1, frame:size())
    ws2812.write(frame)
end

function cylon_eye()
    if dir == np.forward_shift then
        if pos >= leds then
            dir = np.backward_shift
            pos = pos - 1
            np.rotate(frame, 1, dir)
            ws2812.write(frame)
        else
            dir = np.forward_shift
            np.rotate(frame, 1, dir)
            ws2812.write(frame)
            pos = pos + 1
        end
    else
        if pos <= 1 then
            dir = np.forward_shift
            np.rotate(frame, 1, dir)
            ws2812.write(frame)
            pos = pos + 1
        else
            dir = np.backward_shift
            np.rotate(frame, 1, dir)
            ws2812.write(frame)
            pos = pos - 1
        end
    end
end

function cylon_eye_inverted()
    if dir == np.forward_shift then
        if pos >= leds then
            dir = np.backward_shift
            pos = pos - 1
            np.rotate(frame, 1, dir)
            ws2812.write(frame)
        else
            dir = np.forward_shift
            np.rotate(frame, 1, dir)
            ws2812.write(frame)
            pos = pos + 1
        end
    else
        if pos <= 1 then
            dir = np.forward_shift
            np.rotate(frame, 1, dir)
            ws2812.write(frame)
            pos = pos + 1
        else
            dir = np.backward_shift
            np.rotate(frame, 1, dir)
            ws2812.write(frame)
            pos = pos - 1
        end
    end
end

function converging()
    if leds % 2 == 0 then
        if pos >= ( leds / 2 ) then
            if mode == np.fill_pixel then
                frame:set(pos, np.elementary_colors:get(color))
                frame:set(pos + 1, np.elementary_colors:get(color))
                ws2812.write(frame)
                mode = np.unfill_pixel
                pos = 1
            else
                frame:set(pos, 0, 0, 0)
                frame:set(pos + 1, 0, 0, 0)
                ws2812.write(frame)
                mode = np.fill_pixel
                pos = 1
                color = color % (np.elementary_colors:size()) + 1
            end
        else
            if mode == np.fill_pixel then
                frame:set(pos, np.elementary_colors:get(color))
                frame:set(leds + 1 - pos, np.elementary_colors:get(color))
                ws2812.write(frame)
                pos = pos + 1
            else
                frame:set(pos, 0, 0, 0)
                frame:set(leds + 1 - pos, 0, 0, 0)
                ws2812.write(frame)
                pos = pos + 1
            end
        end
    else
        if pos >= ( (leds + 1) / 2 ) then
            if mode == np.fill_pixel then
                frame:set(pos, np.elementary_colors:get(color))
                ws2812.write(frame)
                mode = np.unfill_pixel
                pos = 1
            else
                frame:set(pos, 0, 0, 0)
                ws2812.write(frame)
                mode = np.fill_pixel
                pos = 1
                color = color % (np.elementary_colors:size()) + 1
            end
        else
            if mode == np.fill_pixel then
                frame:set(pos, np.elementary_colors:get(color))
                frame:set(leds + 1 - pos, np.elementary_colors:get(color))
                ws2812.write(frame)
                pos = pos + 1
            else
                frame:set(pos, 0, 0, 0)
                frame:set(leds + 1 - pos, 0, 0, 0)
                ws2812.write(frame)
                pos = pos + 1
            end
        end
    end
end

function diverging()
    if leds % 2 == 0 then
        if pos >= ( leds / 2 ) then
            if mode == np.fill_pixel then
                frame:set( (leds / 2) - (pos - 1) , np.elementary_colors:get(color))
                frame:set( (leds / 2) + (pos - 1) + 1, np.elementary_colors:get(color))
                ws2812.write(frame)
                mode = np.unfill_pixel
                pos = 1
            else
                frame:set( (leds / 2) - (pos - 1), 0, 0, 0)
                frame:set( (leds / 2) + (pos - 1) + 1, 0, 0, 0)
                ws2812.write(frame)
                mode = np.fill_pixel
                pos = 1
                color = color % (np.elementary_colors:size()) + 1
            end
        else
            if mode == np.fill_pixel then
                frame:set( (leds / 2) - (pos - 1), np.elementary_colors:get(color))
                frame:set( (leds / 2) + (pos - 1) + 1, np.elementary_colors:get(color))
                ws2812.write(frame)
                pos = pos + 1
            else
                frame:set( (leds / 2) - (pos - 1), 0, 0, 0)
                frame:set( (leds / 2) + (pos - 1) + 1, 0, 0, 0)
                ws2812.write(frame)
                pos = pos + 1
            end
        end
    else
        if pos >= ( (leds + 1) / 2 ) then
            if mode == np.fill_pixel then
                frame:set( ( (leds + 1) / 2) - ( pos - 1 ), np.elementary_colors:get(color))
                frame:set( ( (leds + 1) / 2) + ( pos - 1 ), np.elementary_colors:get(color))
                ws2812.write(frame)
                mode = np.unfill_pixel
                pos = 1
            else
                frame:set( ( (leds + 1) / 2) - ( pos - 1 ), 0, 0, 0)
                frame:set( ( (leds + 1) / 2) + ( pos - 1 ), 0, 0, 0)
                ws2812.write(frame)
                mode = np.fill_pixel
                pos = 1
                color = color % (np.elementary_colors:size()) + 1
            end
        else
            if mode == np.fill_pixel then
                frame:set( ( (leds + 1) / 2) - ( pos - 1 ), np.elementary_colors:get(color))
                frame:set( ( (leds + 1) / 2) + ( pos - 1 ), np.elementary_colors:get(color))
                ws2812.write(frame)
                pos = pos + 1
            else
                frame:set( ( (leds + 1) / 2) - ( pos - 1 ), 0, 0, 0)
                frame:set(( (leds + 1) / 2) + ( pos - 1 ), 0, 0, 0)
                ws2812.write(frame)
                pos = pos + 1
            end
        end
    end
end


function Apply_Effect()
    
    r1=tonumber("0x" .. string.sub(str_color, 1, 2))
    g1=tonumber("0x" .. string.sub(str_color, 3, 4))
    b1=tonumber("0x" .. string.sub(str_color, 5, 6))

    freq_ms = tonumber(str_freq)

    animator:stop()
    animator:unregister()
    all_leds_off()

    if str_mode == "p0" then
        animator:register(100, tmr.ALARM_SINGLE, all_leds_off)
        animator:start()
    elseif str_mode == "p1" then
        animator:register(100, tmr.ALARM_SINGLE, fill_all)
        animator:start()
    elseif str_mode == "p2" then
        np.monochromatic_gradient(frame, g1, r1, b1, 5)
        ws2812.write(frame)
        animator:register(freq_ms, tmr.ALARM_AUTO, color_scroll)
        animator:start()
    elseif str_mode == "p3" then
        pos = 1        
        frame:fill(rainbow:get(1))
        ws2812.write(frame)
        animator:register(freq_ms, tmr.ALARM_AUTO, color_wheel)
        animator:start()
    elseif str_mode == "p4" then
        frame = rainbow:sub(1, frame:size())
        ws2812.write(frame)
        animator:register(freq_ms, tmr.ALARM_AUTO, color_swirl)
        animator:start()
    elseif str_mode == "p5" then
        dir = np.forward_shift
        pos = 1   
        frame:set(1, g1, r1, b1)
        ws2812.write(frame)
        animator:register(freq_ms, tmr.ALARM_AUTO, cylon_eye)
        animator:start()
    elseif str_mode == "p6" then
        dir = np.forward_shift
        pos = 1
        frame:fill(g1, r1, b1)
        frame:set(1, 0, 0, 0)
        ws2812.write(frame)
        animator:register(freq_ms, tmr.ALARM_AUTO, cylon_eye_inverted)
        animator:start()
    elseif str_mode == "p7" then
        mode = np.fill_pixel
        pos = 1
        color = 1
        animator:register(freq_ms, tmr.ALARM_AUTO, converging)
        animator:start()
    elseif str_mode == "p8" then
        mode = np.fill_pixel
        pos = 1
        color = 1
        animator:register(freq_ms, tmr.ALARM_AUTO, diverging)
        animator:start()
    else
        animator:register(100, tmr.ALARM_SINGLE, all_leds_off)
        animator:start()
    end
end

server = net.createServer(net.TCP)-- create TCP server

function SendHTML(sck, h_mode, h_color, h_freq) -- Send LED brightness HTML page
    htmlstring = "<!DOCTYPE html>\r\n"
    htmlstring = htmlstring.."<html lang=\"en\">\r\n"
    htmlstring = htmlstring.."<head>\r\n"
    htmlstring = htmlstring.."<title>NodeMCU Web Server</title>\r\n"
    htmlstring = htmlstring.."<style>\r\n"
    htmlstring = htmlstring.."table {\r\n"
    htmlstring = htmlstring.."width: 500px;\r\n"
    htmlstring = htmlstring.."margin-left: auto;\r\n"
    htmlstring = htmlstring.."margin-right: auto;\r\n"
    htmlstring = htmlstring.."}\r\n"
    htmlstring = htmlstring.."table, th, td {\r\n"
    htmlstring = htmlstring.."border: 1px solid black;\r\n"
    htmlstring = htmlstring.."border-collapse: collapse;\r\n"
    htmlstring = htmlstring.."text-align: center;\r\n"
    htmlstring = htmlstring.."}\r\n"
    htmlstring = htmlstring.."tr {\r\n"
    htmlstring = htmlstring.."height: 50px;\r\n"
    htmlstring = htmlstring.."}\r\n"
    htmlstring = htmlstring.."</style>\r\n"
    htmlstring = htmlstring.."</head>\r\n"
    htmlstring = htmlstring.."<body style=\"font-family: 'Segoe UI'\">\r\n"
    htmlstring = htmlstring.."<div>\r\n"
    htmlstring = htmlstring.."<table>\r\n"
    htmlstring = htmlstring.."<tr>\r\n"
    htmlstring = htmlstring.."<th colspan=\"2\"> <h1>NodeMCU ws2812b Control </h1> </th>\r\n"
    htmlstring = htmlstring.."</tr>\r\n"
    htmlstring = htmlstring.."<tr>\r\n"
    htmlstring = htmlstring.."<td colspan=\"2\"> <h3> Use the controls below <br> for neopixel effects </h3> </td>\r\n"
    htmlstring = htmlstring.."</tr>\r\n"
    htmlstring = htmlstring.."<form method=\"get\">\r\n"
    htmlstring = htmlstring.."<tr>\r\n"
    htmlstring = htmlstring.."<td style=\"width: 300px\"> <label for=\"pattern\"> <b> <i> Pattern </i> </b> </label> </td>\r\n"
    htmlstring = htmlstring.."<td style=\"width: 200px\">\r\n"
    htmlstring = htmlstring.."<select id=\"pattern\" name=\"pattern\">\r\n"
    htmlstring = htmlstring.."<option value=\"p0\">Off</option>\r\n"
    htmlstring = htmlstring.."<option value=\"p1\">Fill</option>\r\n"
    htmlstring = htmlstring.."<option value=\"p2\">Monochromatic Scroll</option>\r\n"
    htmlstring = htmlstring.."<option value=\"p3\">Color Wheel</option>\r\n"
    htmlstring = htmlstring.."<option value=\"p4\">Rainbow</option>\r\n"
    htmlstring = htmlstring.."<option value=\"p5\">Bounce</option>\r\n"
    htmlstring = htmlstring.."<option value=\"p6\">Bounce Inverted</option>\r\n"
    htmlstring = htmlstring.."<option value=\"p7\">Converging</option>\r\n"
    htmlstring = htmlstring.."<option value=\"p8\">Diverging</option>\r\n"
    htmlstring = htmlstring.."</select>\r\n"
    htmlstring = htmlstring.."</td>\r\n"
    htmlstring = htmlstring.."</tr>\r\n"
    htmlstring = htmlstring.."<tr>\r\n"
    htmlstring = htmlstring.."<td style=\"width: 300px\"> <label for=\"colour\"> <b> <i> Color </i> </b> </label> </td>\r\n"
    htmlstring = htmlstring.."<td style=\"width: 200px\"> <input type=\"color\" id=\"colour\" name=\"colour\" value=\"#" .. h_color .. "\"> </td>\r\n"
    htmlstring = htmlstring.."</tr>\r\n"
    htmlstring = htmlstring.."<tr>\r\n"
    htmlstring = htmlstring.."<td style=\"width: 300px\"> <label for=\"frequency\"> <b> <i> Frequency (ms) </i> </b> </label> </td>\r\n"
    htmlstring = htmlstring.."<td style=\"width: 200px\"> <input type=\"range\" min=\"10\" max=\"200\" value=\"" .. h_freq .. "\" id=\"frequency\" name=\"frequency\"> </td>\r\n"
    htmlstring = htmlstring.."</tr>\r\n"
    htmlstring = htmlstring.."<tr>\r\n"
    htmlstring = htmlstring.."<td colspan=\"2\"> <input type=\"submit\" value=\"Apply Effects\"> </td>\r\n"
    htmlstring = htmlstring.."</tr>\r\n"
    htmlstring = htmlstring.."</form>\r\n"
    htmlstring = htmlstring.."</table>\r\n"
    htmlstring = htmlstring.."</div>\r\n"
    htmlstring = htmlstring.."</body>\r\n"
    htmlstring = htmlstring.."</html>\r\n"

    if h_mode == "p0" then
        htmlstring = string.gsub(htmlstring, "value=\"p0\"", "value=\"p0\" selected" )
    elseif h_mode == "p1" then
        htmlstring = string.gsub(htmlstring, "value=\"p1\"", "value=\"p1\" selected" )
    elseif h_mode == "p2" then
        htmlstring = string.gsub(htmlstring, "value=\"p2\"", "value=\"p2\" selected" )
    elseif h_mode == "p3" then
        htmlstring = string.gsub(htmlstring, "value=\"p3\"", "value=\"p3\" selected" )
    elseif h_mode == "p4" then
        htmlstring = string.gsub(htmlstring, "value=\"p4\"", "value=\"p4\" selected" )
    elseif h_mode == "p5" then
        htmlstring = string.gsub(htmlstring, "value=\"p5\"", "value=\"p5\" selected" )
    elseif h_mode == "p6" then
        htmlstring = string.gsub(htmlstring, "value=\"p6\"", "value=\"p6\" selected" )
    elseif h_mode == "p7" then
        htmlstring = string.gsub(htmlstring, "value=\"p7\"", "value=\"p7\" selected" )
    elseif h_mode == "p8" then
        htmlstring = string.gsub(htmlstring, "value=\"p8\"", "value=\"p8\" selected" )
    else
        htmlstring = string.gsub(htmlstring, "value=\"p0\"", "value=\"p0\" selected" )
    end

    sck:send(htmlstring)
end


function receiver(sck, data)-- process callback on recive data from client
    first_line_end = string.find(data, "\r")
    GET_request_raw = string.sub(data, 1, first_line_end)
    GET_request = string.gsub(GET_request_raw, "%%23", "")     -- % is magic character for lua string matching
    print(GET_request_raw)
    print(GET_request)

    if string.find(GET_request, "pattern") and string.find(GET_request, "colour") and string.find(GET_request, "frequency") then
        words = {}
        keyz= {}
        valuez = {}

        for w in (GET_request):gmatch("%w+=+%w+") do 
            table.insert(words, w) 
        end

        for i = 1, #words do
            local equalsign = string.find( words[i], "=" )
            --print(equalsign)
            local tempkey = string.sub(words[i], 1, equalsign - 1 )
            local tempvalue = string.sub(words[i], equalsign + 1, string.len(words[i]) )
            --print(tostring(tempkey).."=>"..tostring(tempvalue))
            keyz[#keyz + 1] = tempkey
            valuez[#valuez + 1] = tempvalue
        end

        for i = 1, #words do
            print(tostring(keyz[i]).." => "..tostring(valuez[i]) ) 
        end

        local k1 = tostring(keyz[1])
        local v1 = tostring(valuez[1])

        local k2 = tostring(keyz[2])
        local v2 = tonumber("0x"..valuez[2])            -- must

        local k3 = tostring(keyz[3])
        local v3 = tonumber(valuez[3])

        if k1 == "pattern" and v1 ~= nil then  -- not equal to operator.
            str_mode = v1
        end

        if k2 == "colour" and v2 ~= nil then
            str_color = valuez[2]
        end

        if k3 == "frequency" and v3 ~= nil then
            str_freq = valuez[3]
        end
        
        Apply_Effect()

        SendHTML(sck, str_mode, str_color, str_freq)

        words = {}
        keyz= {}
        valuez = {}
    elseif  string.find(data, "GET / ") then
        SendHTML(sck, str_mode, str_color, str_freq)
    else
        SendHTML(sck, str_mode, str_color, str_freq)
    end

    -- never use below line,
    -- until and unless you want to reset the http connection
    --sck:on("sent", function(conn) conn:close() end)
end



function client_connected (conn)
    conn:on("receive", receiver)
    print(conn:getpeer())
end



if server then
    server:listen(80, client_connected)-- listen to the port 80
end

neopixel.lua

Lua
Helper library to assist with different neopixel effects.
local ws2812b_module = {}

-- assuming WS2812B GRB LED
led_count = 10
channel_count = 3

ws2812b_module.forward_shift = 0
ws2812b_module.backward_shift = 1
ws2812b_module.fill_pixel = 0
ws2812b_module.unfill_pixel = 1

ws2812b_module.elementary_colors = pixbuf.newBuffer(7, channel_count)
ws2812b_module.elementary_colors:set(1, 200, 0, 0)
ws2812b_module.elementary_colors:set(2, 0, 200, 0)
ws2812b_module.elementary_colors:set(3, 0, 0, 200)
ws2812b_module.elementary_colors:set(4, 200, 200, 0)
ws2812b_module.elementary_colors:set(5, 200, 0, 200)
ws2812b_module.elementary_colors:set(6, 0, 200, 200)
ws2812b_module.elementary_colors:set(7, 200, 200, 200)

local PI = math.pi
local r360 = 2*PI -- 2*PI radians equal 360 degrees
local r180 = PI
local r90  = PI/2

local function d2r(x) -- degree to radian
    return ( (x*PI) / 180.00)
end

local function sin(x) -- x in radians
    local sign = 1;
    if x < 0 then x = -x + r180 end
    x = x % r360; -- x < 360
    if x > r180 then x = r360 - x;  sign = -1 end
    if x > r90  then x = r180 - x end
    local x2 = x*x;
    return sign*x*(x2*(x2*(-x2+42)-840)+5040)/5040;
end

function ws2812b_module.pixel_init(leds, channels)
    led_count = leds
    channel_count = channels

    ws2812.init()
    ws2812b_module.all_off()
end

function ws2812b_module.all_off()
    local pixels = pixbuf.newBuffer(led_count, channel_count)
    pixels:fill(0, 0, 0)
    ws2812.write(pixels)
end

function ws2812b_module.monochromatic_gradient(buffer, g, r, b, factor)
    local n = buffer:size()
    r = r % 256         --r = r % 255, this won't work
    g = g % 256         --g = g % 255, this won't work
    b = b % 256         --b = b % 256, this won't work
    local rtemp, gtemp, btemp = 0, 0, 0

    -- linear gradient, linear equation of line
    -- y = ( (y2 - y1) / (x2 - x1) ) * (x - x1) + y1
    for i = 1, n do
        rtemp = ( (r/factor - r) / (n - 1) ) * (i - 1) + r
        gtemp = ( (g/factor - g) / (n - 1) ) * (i - 1) + g
        btemp = ( (b/factor - b) / (n - 1) ) * (i - 1) + b
        buffer:set(i, gtemp, rtemp, btemp)
    end
end

function ws2812b_module.rotate(buffer, val, direction)
    if direction == ws2812b_module.forward_shift then
        buffer:shift(val, pixbuf.SHIFT_CIRCULAR)
    else
        buffer:shift(-val, pixbuf.SHIFT_CIRCULAR)
    end
end

-- 3 sine phases, each having phase difference of 120 degrees mutually.
function ws2812b_module.rainbow_buffer(buffer)
    local data_points = buffer:size()
    local rtemp, gtemp, btemp = 0, 0, 0
    buffer:fill(0, 0, 0)
    
    for i = 1, data_points do
        rtemp = 255 * ( ( sin( d2r( ( i - 1 ) * 360 / ( data_points ) + 000 ) ) + 1 ) / 2 )
        gtemp = 255 * ( ( sin( d2r( ( i - 1 ) * 360 / ( data_points ) + 120 ) ) + 1 ) / 2 )
        btemp = 255 * ( ( sin( d2r( ( i - 1 ) * 360 / ( data_points ) + 240 ) ) + 1 ) / 2 )
        buffer:set(i, gtemp, rtemp, btemp)
    end
end



return ws2812b_module

neopixel_ui.html

HTML
A simple front-end webpage for controlling neopixels
<!DOCTYPE html>
<html lang="en">
    <head>
        <title>NodeMCU Web Server</title>

        <style>
            table {
                width: 500px;
                margin-left: auto;
                margin-right: auto;
            }

            table, th, td {
                border: 1px solid black;
                border-collapse: collapse;
                text-align: center;
            }

            tr {
                height: 50px;
            }


        </style>

    </head>

    <body style="font-family: 'Segoe UI'">
        <div>
            <table>
                <tr>
                    <th colspan="2"> <h1>NodeMCU ws2812b Control </h1> </th>
                </tr>
                <tr>
                    <td colspan="2"> <h3> Use the controls below <br> for neopixel effects </h3> </td>
                </tr>
                <form method="get">
                    <tr>
                        <td style="width: 300px"> <label for="pattern"> <b> <i> Pattern </i> </b> </label> </td>
                        <td style="width: 200px">
                            <select id="pattern" name="pattern">
                                <option value="p0">Off</option>
                                <option value="p1">Fill</option>
                                <option value="p2">Monochromatic Scroll</option>
                                <option value="p3">Color Wheel</option>
                                <option value="p4">Rainbow</option>
                                <option value="p5">Bounce</option>
                                <option value="p6">Bounce Inverted</option>
                                <option value="p7">Converging</option>
                                <option value="p8">Diverging</option>
                            </select>
                        </td>
                    </tr>
                    <tr>
                        <td style="width: 300px"> <label for="colour"> <b> <i> Color </i> </b> </label> </td>
                        <td style="width: 200px"> <input type="color" id="colour" name="colour" value="#ffff00"> </td>
                    </tr>
                    <tr>
                        <td style="width: 300px"> <label for="frequency"> <b> <i> Frequency (ms) </i> </b> </label> </td>
                        <td style="width: 200px"> <input type="range" min="10" max="200" value="50" id="frequency" name="frequency"> </td>
                    </tr>
                    <tr>
                        <td colspan="2"> <input type="submit" value="Apply Effects"> </td>
                    </tr>
                </form>
            </table>
        </div>
    </body>
</html>

node_config.lua

Lua
A minimal config file to cater different requirements easily.
-- Environment
-- can be "dev", "test" or "prod"
operating_env = "prod"

-- Application specific configuration,
-- which may require frequent changes,
-- hence not convenient to use in one of the LFS compiled files.
-- Number of LEDs (aka pixels) and number of channels.
leds = 5
channels = 3        -- RGB only, no W

init.lua

Lua
Sample init.lua to initalize your application in a non-blocking manner.
-- in case anything goes wrong.
--file.rename("init.lua","init_old.lua")
-- And when everything is all right
--file.rename("init_old.lua","init.lua")
--------------------------------------------------

sys_var_file = "node_config.lua"

app_file_1 = "neopixel_control_STA_GET.lua"

test_file_1 = "neopixel_test.lua"

print("30 seconds to execute init.lua")

function on_boot(t)
    print("executing init.lua")

    print("initiating LFS")
    node.LFS._init()
    dofile("hello_world.lua")
    print("LFS initialized")

    
    if file.exists(sys_var_file) then
        dofile(sys_var_file)
    else
        operating_env = "dev"
    end

    if (operating_env == "prod") then
        print("production node !")
        print("executing application file(s)")            
        dofile(app_file_1)
        --dofile(app_file_2)
        --dofile(app_file_3)
    elseif (operating_env == "test") then
        print("testing node !")
        print("executing testing file(s)")
        dofile(test_file_1)
        --dofile(test_file_2)
    else
        print("development node !")
        print("no further commands to execute automatically")
    end
    print("done !")
    t:unregister()
end

-- single trigger at boot
-- give a 30 seconds delay to delete or rename init.lua if
-- any exception has occured.
boot_timer = tmr.create()
boot_timer:alarm(30 * 1000, tmr.ALARM_SINGLE, on_boot)

process.txt

AsciiDoc
Follow this process to upload this code to your NodeMCU.
I have ESP8266 (ESP-12 module) with 4 MB flash.
Below steps work fine for this module for any board.
If using differnt board, then modify settings if any.

1. build firmware online with these modules or use the one I have uploaded :
> https://nodemcu-build.com/
> provide your email address, firmware will be sent over this email
> Select "release" branch
> select these modules : bit,color_utils,file,gpio,net,node,pixbuf,sjson,tmr,uart,wifi,ws2812,ws2812_effects
> for LFS options, select : LFS - 64 KB, SPIFFS address - 64 KB ,SPIFFS size - rest of flash 
> if you understand LFS better, then fill free to modify LFS setting as per your wish.

2. Step 1 will get you two binaries : Float and Integer
> Download PyFlasher : https://github.com/marcelstoer/nodemcu-pyflasher
> upload firmware (float one) using nodemcu-PyFlasher
> Select Serial port of ESP8266, browse to float firmware file, DIO mode (or click on "i" to understand mode based on your esp8266 module), erase flash would be fine
> click on "Flash NodeMCU"

3. Open Esplorer, configure wifi connection with your Hotspot or Access Point router.
> So, that the connections details are saved and next time we just need to call these two lines after reset to connect
> wifi.setmode(wifi.STATION)  
> wifi.sta.connect()

4. Download the latest luac from : https://github.com/nodemcu/nodemcu-firmware/releases
> Or use the one I have provided. 
> Complile files in folder "neopixel_src" using this command
> luac.cross_3.0.0-release_20210201_x64_float_Lua51.exe -f -o .\neopixel_LFS.img .\neopixel_src\*.lua

5. upload the generated LFS img file using nodemcu-uploader command (change port as per shown in device manager)
> nodemcu-uploader --port COM18 upload neopixel_LFS.img

6. Open Esplorer, connect to ESP8266.
> Load the LFS using this command, This will restart the nodemcu
> node.LFS.reload("neopixel_LFS.img")
> After restart, following command can be used to check files in LFS
> for k,v in pairs(node.LFS.list())  do print(k,v) end

7. create node_config.lua using Esplorer
> as per the one located in folder "files_on_flash"

8. create init.lua using Esplorer
as per the one located in folder "files_on_flash"

9. Restart the nodemcu using reset button.

10. In case anything goes wrong, run this command in esplorer before init.lua starts executing main file
> file.rename("init.lua","init_old.lua")
> Restart the nodemcu using reset button.
> Once done with troubleshooting, run this command to restore init.lua
> file.rename("init_old.lua","init.lua")
> Restart the nodemcu using reset button.

11. Once all good, open IP address of your nodemcu in web browser and enjoy !

read_html_to_write_lua.py

Python
A nice script to help you convert html script to equivalent single lua string.
file1 = open('neopixel_ui.html', 'r')
file2 = open('neopixel_ui.txt', 'w')
count = 0

while True:
    count += 1

    line = file1.readline()
    #print(type(line))

    if not line:
        break

    # remove all the leaading and trailing spaces
    line0 = line.strip(" ")

    # Slice the string from first character to second last character.
    # We don't need last character as it is a new-line character '\n'
    line1 = line0[0:(len(line0) - 1):1]

    # add backslash before each double quote - ( " ) character inside the string
    # because double quote is itself used as the string enclosing character in lua.
    line2 = line1.replace('\"', '\\\"')

    # add the standard leading string used for concatenation in lua code to send HTML as a string.
    # also add CR-LF characters inside the string and LF then at the end of the actual string
    line3 = 'htmlstring = htmlstring..' + '\"' + line2 + '\\r\\n' + '\"' + '\n' 

    # Just print the line3 on console for info
    print( str(len(line1)) + " - " + line3)

    # write the line to file only if it is not an empty string.
    # required because since " we believe in clean code :) ",
    # there would be many blank lines in our html code.
    # all of these empty lines must have become empty strings,
    # because we stripped all the spaces in line0.
    # And we don't need such empty strings added to standard lua string in our code.
    # As they don't add any value and just increase the size of lua scripts.
    if (len(line1) != 0) :
        file2.write(line3)
    
file1.close()
file2.close()

NodeMCU_neopixel_webserver.zip

Lua
Zip file with all the required files to try this project on your NodeMCU !
No preview (download only).

Credits

Rudra Lad

Rudra Lad

7 projects • 14 followers

Comments