638|2

10

帖子

6

TA的资源

一粒金砂(中级)

楼主
 

【得捷电子Follow me第2期】任务5:通过网络控制WS2812B [复制链接]

 

【得捷电子Follow me第2期】任务5:通过网络控制WS2812B

在手机上通过网络控制板载Neopixel LED的显示和颜色切换,屏幕同步显示状态。

接线:

编程语言和环境用CircuitPython:

开发板:

Adafruit Feather ESP32-S3 TFT

运行环境:

Adafruit CircuitPython 8.2.3 on 2023-08-11

编辑器:

mu-editor

用到的额外模块:

adafruit_httpserver

adafruit_display_text

neopixel.mpy

还需要在code.py同目录下创建一个secrets.py,存放要连接的wifi名称跟密码。

还需要在code.py同目录下创建一个mywebsite文件夹,存放index.html网页。

代码解析:

以下是secrets.py文件完整代码:(相应位置要改成自己的wifi名称跟密码,其他几个暂时没用上)

secrets={
    "ssid":"要连接的wifi名称",
    "password":"要连接的wifi密码",
    'aio_username' : 'adafruit io账号',# 暂时用不上
    'aio_key' : 'adafruit io API key',# 暂时用不上
    'weather_key': 'StmLNJLbxIN0acv7f', #心知天气api key
    'timezone' : "America/New_York", # http://worldtimeapi.org/timezones
}

以下是code.py文件完整代码:

#adafruit_httpserver/
#adafruit_display_text/
#neopixel.mpy
#adafruit_io/
#adafruit_minimqtt/

import board
import displayio
import time
import ipaddress
import wifi
import ssl
import socketpool
import neopixel
import digitalio
import json
import terminalio
import bitmaptools
#import adafruit_minimqtt.adafruit_minimqtt as MQTT
#from adafruit_io.adafruit_io import IO_MQTT

from adafruit_httpserver import Server, Request, Response, FileResponse, JSONResponse, Redirect, Route, Websocket, Headers
import adafruit_httpserver.methods as METHODS
from adafruit_display_text import label

PORT = 80
WIDTH = board.DISPLAY.width;
HEIGHT = board.DISPLAY.height;

try:
    from secrets import secrets
except ImportError:
    print("need secrets.py, please add them!")
    raise


print("wifi conneting...", secrets["ssid"])
wifi.radio.connect(secrets["ssid"],secrets["password"])
pool=socketpool.SocketPool(wifi.radio)

#=======================================
#mqtt太拉啦,耗时太长,注释掉
#sslContext=ssl.create_default_context()
#sslContext.check_hostname=False

#aio_username=secrets["aio_username"]
#aio_key=secrets['aio_key']

#mqtt_client=MQTT.MQTT(
#    broker="io.adafruit.com",
#    username=secrets["aio_username"],
#    password=secrets["aio_key"],
#    socket_pool=pool,
#    ssl_context=sslContext,
#)
#def connected(client):
#    print("MQTT connected!")
#    client.subscribe("neopixel")

#def message(client, id, rgb):
#    print("MQTT recv: {0} {1}".format(id, rgb))
#    if id=="neopixel":
#        global need2update,ledcolor
#        need2update=True
#        ledcolor = int(rgb[1:],16)

#io=IO_MQTT(mqtt_client)
#io.on_connect=connected
#io.on_message=message
#=======================================

HOST = str(wifi.radio.ipv4_address)


server=Server(pool, 'mywebsite', debug=True)
server.start(HOST, PORT)
print('Server %s: %d is listening... '%(server.host, server.port))

@server.route('/', METHODS.GET)
def rootRedirect(req):
    return Redirect(req, '/index.html', permanent=True)

ws=None

def setLedColor(rgb):
    pl[0]=rgb;
    led.fill(rgb);

@server.route('/rgb', METHODS.GET)
def upgradeWS(req, *args):
    h=Headers()
    if 'Sec-WebSocket-Protocol' in req.headers:
        h.update({'Sec-WebSocket-Protocol':'json'})

    global ws
    if ws is not None:
        ws.close()

    ws=Websocket(req, h)

    return ws


led=neopixel.NeoPixel(board.NEOPIXEL, 1)
ledcolor = 0xff0000
btn = digitalio.DigitalInOut(board.BOOT0)
btn.switch_to_input(digitalio.Pull.UP)

bmp=displayio.Bitmap(30,30,1)
pl=displayio.Palette(2)
pl[0]=ledcolor
bmp.fill(1)
bitmaptools.draw_circle(bmp, bmp.width//2, bmp.height//2, bmp.width//2-1, 0)
bitmaptools.boundary_fill(bmp, bmp.width//2, bmp.height//2, 0, 1)
colorMark=displayio.TileGrid(bmp, pixel_shader=pl)
colorMark.x=(WIDTH-bmp.width)//2
colorMark.y=(HEIGHT - bmp.height)//2
lb0=label.Label(terminalio.FONT, text='', color=0xffffff)
lb0.y=5
lb0.x=5
gp=displayio.Group()
gp.append(colorMark)
gp.append(lb0)

#不知为何直接在CIRCUITPYTHON_TERMINAL搞会老是重启。
#displayio.CIRCUITPYTHON_TERMINAL.append(colorMark)
board.DISPLAY.root_group = gp
def switchDisplay():
    board.DISPLAY.root_group = gp if board.DISPLAY.root_group==displayio.CIRCUITPYTHON_TERMINAL else displayio.CIRCUITPYTHON_TERMINAL


st=time.monotonic()
fps=0
iorate=0
need2update=True
btnv0 = btn.value
btnv1 = btnv0
while True:
    server.poll()

    if ws is not None:
        try:
           if (data:=ws.receive()) is not None:
                try:
                    data = json.loads(data)
                    if(g:=data.get('get')) is not None:
                        ws.send_message(json.dumps({'rgb':ledcolor}))

                    elif(rgb:=data.get('rgb')) is not None:
                        #print(rgb)
                        if type(rgb)==int:
                            need2update = True
                            ledcolor=rgb
                except:
                    pass
        except:
            ws.close()
            ws=None

    btnv1=btn.value

    et=time.monotonic()
    dt = et-st
    st=et
    fps-=dt
    iorate-=dt
    #30帧更新频率, 60帧有点快,偶尔卡一下
    if(fps<0):
        fps=1/30
        if need2update:
            need2update=False
            setLedColor(ledcolor)
        if btnv0==True and btnv1==False:
            switchDisplay()
        btnv0=btnv1
    #=======================================
    #mqtt太拉啦,耗时太长,注释掉
    #if(iorate<0):
    #    iorate=1
    #    try:
    #        if not io.is_connected:
    #            print("MQTT connecting...")
    #            io.connect()
    #        t0=time.monotonic()
    #        io.loop(0.1)
    #        print((time.monotonic()-t0)*1000)
    #    except Exception as e:
    #        print("MQTT error: ",e)
    #=======================================
    lb0.text = '%.2f ms'%(dt*1000)
    #time.sleep(0.001)

前面几个贴子讲过的就不重复讲了。

源码里面有一大片注释掉的地方,原本是打算把MQTT的方式也用上的,minimqtt模块试了下,

效果并不好,io.loop()这一句至少耗时1秒,太慢了,不符合我的要求,所以不用了。

(mqtt还要去https://io.adafruit.com/建个dashboard,本质上其实也是类似建了个个人网页。)

#用httpserver模块建个web服务器,mywebsite是服务器根目录,随后的文件访问都会被自动改到在该目录下进行。

server=Server(pool, 'mywebsite', debug=True)
server.start(HOST, PORT)

 #添加”/“的访问响应,重定向到直接读取index.html。客户端浏览器会自动访问index.html。

(感觉python的发展越来越怪了,@字符一开始看得有点谜)

@server.route('/', METHODS.GET)
def rootRedirect(req):
    return Redirect(req, '/index.html', permanent=True)

#在添加个‘/rgb’的响应,把普通http请求的socket升级成websocket,

请求头如果带了Sec-WebSocket-Protocol,需要回复一下,不然升级不了。当然也可以在客户端发请求时不带这个请求头,就不用回复了

只运行一个websocket连接,所以后面加了些判断是否已经存在连接,如果是,就关掉原来的连接。

@server.route('/rgb', METHODS.GET)
def upgradeWS(req, *args):
    h=Headers()
    if 'Sec-WebSocket-Protocol' in req.headers:
        h.update({'Sec-WebSocket-Protocol':'json'})

    global ws
    if ws is not None:
        ws.close()

    ws=Websocket(req, h)

    return ws

中间一堆到while循环那里,就是一些led设置啊,按键设置啊,屏幕显示啊,就不说了。

#服务器轮询,websocket也是轮询,websocket收到数据就解析成json,先判断是否有‘get’请求,有就回复当前led颜色,没有就提取rgb,存入ledcolor全局变量,

没直接更新led,因为直接更新led会耗时间,websocket请求太快的话,circuitpython好像收到一帧信息不立即返回,而是卡住收一段时间,效果不是很好,所以

我设了个30帧的更新频率,这样客户端快速操作时,esp32也可以快速响应。

while True:
    server.poll()

    if ws is not None:
        try:
           if (data:=ws.receive()) is not None:
                try:
                    data = json.loads(data)
                    if(g:=data.get('get')) is not None:
                        ws.send_message(json.dumps({'rgb':ledcolor}))

                    elif(rgb:=data.get('rgb')) is not None:
                        #print(rgb)
                        if type(rgb)==int:
                            need2update = True
                            ledcolor=rgb
                except:
                    pass
        except:
            ws.close()
            ws=None
	......
	et=time.monotonic()
    dt = et-st
    st=et
    fps-=dt
    #30帧更新频率, 60帧有点快,偶尔卡一下
    if(fps<0):
        fps=1/30
        if need2update:
            need2update=False
            setLedColor(ledcolor)
        if btnv0==True and btnv1==False:
            switchDisplay()
        btnv0=btnv1
    ......

以下是index.js文件完整代码:

"use strict";


window.addEventListener('DOMContentLoaded', () => {
	const origin = top.location.origin;				// http://127.0.0.1
	const host = top.location.host;					// 127.0.0.1
	const hostp = top.location.origin.substr(4);	// ://127.0.0.1或者s://127.0.0.1

	update();
});
/**@type{HTMLCanvasElement} */
const cv = document.getElementById('cv');
/**@type{CanvasRenderingContext2D} */
const ctx = cv.getContext('2d');
let fps = 0;
let selectedHSL = [0, 100, 50];
let selectedHSLLast = [0, 100, 50];
let selectedHSLInd = 0;
let rgbcolor = 0xff0000;
let paletteOX = cv.width * 0.5;
let paletteOY = cv.height * 0.5;
let paletteRadius = 80;
let paletteWidth = 20;
let paletteGap = 10;
let need2Send = false;
const paletteAngPerSat = 3;
const paletteAngPerLight = 3;

const ws = new WebSocket(`ws://${location.host}/rgb`, 'json');
ws.onmessage = (/**@type{MessageEvent} */ev) => {
	const json = JSON.parse(ev.data);
	console.log(json)
	let r = json.rgb >> 16;
	let g = (json.rgb >> 8) & 0x0000ff;
	let b = json.rgb & 0x0000ff;

	let [h, s, l] = rgb2hsl(r, g, b);
	console.log(h, s, l);
	selectedHSL[0] = h;
	selectedHSL[1] = s;
	selectedHSL[2] = l;
	rgbcolor = (r << 16) + (g << 8) + (b);
}
ws.onerror = (/**@type{Event} */ev) => {
	console.error('ws error: ', ev);
}
ws.onopen = (/**@type{Event} */ev) => {
	console.log('ws opened!', ev);
	ws.send(JSON.stringify({ get: true }));
}
ws.onclose = (/**@type{CloseEvent} */ev) => {
	console.log('ws closed!', ev);
	alert("websocket已经断开,请刷新网页重新连接!");
}
/**
 * @param {Number} rgb 
 */
function sendRGB(rgb) {
	ws.send(JSON.stringify({ rgb: rgb }));
}

function resizeCV() {
	cv.width = cv.clientWidth;
	cv.height = cv.clientHeight;
	paletteOX = cv.width * 0.5;
	paletteOY = cv.height * 0.5;
}
window.onresize = resizeCV;
cv.addEventListener("mousedown", handleDown);
cv.addEventListener("touchstart", handleDown);
cv.addEventListener("mouseup", handleUp);
cv.addEventListener("touchend", handleUp);
cv.addEventListener("click", handleClick);
cv.addEventListener("mousemove", handleDrag);
cv.addEventListener("touchmove", handleDrag);
resizeCV();

function updateSelectHSLInd(x, y) {
	const r = Math.sqrt(x * x + y * y);

	if (r < paletteRadius + paletteWidth) {
		selectedHSLInd = 0;
	} else if (r < paletteRadius + paletteWidth * 2 + paletteGap * 2) {
		selectedHSLInd = 1;
	} else if (r < paletteRadius + paletteWidth * 3 + paletteGap * 3) {
		selectedHSLInd = 2
	}
}

function handleDown(/**@type{TouchEvent | MouseEvent} */ev) {
	let x = 0;
	let y = 0;
	if (ev instanceof MouseEvent) {
		x = ev.clientX;
		y = ev.clientY;
	} else {
		x = ev.touches[0].clientX;
		y = ev.touches[0].clientY;
	}

	x = x - paletteOX;
	y = - y + paletteOY;
	updateSelectHSLInd(x, y);

}
function handleClick(/**@type{TouchEvent | MouseEvent} */ev) {
	// console.log(ev.button);
}
function handleUp(/**@type{TouchEvent | MouseEvent} */ev) {
	selectedHSLLast[0] = selectedHSL[0];
	selectedHSLLast[1] = selectedHSL[1];
	selectedHSLLast[2] = selectedHSL[2];
}
function handleDrag(/**@type{TouchEvent | MouseEvent} */ev) {
	ev.preventDefault();
	let x = 0;
	let y = 0;
	if (ev instanceof MouseEvent) {
		if (!ev.buttons) return;
		x = ev.clientX;
		y = ev.clientY;
	} else {
		x = ev.touches[0].clientX;
		y = ev.touches[0].clientY;
	}
	// console.log(ev);
	x = x - paletteOX;
	y = - y + paletteOY;
	updateSelectHSLInd(x, y);
	//-180 ~ 180
	let deg = 0;
	let a, cosa, sina, x1, y1, maxdeg;
	switch (selectedHSLInd) {
		case 0:
			deg = Math.floor(Math.atan2(y, x) * 180 / Math.PI)
			deg = deg < 0 ? 360 + deg : deg;
			break;
		case 1:
			a = deg2rad(selectedHSL[0]);
			cosa = Math.cos(a);
			sina = Math.sin(a);
			x1 = cosa * x + sina * y;
			y1 = cosa * y - sina * x;
			deg = -Math.floor(Math.atan2(y1, x1) * 180 / Math.PI)
			deg = deg < 0 ? 360 + deg : deg;
			maxdeg = paletteAngPerSat * 100;
			if (deg >= 0 && deg <= maxdeg) {

			}
			else if (deg < 360 - (360 - maxdeg) * 0.5) {
				deg = maxdeg;
			}
			else {
				deg = 0;
			}
			deg = 100 - Math.floor(deg / paletteAngPerSat);
			break;
		default:
			a = deg2rad(selectedHSL[0] - (100 - selectedHSL[1]) * paletteAngPerSat - 50 * paletteAngPerLight);
			cosa = Math.cos(a);
			sina = Math.sin(a);
			x1 = cosa * x + sina * y;
			y1 = cosa * y - sina * x;
			deg = Math.floor(Math.atan2(y1, x1) * 180 / Math.PI)
			deg = deg < 0 ? 360 + deg : deg;
			maxdeg = paletteAngPerLight * 100;
			if (deg >= 0 && deg <= maxdeg) {

			}
			else if (deg < 360 - (360 - maxdeg) * 0.5) {
				deg = maxdeg;
			}
			else {
				deg = 0;
			}
			deg = Math.floor(deg / paletteAngPerLight);
			break;
	}
	selectedHSL[selectedHSLInd] = deg;

	let [r, g, b] = hsl2rgbInt(selectedHSL[0], selectedHSL[1], selectedHSL[2]);
	rgbcolor = (r << 16) + (g << 8) + (b);
	need2Send = true;
}
function clear() {
	ctx.clearRect(0, 0, cv.width, cv.height)
}


function draw() {
	clear();
	drawPalette(ctx, paletteOX, paletteOY, paletteRadius, paletteWidth, paletteGap, ...selectedHSL, ...selectedHSLLast, selectedHSLInd);
	ctx.font = '24px  sans-serif';
	ctx.fillStyle = 'black';
	let s = `HSL: ${selectedHSL[0]} ${selectedHSL[1]}% ${selectedHSL[2]}%`;
	let m = ctx.measureText(s);
	ctx.fillText(s, (cv.width - m.width) * 0.5, cv.height * 0.5 + paletteRadius + paletteWidth * 3 + paletteGap * 3 + 24);
	let r = rgbcolor >> 16;
	let g = (rgbcolor >> 8) & 0x0000ff;
	let b = (rgbcolor) & 0x0000ff;
	function to16(num) {
		num = num.toString(16).toUpperCase();
		return num.length < 2 ? '0' + num : num;
	}
	let srgb = `0x${to16(r)}${to16(g)}${to16(b)}`;
	s = `RGB: ${r} ${g} ${b} ${srgb}`;
	m = ctx.measureText(s);
	ctx.fillText(s, (cv.width - m.width) * 0.5, cv.height * 0.5 + paletteRadius + paletteWidth * 3 + paletteGap * 3 + 48);
	drawFPS(ctx);
}
function drawFPS(/**@type{CanvasRenderingContext2D} */ctx) {
	ctx.font = "32px sans-serif";
	ctx.fillText(`FPS: ${fps.toFixed(2)}`, 5, 40);
}

/**
 * 圆上的点
 * @param {Number} ox 
 * @param {Number} oy 
 * @param {Number} r 
 * @param {Number} rad 
 * @returns {[Number, Number]} [x, y]
 */
function getPointOnCircle(ox, oy, r, rad) {
	return [ox + (r) * Math.cos(rad), oy - (r) * Math.sin(rad)];
}
function drawPalette(/**@type{CanvasRenderingContext2D} */ctx, x, y, r, w, paletteGap, hue, sat, light, hue0, sat0, light0, markInd) {
	let rad = deg2rad(0);
	let rr = r;
	/**hue */
	for (let i = 0; i < 360; i++) {
		ctx.beginPath();
		ctx.moveTo(...getPointOnCircle(x, y, rr + w, rad));
		ctx.lineTo(...getPointOnCircle(x, y, rr, rad));
		rad = deg2rad(i + 1);
		ctx.lineTo(...getPointOnCircle(x, y, rr, rad));
		ctx.lineTo(...getPointOnCircle(x, y, rr + w, rad));
		ctx.closePath();
		ctx.fillStyle = `hsl(${i}, 100%, 50%)`;
		ctx.fill();
	}

	/**sat */
	let angOffset = hue;
	rr = rr + w + paletteGap;
	rad = deg2rad(angOffset);
	ctx.strokeStyle = 'white';
	ctx.lineWidth = 2;
	for (let i = 0; i < 101; i++) {
		ctx.beginPath();
		ctx.moveTo(...getPointOnCircle(x, y, rr + w, rad));
		ctx.lineTo(...getPointOnCircle(x, y, rr, rad));
		rad = deg2rad(angOffset - i * paletteAngPerSat);
		ctx.lineTo(...getPointOnCircle(x, y, rr, rad));
		ctx.lineTo(...getPointOnCircle(x, y, rr + w, rad));
		ctx.closePath();
		ctx.stroke();
		ctx.fillStyle = `hsl(${hue}, ${100 - i}%, 50%)`;
		ctx.fill();
	}
	/**light */
	rr = rr + w + paletteGap;
	angOffset = hue - (100 - sat) * paletteAngPerSat;
	rad = deg2rad(angOffset + 50 * paletteAngPerLight);
	ctx.lineWidth = 3;
	for (let i = -50; i < 51; i++) {
		ctx.beginPath();
		ctx.moveTo(...getPointOnCircle(x, y, rr + w, rad));
		ctx.lineTo(...getPointOnCircle(x, y, rr, rad));
		rad = deg2rad(angOffset - i * paletteAngPerLight);
		ctx.lineTo(...getPointOnCircle(x, y, rr, rad));
		ctx.lineTo(...getPointOnCircle(x, y, rr + w, rad));
		ctx.closePath();
		ctx.stroke();
		ctx.fillStyle = `hsl(${hue}, ${sat}%, ${50 - i}%)`;
		ctx.fill();
	}

	/**center */
	ctx.beginPath();
	ctx.moveTo(x - 1, y);
	ctx.arc(x - 1, y, r - w, Math.PI * 0.5, Math.PI * 1.5, false);
	ctx.fillStyle = `hsl(${hue0}, ${sat0}%, ${light0}%)`
	ctx.closePath();
	ctx.fill();
	ctx.beginPath();
	ctx.moveTo(x + 1, y);
	ctx.arc(x + 1, y, r - w, Math.PI * 0.5, Math.PI * 1.5, true);
	ctx.fillStyle = `hsl(${hue}, ${sat}%, ${light}%)`
	ctx.closePath();
	ctx.fill();

	/**mark */
	let color = '';
	let s = '';
	let marka = 1;
	let markw = 6;
	switch (markInd) {
		case 0:
			angOffset = hue;
			color = `hsl(${hue}, 100%, 50%)`;
			s = `${hue}`;
			break;
		case 1:
			angOffset = hue - (100 - sat) * paletteAngPerSat;
			color = `hsl(${hue}, ${sat}%, 50%)`;
			s = `${sat}`;
			marka = -paletteAngPerSat;
			markw = -6;
			break;
		default:
			angOffset = hue - (100 - sat) * paletteAngPerSat - (50 - light) * paletteAngPerLight;
			color = `hsl(${hue}, ${sat}%, ${light}%)`;
			s = `${light}`;
			marka = paletteAngPerLight;
			markw = 6;
			break;
	}
	rad = deg2rad(angOffset);
	rr = r + (markInd) * (w + paletteGap)
	const gap = 2;

	ctx.beginPath();
	ctx.moveTo(...getPointOnCircle(x, y, rr, rad));
	ctx.lineTo(...getPointOnCircle(x, y, rr + w + gap, rad));
	rad = deg2rad(angOffset - markw);
	ctx.lineTo(...getPointOnCircle(x, y, rr + w + gap, rad));
	ctx.lineTo(...getPointOnCircle(x, y, rr + w + w, rad));
	rad = deg2rad(angOffset + markw + marka);
	ctx.lineTo(...getPointOnCircle(x, y, rr + w + w, rad));
	ctx.lineTo(...getPointOnCircle(x, y, rr + w + gap, rad));
	rad = deg2rad(angOffset + marka);
	ctx.lineTo(...getPointOnCircle(x, y, rr + w + gap, rad));
	ctx.lineTo(...getPointOnCircle(x, y, rr, rad));
	ctx.closePath();
	ctx.fillStyle = color;
	ctx.fill();
	let l = markInd == 2 ? (light < 30 ? 100 : 0) : markInd == 1 ? (0) : 50;
	let c = `hsl(${180 + hue}, ${(sat)}%, ${(l)}%)`;
	ctx.strokeStyle = c;
	ctx.lineWidth = 1;
	ctx.stroke();
	ctx.save();
	ctx.fillStyle = c;
	ctx.font = "14px sans-serif";
	const m = ctx.measureText(s);
	ctx.translate(x, y)
	ctx.rotate(0.5 * Math.PI - deg2rad(angOffset + marka * 0.5));
	ctx.translate(0, -rr - w - gap - 2)
	ctx.fillText(s, - m.width * 0.5, 0);
	ctx.restore();
}



let st = new Date().getTime();

function update() {
	let et = new Date().getTime();
	fps = 1000 / (et - st);
	// console.log('FPS: ', fps);
	st = et;
	draw();
	if (need2Send) {
		sendRGB(rgbcolor);
		need2Send = false;
	}
	requestAnimationFrame(update);
}

/**
 * 
 * @param {Number} h 0 ~ 360
 * @param {Number} s 0.0 ~ 1.0
 * @param {Number} l 0.0 ~ 1.0
 * @returns {[Number, Number, Number]} RGB: 0.0 ~ 1.0
 */
function hsl2rgb(h, s, l) {
	let r = 0, g = 0, b = 0;
	if (s === 0) {
		r = g = b = l;
	} else {

		const q = l < 0.5 ? l * (1 + s) : l + s - (l * s);
		const p = 2 * l - q;
		const k = h / 360;

		const f = function (p, q, t) {
			if (t < 0) {
				t += 1;
			} else if (t > 1) {
				t -= 1;
			}
			let c = 0;
			if (t < 1 / 6) {
				c = p + ((q - p) * 6 * t);
			} else if (t < 1 / 2) {
				c = q;
			} else if (t < 2 / 3) {
				c = p + ((q - p) * 6 * (2 / 3 - t));
			} else {
				c = p;
			}
			return c;
		}

		r = f(p, q, k + 1 / 3);
		g = f(p, q, k);
		b = f(p, q, k - 1 / 3);
	}
	return [r, g, b];
}
/**
 * @param {Number} h 0~360
 * @param {Number} s 0~100
 * @param {Nubmer} l 0~100
 * @returns {[Number, Number, Number]} [255, 255, 255]
 */
function hsl2rgbInt(h, s, l) {
	let [r, g, b] = hsl2rgb(h, s / 100, l / 100);
	r *= 255;
	g *= 255;
	b *= 255;
	r = Math.round(r);
	g = Math.round(g);
	b = Math.round(b);
	return [r, g, b];
}
/**
 * @param {Number} r 0~255
 * @param {Number} g 0~255
 * @param {Number} b 0~255
 * @returns {[Number, Number, Number]} [0~360, 0~100, 0~100]
 */
function rgb2hsl(r, g, b) {
	const max = Math.max(r, g, b);
	const min = Math.min(r, g, b);
	let h = 0;
	if (max === min) {
		h = 0;
	} else if (max === r) {
		h = 60 * (g - b) / (max - min);
		if (g < b)
			h += 360;
	} else if (max === g) {
		h = 60 * (b - r) / (max - min) + 120
	} else if (max === b) {
		h = 60 * (r - g) / (max - min) + 240
	}
	let l = 0.5 * (max + min) / 255 * 100;
	let s = 0;
	if (l === 0 || max === min) {
		s = 0;
	} else if (l <= 50) {
		s = (max - min) / (max + min) * 100;
	} else {
		s = (max - min) / (510 - (max + min)) * 100;
	}
	return [Math.round(h), Math.round(s), Math.round(l)]
}
function deg2rad(deg) {
	return Math.PI * deg / 180;
}

js源码我就不说了,其实花费时间最多的就是这个js文件了,一个狂拽酷炫吊炸天的调色盘,花费了我无数个夜晚,

不过转起来不是那么顺风顺水,一不留神就转到其他圆环了。

一开始是点一下指定圆环,然后只控制那个环,但是转起来不够狂,于是改成指哪转哪,但是还是不好,算了不改了,

你们要是喜欢就拿去吧。

吐槽一下这个任务居然要手机控制,只有一个手机的我费了九牛二虎之力才找到了解决办法:

需要用到的网站https://vdo.ninja/ 可以自己开直播哈哈哈。

效果展示:

 

最新回复

屏幕同步RGB灯颜色,这个想法挺不错的,还能看出屏幕或RGB灯的色差。   详情 回复 发表于 2023-9-15 17:24
点赞 关注
 
 

回复
举报

1702

帖子

0

TA的资源

五彩晶圆(初级)

沙发
 

酷炫吊炸天的调色盘效果蛮好看的么

js源码在那里呢

 
 
 

回复

7186

帖子

2

TA的资源

版主

板凳
 

屏幕同步RGB灯颜色,这个想法挺不错的,还能看出屏幕或RGB灯的色差。

 
 
 

回复
您需要登录后才可以回帖 登录 | 注册

随便看看
查找数据手册?

EEWorld Datasheet 技术支持

相关文章 更多>>
关闭
站长推荐上一条 1/9 下一条

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

About Us 关于我们 客户服务 联系方式 器件索引 网站地图 最新更新 手机版

站点相关: 国产芯 安防电子 汽车电子 手机便携 工业控制 家用电子 医疗电子 测试测量 网络通信 物联网

北京市海淀区中关村大街18号B座15层1530室 电话:(010)82350740 邮编:100190

电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2025 EEWORLD.com.cn, Inc. All rights reserved
快速回复 返回顶部 返回列表