Compare commits
2 commits
05c0f1bdcc
...
91341b2d75
Author | SHA1 | Date | |
---|---|---|---|
91341b2d75 | |||
96c74c1f4a |
8 changed files with 663 additions and 85 deletions
|
@ -15,6 +15,16 @@ api:
|
|||
- lambda: |-
|
||||
id(set_pattern).execute(effect_string);
|
||||
|
||||
web_server:
|
||||
port: 80
|
||||
version: 2
|
||||
# local: true
|
||||
# css_url: "https://hass.sqkyote.me/local/esphome/webserver.css"
|
||||
# css_include: ./static/webserver.min.css
|
||||
# js_url: "https://hass.sqkyote.me/local/esphome/prism.js"
|
||||
# js_include: ./static/composite_tuya_light.js
|
||||
ota: false
|
||||
|
||||
uart:
|
||||
rx_pin: GPIO3
|
||||
tx_pin: GPIO1
|
||||
|
|
9
static/composite_tuya_light.js
Normal file
9
static/composite_tuya_light.js
Normal file
|
@ -0,0 +1,9 @@
|
|||
const source=new EventSource("/events");source.addEventListener("log",function(t){const e=document.getElementById("log");let n=[["[1;31m","e"],["[0;33m","w"],["[0;32m","i"],["[0;35m","c"],["[0;36m","d"],["[0;37m","v"]],o="";for(const e of n)t.data.startsWith(e[0])&&(o=e[1]);""==o&&(e.innerHTML+=t.data+"\n"),e.innerHTML+='<span class="'+o+'">'+t.data.substr(7,t.data.length-11)+"</span>\n"});const actions=[["switch",["toggle"]],["light",["toggle"]],["fan",["toggle"]],["cover",["open","close"]],["button",["press"]],["lock",["lock","unlock","open"]]];const multi_actions=[["select","option"],["number","value"]];source.addEventListener("state",function(t){const e=JSON.parse(t.data);document.getElementById(e.id).children[1].innerText=e.state});const states=document.getElementById("states");let row,i=0;for(;row=states.rows[i];i++)if(row.children[2].children.length){for(const t of actions)if(row.classList.contains(t[0])){let e=row.id.substr(t[0].length+1);for(let n=0;n<row.children[2].children.length&&n<t[1].length;n++)row.children[2].children[n].addEventListener("click",function(){const o=new XMLHttpRequest;o.open("POST","/"+t[0]+"/"+e+"/"+t[1][n],!0),o.send()})}for(const t of multi_actions)if(row.classList.contains(t[0])){let e=row.id.substr(t[0].length+1);row.children[2].children[0].addEventListener("change",function(){const n=new XMLHttpRequest;n.open("POST","/"+t[0]+"/"+e+"/set?"+t[1]+"="+encodeURIComponent(this.value),!0),n.send()})}}
|
||||
{let e,t,r,o,n,l,s,i,f,h,w,a,d,u,_,c,S,g,y,b,m,v,j,x,O;s=Object.getPrototypeOf,f={},h=s(i={isConnected:1}),w=s(s),a=(e,t,r,o)=>(e??(setTimeout(r,o),new Set)).add(t),d=(e,t,o)=>{let n=r;r=t;try{return e(o)}catch(e){return console.error(e),o}finally{r=n}},u=e=>e.filter(e=>e.t?.isConnected),_=e=>n=a(n,e,()=>{for(let e of n)e.o=u(e.o),e.l=u(e.l);n=l},1e3),c={get val(){return r?.i?.add(this),this.rawVal},get oldVal(){return r?.i?.add(this),this.h},set val(o){r?.u?.add(this),o!==this.rawVal&&(this.rawVal=o,this.o.length+this.l.length?(t?.add(this),e=a(e,this,x)):this.h=o)}},S=e=>({__proto__:c,rawVal:e,h:e,o:[],l:[]}),g=(e,t)=>{let r={i:new Set,u:new Set},n={f:e},l=o;o=[];let s=d(e,r,t);s=(s??document).nodeType?s:new Text(s);for(let e of r.i)r.u.has(e)||(_(e),e.o.push(n));for(let e of o)e.t=s;return o=l,n.t=s},y=(e,t=S(),r)=>{let n={i:new Set,u:new Set},l={f:e,s:t};l.t=r??o?.push(l)??i,t.val=d(e,n,t.rawVal);for(let e of n.i)n.u.has(e)||(_(e),e.l.push(l));return t},b=(e,...t)=>{for(let r of t.flat(1/0)){let t=s(r??0),o=t===c?g(()=>r.val):t===w?g(r):r;o!=l&&e.append(o)}return e},m=(e,t,...r)=>{let[{is:o,...n},...i]=s(r[0]??0)===h?r:[{},...r],a=e?document.createElementNS(e,t,{is:o}):document.createElement(t,{is:o});for(let[e,r]of Object.entries(n)){let o=t=>t?Object.getOwnPropertyDescriptor(t,e)??o(s(t)):l,n=t+","+e,i=f[n]??=o(s(a))?.set??0,h=e.startsWith("on")?(t,r)=>{let o=e.slice(2);a.removeEventListener(o,r),a.addEventListener(o,t)}:i?i.bind(a):a.setAttribute.bind(a,e),d=s(r??0);e.startsWith("on")||d===w&&(r=y(r),d=c),d===c?g(()=>(h(r.val,r.h),a)):h(r)}return b(a,i)},v=e=>({get:(t,r)=>m.bind(l,e,r)}),j=(e,t)=>t?t!==e&&e.replaceWith(t):e.remove(),x=()=>{let r=0,o=[...e].filter(e=>e.rawVal!==e.h);do{t=new Set;for(let e of new Set(o.flatMap(e=>e.l=u(e.l))))y(e.f,e.s,e.t),e.t=l}while(++r<100&&(o=[...t]).length);let n=[...e].filter(e=>e.rawVal!==e.h);e=l;for(let e of new Set(n.flatMap(e=>e.o=u(e.o))))j(e.t,g(e.f,e.t)),e.t=l;for(let e of n)e.h=e.rawVal},O={tags:new Proxy(e=>new Proxy(m,v(e)),v()),hydrate:(e,t)=>j(e,g(t,e)),add:b,state:S,derive:y},window.van=O;}
|
||||
{let e,t,r,{fromEntries:o,entries:l,keys:n,hasOwn:f,getPrototypeOf:a}=Object,{get:i,set:y,deleteProperty:c,ownKeys:s}=Reflect,{state:m,derive:d,add:u}=van,b=1e3,w=Symbol(),A=Symbol(),S=Symbol(),_=Symbol(),g=Symbol(),p=Symbol(),P=e=>(e[A]=1,e),v=e=>e instanceof Object&&!(e instanceof Function)&&!e[p],h=e=>{if(e?.[A]){let t=m();return d(()=>{let r=e();v(t.rawVal)&&v(r)?B(t.rawVal,r):t.val=x(r)}),t}return m(x(e))},F=e=>{let t=Array.isArray(e)?[]:{__proto__:a(e)};for(let[r,o]of l(e))t[r]=h(o);return t[S]=[],t[_]=m(1),t},O={get:(e,t,r)=>t===w?e:f(e,t)?Array.isArray(e)&&"length"===t?(e[_].val,e.length):e[t].val:i(e,t,r),set:(e,o,l,n)=>f(e,o)?Array.isArray(e)&&"length"===o?(l!==e.length&&++e[_].val,e.length=l,1):(e[o].val=x(l),1):o in e?y(e,o,l,n):y(e,o,h(l))&&(++e[_].val,C(e).forEach(E.bind(t,n,o,e[o],r)),1),deleteProperty:(e,t)=>(c(e,t)&&R(e,t),++e[_].val),ownKeys:e=>(e[_].val,s(e))},x=e=>!v(e)||e[w]?e:new Proxy(F(e),O),D=e=>(e[p]=1,e),j=e=>e[w],K=a(m()),N=e=>new Proxy(e,{get:(e,t,r)=>a(e[t]??0)===K?{val:k(e[t].rawVal)}:i(e,t,r)}),k=e=>e?.[w]?new Proxy(N(e[w]),O):e,C=e=>e[S]=e[S].filter(e=>e.t.isConnected),E=(e,t,r,o,{t:l,f:f})=>{let a=Array.isArray(e),i=a?Number(t):t;u(l,()=>l[g][t]=f(r,()=>delete e[t],i)),a&&!o&&i!==e.length-1&&l.insertBefore(l.lastChild,l[g][n(e).find(e=>Number(e)>i)])},R=(e,t)=>{for(let r of C(e)){let e=r.t[g];e[t]?.remove(),delete e[t]}},T=r=>(e??(setTimeout(()=>(e.forEach(C),e=t),b),e=new Set)).add(r),q=(e,t,r)=>{let o={t:e instanceof Function?e():e,f:r},n=t[w];o.t[g]={},n[S].push(o),T(n);for(let[e,r]of l(n))E(t,e,r,1,o);return o.t},z=(e,t)=>{for(let[r,o]of l(t)){let t=e[r];v(t)&&v(o)?z(t,o):e[r]=o}for(let r in e)f(t,r)||delete e[r];let r=n(t),o=Array.isArray(e);if(o||n(e).some((e,t)=>e!==r[t])){let l=e[w];if(o)e.length=t.length;else{++l[_].val;let e={...l};for(let e of r)delete l[e];for(let t of r)l[t]=e[t]}for(let{t:e}of C(l)){let{firstChild:t,[g]:o}=e;for(let l of r)t===o[l]?t=t.nextSibling:e.insertBefore(o[l],t)}}return e},B=(e,n)=>{r=1;try{return z(e,n instanceof Function?Array.isArray(e)?n(e.filter(e=>1)):o(n(l(e))):n)}finally{r=t}},G=e=>Array.isArray(e)?e.filter(e=>1).map(G):v(e)?o(l(e).map(([e,t])=>[e,G(t)])):e;window.vanX={calc:P,reactive:x,noreactive:D,stateFields:j,raw:k,list:q,replace:B,compact:G}}
|
||||
function hex2tuya(hex){hex=hex.replace(/^#/,'');const r=parseInt(hex.substring(0,2),16);const g=parseInt(hex.substring(2,4),16);const b=parseInt(hex.substring(4,6),16);const hsv=rgb2hsv(r,g,b);const h=Math.round(hsv.h).toString(16).padStart(4,"0");const s=Math.round(hsv.s*10).toString(16).padStart(4,"0");const v=Math.round(hsv.v*10).toString(16).padStart(4,"0");const tuyastr=h+s+v+"00000000";return tuyastr;}
|
||||
function rgb2hsv(r,g,b){let rabs,gabs,babs,rr,gg,bb,h,s,v,diff,diffc,percentRoundFn;rabs=r/255;gabs=g/255;babs=b/255;v=Math.max(rabs,gabs,babs),diff=v-Math.min(rabs,gabs,babs);diffc=c=>(v-c)/6/diff+1/2;percentRoundFn=num=>Math.round(num*100)/100;if(diff==0){h=s=0;}else{s=diff/v;rr=diffc(rabs);gg=diffc(gabs);bb=diffc(babs);if(rabs===v){h=bb-gg;}else if(gabs===v){h=(1/3)+rr-bb;}else if(babs===v){h=(2/3)+gg-rr;}
|
||||
if(h<0){h+=1;}else if(h>1){h-=1;}}
|
||||
return{h:Math.round(h*360),s:percentRoundFn(s*100),v:percentRoundFn(v*100)};}
|
||||
const{a,button,p,div,input,span,ul,li,select,option}=van.tags;const lightModes=["Fade","Flash","Music"];const lightPatterns=["Solid","Up","Down","Center","Stripe","Out","In","Rotate","Spiral"];const ColorPicker=()=>{const config=vanX.reactive({id:Math.round(Math.random()*30),mode:0,pattern:0,speed:10,colors:[{color:"#ffffff"}]});return div(div(span("ID: ",input({type:"number",min:0,max:30,required:true,value:config.id,oninput:e=>config.id=parseInt(e.target.value)}))),div(span("Mode: ",select({oninput:e=>config.mode=parseInt(e.target.value)},...lightModes.map((v,i)=>option({value:i.toString(),selected:i==config.mode},v))))),div(span("Pattern: ",select({oninput:e=>config.pattern=parseInt(e.target.value)},...lightPatterns.map((v,i)=>option({value:i.toString(),selected:i==config.pattern},v))))),div(span("Speed: ",()=>config.speed,input({type:'range',min:0,max:100,required:true,value:config.speed,oninput:e=>config.speed=parseInt(e.target.value)}))),div("Colors:",vanX.list(ul,config.colors,({val:v},deleter)=>li(span(a({onclick:deleter},"✖️"),input({type:"color",value:v.color,oninput:e=>v.color=e.target.value}),()=>`${v.color}`))),button({onclick:()=>config.colors.push({color:"#ffffff"})},"➕")),div(()=>{const id=config.id.toString(16).padStart(2,"0");const mode=config.mode.toString(16).padStart(2,"0");const pattern=config.pattern.toString(16).padStart(2,"0");const speed=config.speed.toString(16).padStart(2,"0");const colorString=config.colors.map(v=>hex2tuya(v.color)).join("")
|
||||
const configstring=`${id}${speed}${mode}${pattern}${colorString}`;return configstring;}));};setTimeout(() => van.add(document.body,ColorPicker()),1000);
|
73
static/webserver-v1.js
Normal file
73
static/webserver-v1.js
Normal file
|
@ -0,0 +1,73 @@
|
|||
const source = new EventSource("/events");
|
||||
|
||||
source.addEventListener('log', function (e) {
|
||||
const log = document.getElementById("log");
|
||||
let log_prefs = [
|
||||
["\u001b[1;31m", 'e'],
|
||||
["\u001b[0;33m", 'w'],
|
||||
["\u001b[0;32m", 'i'],
|
||||
["\u001b[0;35m", 'c'],
|
||||
["\u001b[0;36m", 'd'],
|
||||
["\u001b[0;37m", 'v'],
|
||||
];
|
||||
|
||||
let klass = '';
|
||||
for (const log_pref of log_prefs){
|
||||
if (e.data.startsWith(log_pref[0])) {
|
||||
klass = log_pref[1];
|
||||
}
|
||||
}
|
||||
if (klass == ''){
|
||||
log.innerHTML += e.data + '\n';
|
||||
}
|
||||
log.innerHTML += '<span class="' + klass + '">' + e.data.substr(7, e.data.length - 11) + "</span>\n";
|
||||
});
|
||||
|
||||
const actions = [
|
||||
["switch", ["toggle"]],
|
||||
["light", ["toggle"]],
|
||||
["fan", ["toggle"]],
|
||||
["cover", ["open", "close"]],
|
||||
["button", ["press"]],
|
||||
["lock", ["lock", "unlock", "open"]],
|
||||
];
|
||||
const multi_actions = [
|
||||
["select", "option"],
|
||||
["number", "value"],
|
||||
];
|
||||
|
||||
source.addEventListener('state', function (e) {
|
||||
const data = JSON.parse(e.data);
|
||||
document.getElementById(data.id).children[1].innerText = data.state;
|
||||
});
|
||||
|
||||
const states = document.getElementById("states");
|
||||
let i = 0, row;
|
||||
for (; row = states.rows[i]; i++) {
|
||||
if (!row.children[2].children.length) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const domain of actions){
|
||||
if (row.classList.contains(domain[0])) {
|
||||
let id = row.id.substr(domain[0].length+1);
|
||||
for (let j=0;j<row.children[2].children.length && j < domain[1].length; j++){
|
||||
row.children[2].children[j].addEventListener('click', function () {
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open("POST", '/'+domain[0]+'/' + id + '/' + domain[1][j], true);
|
||||
xhr.send();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const domain of multi_actions){
|
||||
if (row.classList.contains(domain[0])) {
|
||||
let id = row.id.substr(domain[0].length+1);
|
||||
row.children[2].children[0].addEventListener('change', function () {
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open("POST", '/'+domain[0]+'/' + id + '/set?'+domain[1]+'=' + encodeURIComponent(this.value), true);
|
||||
xhr.send();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
416
static/webserver.css
Normal file
416
static/webserver.css
Normal file
|
@ -0,0 +1,416 @@
|
|||
/* Based off of https://github.com/sindresorhus/github-markdown-css */
|
||||
|
||||
.markdown-body {
|
||||
-ms-text-size-adjust: 100%;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
line-height: 1.5;
|
||||
color: #24292e;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
|
||||
font-size: 16px;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.markdown-body a {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.markdown-body a:active,
|
||||
.markdown-body a:hover {
|
||||
outline-width: 0;
|
||||
}
|
||||
|
||||
.markdown-body strong {
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
.markdown-body h1 {
|
||||
font-size: 2em;
|
||||
margin: 0.67em 0;
|
||||
}
|
||||
|
||||
.markdown-body img {
|
||||
border-style: none;
|
||||
}
|
||||
|
||||
.markdown-body pre {
|
||||
font-family: monospace, monospace;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
.markdown-body hr {
|
||||
box-sizing: content-box;
|
||||
height: 0;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.markdown-body input {
|
||||
font: inherit;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.markdown-body input {
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.markdown-body [type="checkbox"] {
|
||||
box-sizing: border-box;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.markdown-body * {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.markdown-body input {
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
.markdown-body a {
|
||||
color: #0366d6;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.markdown-body a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.markdown-body strong {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.markdown-body hr {
|
||||
height: 0;
|
||||
margin: 15px 0;
|
||||
overflow: hidden;
|
||||
background: transparent;
|
||||
border: 0;
|
||||
border-bottom: 1px solid #dfe2e5;
|
||||
}
|
||||
|
||||
.markdown-body hr::before {
|
||||
display: table;
|
||||
content: "";
|
||||
}
|
||||
|
||||
.markdown-body hr::after {
|
||||
display: table;
|
||||
clear: both;
|
||||
content: "";
|
||||
}
|
||||
|
||||
.markdown-body table {
|
||||
border-spacing: 0;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.markdown-body td,
|
||||
.markdown-body th {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.markdown-body h1,
|
||||
.markdown-body h2,
|
||||
.markdown-body h3,
|
||||
.markdown-body h4,
|
||||
.markdown-body h5,
|
||||
.markdown-body h6 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.markdown-body h1 {
|
||||
font-size: 32px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.markdown-body h2 {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.markdown-body h3 {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.markdown-body h4 {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.markdown-body h5 {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.markdown-body h6 {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.markdown-body p {
|
||||
margin-top: 0;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.markdown-body blockquote {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.markdown-body ul,
|
||||
.markdown-body ol {
|
||||
padding-left: 0;
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.markdown-body ol ol,
|
||||
.markdown-body ul ol {
|
||||
list-style-type: lower-roman;
|
||||
}
|
||||
|
||||
.markdown-body ul ul ol,
|
||||
.markdown-body ul ol ol,
|
||||
.markdown-body ol ul ol,
|
||||
.markdown-body ol ol ol {
|
||||
list-style-type: lower-alpha;
|
||||
}
|
||||
|
||||
.markdown-body dd {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.markdown-body pre {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace;
|
||||
font-size: 12px;
|
||||
}
|
||||
.markdown-body::before {
|
||||
display: table;
|
||||
content: "";
|
||||
}
|
||||
|
||||
.markdown-body::after {
|
||||
display: table;
|
||||
clear: both;
|
||||
content: "";
|
||||
}
|
||||
|
||||
.markdown-body>*:first-child {
|
||||
margin-top: 0 !important;
|
||||
}
|
||||
|
||||
.markdown-body>*:last-child {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
.markdown-body a:not([href]) {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.markdown-body p,
|
||||
.markdown-body blockquote,
|
||||
.markdown-body ul,
|
||||
.markdown-body ol,
|
||||
.markdown-body dl,
|
||||
.markdown-body table,
|
||||
.markdown-body pre {
|
||||
margin-top: 0;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.markdown-body hr {
|
||||
height: 0.25em;
|
||||
padding: 0;
|
||||
margin: 24px 0;
|
||||
background-color: #e1e4e8;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.markdown-body blockquote {
|
||||
padding: 0 1em;
|
||||
color: #6a737d;
|
||||
border-left: 0.25em solid #dfe2e5;
|
||||
}
|
||||
|
||||
.markdown-body blockquote>:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.markdown-body blockquote>:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.markdown-body h1,
|
||||
.markdown-body h2,
|
||||
.markdown-body h3,
|
||||
.markdown-body h4,
|
||||
.markdown-body h5,
|
||||
.markdown-body h6 {
|
||||
margin-top: 24px;
|
||||
margin-bottom: 16px;
|
||||
font-weight: 600;
|
||||
line-height: 1.25;
|
||||
}
|
||||
|
||||
.markdown-body h1 {
|
||||
padding-bottom: 0.3em;
|
||||
font-size: 2em;
|
||||
border-bottom: 1px solid #eaecef;
|
||||
}
|
||||
|
||||
.markdown-body h2 {
|
||||
padding-bottom: 0.3em;
|
||||
font-size: 1.5em;
|
||||
border-bottom: 1px solid #eaecef;
|
||||
}
|
||||
|
||||
.markdown-body h3 {
|
||||
font-size: 1.25em;
|
||||
}
|
||||
|
||||
.markdown-body h4 {
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
.markdown-body h5 {
|
||||
font-size: 0.875em;
|
||||
}
|
||||
|
||||
.markdown-body h6 {
|
||||
font-size: 0.85em;
|
||||
color: #6a737d;
|
||||
}
|
||||
|
||||
.markdown-body ul,
|
||||
.markdown-body ol {
|
||||
padding-left: 2em;
|
||||
}
|
||||
|
||||
.markdown-body ul ul,
|
||||
.markdown-body ul ol,
|
||||
.markdown-body ol ol,
|
||||
.markdown-body ol ul {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.markdown-body li {
|
||||
word-wrap: break-all;
|
||||
}
|
||||
|
||||
.markdown-body li>p {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.markdown-body li+li {
|
||||
margin-top: 0.25em;
|
||||
}
|
||||
|
||||
.markdown-body dl {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.markdown-body dl dt {
|
||||
padding: 0;
|
||||
margin-top: 16px;
|
||||
font-size: 1em;
|
||||
font-style: italic;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.markdown-body dl dd {
|
||||
padding: 0 16px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.markdown-body table {
|
||||
display: block;
|
||||
width: 100%;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.markdown-body table th {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.markdown-body table th,
|
||||
.markdown-body table td {
|
||||
padding: 6px 13px;
|
||||
border: 1px solid #dfe2e5;
|
||||
}
|
||||
|
||||
.markdown-body table tr {
|
||||
background-color: #fff;
|
||||
border-top: 1px solid #c6cbd1;
|
||||
}
|
||||
|
||||
.markdown-body table tr:nth-child(2n) {
|
||||
background-color: #f6f8fa;
|
||||
}
|
||||
|
||||
.markdown-body img {
|
||||
max-width: 100%;
|
||||
box-sizing: content-box;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.markdown-body img[align=right] {
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
.markdown-body img[align=left] {
|
||||
padding-right: 20px;
|
||||
}
|
||||
|
||||
.markdown-body pre {
|
||||
padding: 16px;
|
||||
overflow: auto;
|
||||
font-size: 85%;
|
||||
line-height: 1.45;
|
||||
background-color: #f6f8fa;
|
||||
border-radius: 3px;
|
||||
word-wrap: normal;
|
||||
}
|
||||
.markdown-body :checked+.radio-label {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
border-color: #0366d6;
|
||||
}
|
||||
|
||||
.markdown-body hr {
|
||||
border-bottom-color: #eee;
|
||||
}
|
||||
|
||||
#log .v {
|
||||
color: #888888;
|
||||
}
|
||||
|
||||
#log .d {
|
||||
color: #00DDDD;
|
||||
}
|
||||
|
||||
#log .c {
|
||||
color: magenta;
|
||||
}
|
||||
|
||||
#log .i {
|
||||
color: limegreen;
|
||||
}
|
||||
|
||||
#log .w {
|
||||
color: yellow;
|
||||
}
|
||||
|
||||
#log .e {
|
||||
color: red;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#log {
|
||||
background-color: #1c1c1c;
|
||||
}
|
2
static/webserver.min.js
vendored
2
static/webserver.min.js
vendored
|
@ -1 +1 @@
|
|||
const source=new EventSource("/events");source.addEventListener("log",function(t){const e=document.getElementById("log");let n=[["[1;31m","e"],["[0;33m","w"],["[0;32m","i"],["[0;35m","c"],["[0;36m","d"],["[0;37m","v"]],o="";for(const e of n)t.data.startsWith(e[0])&&(o=e[1]);""==o&&(e.innerHTML+=t.data+"\n"),e.innerHTML+='<span class="'+o+'">'+t.data.substr(7,t.data.length-11)+"</span>\n"}),actions=[["switch",["toggle"]],["light",["toggle"]],["fan",["toggle"]],["cover",["open","close"]],["button",["press"]],["lock",["lock","unlock","open"]]],multi_actions=[["select","option"],["number","value"]],source.addEventListener("state",function(t){const e=JSON.parse(t.data);document.getElementById(e.id).children[1].innerText=e.state});const states=document.getElementById("states");let row,i=0;for(;row=states.rows[i];i++)if(row.children[2].children.length){for(const t of actions)if(row.classList.contains(t[0])){let e=row.id.substr(t[0].length+1);for(let n=0;n<row.children[2].children.length&&n<t[1].length;n++)row.children[2].children[n].addEventListener("click",function(){const o=new XMLHttpRequest;o.open("POST","/"+t[0]+"/"+e+"/"+t[1][n],!0),o.send()})}for(const t of multi_actions)if(row.classList.contains(t[0])){let e=row.id.substr(t[0].length+1);row.children[2].children[0].addEventListener("change",function(){const n=new XMLHttpRequest;n.open("POST","/"+t[0]+"/"+e+"/set?"+t[1]+"="+encodeURIComponent(this.value),!0),n.send()})}}
|
||||
const source=new EventSource("/events");source.addEventListener("log",function(t){const e=document.getElementById("log");let n=[["[1;31m","e"],["[0;33m","w"],["[0;32m","i"],["[0;35m","c"],["[0;36m","d"],["[0;37m","v"]],o="";for(const e of n)t.data.startsWith(e[0])&&(o=e[1]);""==o&&(e.innerHTML+=t.data+"\n"),e.innerHTML+='<span class="'+o+'">'+t.data.substr(7,t.data.length-11)+"</span>\n"});const actions=[["switch",["toggle"]],["light",["toggle"]],["fan",["toggle"]],["cover",["open","close"]],["button",["press"]],["lock",["lock","unlock","open"]]];const multi_actions=[["select","option"],["number","value"]];source.addEventListener("state",function(t){const e=JSON.parse(t.data);document.getElementById(e.id).children[1].innerText=e.state});const states=document.getElementById("states");let row,i=0;for(;row=states.rows[i];i++)if(row.children[2].children.length){for(const t of actions)if(row.classList.contains(t[0])){let e=row.id.substr(t[0].length+1);for(let n=0;n<row.children[2].children.length&&n<t[1].length;n++)row.children[2].children[n].addEventListener("click",function(){const o=new XMLHttpRequest;o.open("POST","/"+t[0]+"/"+e+"/"+t[1][n],!0),o.send()})}for(const t of multi_actions)if(row.classList.contains(t[0])){let e=row.id.substr(t[0].length+1);row.children[2].children[0].addEventListener("change",function(){const n=new XMLHttpRequest;n.open("POST","/"+t[0]+"/"+e+"/set?"+t[1]+"="+encodeURIComponent(this.value),!0),n.send()})}}
|
||||
|
|
100
tuyacolor/app.js
Normal file
100
tuyacolor/app.js
Normal file
|
@ -0,0 +1,100 @@
|
|||
const {a, button, p, div, input, span, ul, li, select, option} = van.tags;
|
||||
const lightModes = [
|
||||
"Fade",
|
||||
"Flash",
|
||||
"Music"
|
||||
];
|
||||
const lightPatterns = [
|
||||
"Solid",
|
||||
"Up",
|
||||
"Down",
|
||||
"Center",
|
||||
"Stripe",
|
||||
"Out",
|
||||
"In",
|
||||
"Rotate",
|
||||
"Spiral"
|
||||
];
|
||||
const ColorPicker = () => {
|
||||
const config = vanX.reactive({
|
||||
id: Math.round(Math.random()*30),
|
||||
mode: 0,
|
||||
pattern: 0,
|
||||
speed: 10,
|
||||
colors: [{color: "#ffffff"}]
|
||||
});
|
||||
return div(
|
||||
div(
|
||||
span(
|
||||
"ID: ",
|
||||
input({
|
||||
type: "number",
|
||||
min: 0,
|
||||
max: 30,
|
||||
required: true,
|
||||
value: config.id,
|
||||
oninput: e => config.id = parseInt(e.target.value)
|
||||
})
|
||||
)
|
||||
),
|
||||
div(
|
||||
span(
|
||||
"Mode: ",
|
||||
select(
|
||||
{oninput: e => config.mode = parseInt(e.target.value) },
|
||||
...lightModes.map((v,i) => option({ value: i.toString(), selected: i == config.mode}, v))
|
||||
)
|
||||
)
|
||||
),
|
||||
div(
|
||||
span(
|
||||
"Pattern: ",
|
||||
select(
|
||||
{oninput: e => config.pattern = parseInt(e.target.value) },
|
||||
...lightPatterns.map((v,i) => option({ value: i.toString(), selected: i == config.pattern}, v))
|
||||
)
|
||||
)
|
||||
),
|
||||
div(
|
||||
span(
|
||||
"Speed: ",
|
||||
() => config.speed,
|
||||
input({
|
||||
type: 'range',
|
||||
min: 0,
|
||||
max: 100,
|
||||
required: true,
|
||||
value: config.speed,
|
||||
oninput: e => config.speed = parseInt(e.target.value)
|
||||
})
|
||||
)
|
||||
),
|
||||
div(
|
||||
"Colors:",
|
||||
vanX.list(ul, config.colors, ({val: v}, deleter) => li(
|
||||
span(
|
||||
a({onclick: deleter}, "X"),
|
||||
input({
|
||||
type: "color",
|
||||
value: v.color,
|
||||
oninput: e => v.color = e.target.value
|
||||
}),
|
||||
() => ` ${v.color}`
|
||||
)
|
||||
)),
|
||||
button({onclick: () => config.colors.push({color:"#ffffff"})}, "+")
|
||||
),
|
||||
div(
|
||||
() => {
|
||||
const id = config.id.toString(16).padStart(2,"0");
|
||||
const mode = config.mode.toString(16).padStart(2,"0");
|
||||
const pattern = config.pattern.toString(16).padStart(2,"0");
|
||||
const speed = config.speed.toString(16).padStart(2,"0");
|
||||
const colorString = config.colors.map(v => hex2tuya(v.color)).join("")
|
||||
const configstring = `${id}${speed}${mode}${pattern}${colorString}`;
|
||||
return configstring;
|
||||
}
|
||||
)
|
||||
);
|
||||
};
|
||||
van.add(document.body, ColorPicker());
|
50
tuyacolor/hex2tuya.js
Normal file
50
tuyacolor/hex2tuya.js
Normal file
|
@ -0,0 +1,50 @@
|
|||
function hex2tuya(hex) {
|
||||
hex = hex.replace(/^#/,'');
|
||||
const r = parseInt(hex.substring(0,2),16);
|
||||
const g = parseInt(hex.substring(2,4),16);
|
||||
const b = parseInt(hex.substring(4,6),16);
|
||||
const hsv = rgb2hsv(r,g,b);
|
||||
const h = Math.round(hsv.h).toString(16).padStart(4,"0");
|
||||
const s = Math.round(hsv.s*10).toString(16).padStart(4,"0");
|
||||
const v = Math.round(hsv.v*10).toString(16).padStart(4,"0");
|
||||
const tuyastr = h+s+v+"00000000";
|
||||
return tuyastr;
|
||||
}
|
||||
|
||||
function rgb2hsv (r, g, b) {
|
||||
// Sourced from https://stackoverflow.com/a/8023734
|
||||
let rabs, gabs, babs, rr, gg, bb, h, s, v, diff, diffc, percentRoundFn;
|
||||
rabs = r / 255;
|
||||
gabs = g / 255;
|
||||
babs = b / 255;
|
||||
v = Math.max(rabs, gabs, babs),
|
||||
diff = v - Math.min(rabs, gabs, babs);
|
||||
diffc = c => (v - c) / 6 / diff + 1 / 2;
|
||||
percentRoundFn = num => Math.round(num * 100) / 100;
|
||||
if (diff == 0) {
|
||||
h = s = 0;
|
||||
} else {
|
||||
s = diff / v;
|
||||
rr = diffc(rabs);
|
||||
gg = diffc(gabs);
|
||||
bb = diffc(babs);
|
||||
|
||||
if (rabs === v) {
|
||||
h = bb - gg;
|
||||
} else if (gabs === v) {
|
||||
h = (1 / 3) + rr - bb;
|
||||
} else if (babs === v) {
|
||||
h = (2 / 3) + gg - rr;
|
||||
}
|
||||
if (h < 0) {
|
||||
h += 1;
|
||||
}else if (h > 1) {
|
||||
h -= 1;
|
||||
}
|
||||
}
|
||||
return {
|
||||
h: Math.round(h * 360),
|
||||
s: percentRoundFn(s * 100),
|
||||
v: percentRoundFn(v * 100)
|
||||
};
|
||||
}
|
|
@ -1,91 +1,11 @@
|
|||
<html>
|
||||
<head>
|
||||
<title>Tuya Color Patterns</title>
|
||||
<script type="text/javascript" src="./van.nomodule.min.js" />
|
||||
<script type="text/javascript" src="./van-x.nomodule.min.js" />
|
||||
<script type="text/javascript">
|
||||
function hex2tuya(hex) {
|
||||
hex = hex.replace(/^#/,'');
|
||||
const r = parseInt(hex.substring(0,2),16);
|
||||
const g = parseInt(hex.substring(2,4),16);
|
||||
const b = parseInt(hex.substring(4,6),16);
|
||||
const hsv = rgb2hsv(r,g,b);
|
||||
const h = Math.round(hsv.h).toString(16).padStart(4,"0");
|
||||
const s = Math.round(hsv.s*10).toString(16).padStart(4,"0");
|
||||
const v = Math.round(hsv.v*10).toString(16).padStart(4,"0");
|
||||
const tuyastr = h+s+v+"00000000";
|
||||
return tuyastr;
|
||||
}
|
||||
|
||||
function rgb2hsv (r, g, b) {
|
||||
// Sourced from https://stackoverflow.com/a/8023734
|
||||
let rabs, gabs, babs, rr, gg, bb, h, s, v, diff, diffc, percentRoundFn;
|
||||
rabs = r / 255;
|
||||
gabs = g / 255;
|
||||
babs = b / 255;
|
||||
v = Math.max(rabs, gabs, babs),
|
||||
diff = v - Math.min(rabs, gabs, babs);
|
||||
diffc = c => (v - c) / 6 / diff + 1 / 2;
|
||||
percentRoundFn = num => Math.round(num * 100) / 100;
|
||||
if (diff == 0) {
|
||||
h = s = 0;
|
||||
} else {
|
||||
s = diff / v;
|
||||
rr = diffc(rabs);
|
||||
gg = diffc(gabs);
|
||||
bb = diffc(babs);
|
||||
|
||||
if (rabs === v) {
|
||||
h = bb - gg;
|
||||
} else if (gabs === v) {
|
||||
h = (1 / 3) + rr - bb;
|
||||
} else if (babs === v) {
|
||||
h = (2 / 3) + gg - rr;
|
||||
}
|
||||
if (h < 0) {
|
||||
h += 1;
|
||||
}else if (h > 1) {
|
||||
h -= 1;
|
||||
}
|
||||
}
|
||||
return {
|
||||
h: Math.round(h * 360),
|
||||
s: percentRoundFn(s * 100),
|
||||
v: percentRoundFn(v * 100)
|
||||
};
|
||||
}
|
||||
</script>
|
||||
<script type="text/javascript" src="van.nomodule.min.js"></script>
|
||||
<script type="text/javascript" src="van-x.nomodule.min.js"></script>
|
||||
<script type="text/javascript" src="hex2tuya.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
<script type="text/javascript" id="appcode">
|
||||
const {a, button, del, div, input, span, ul, li} = van.tags;
|
||||
const patternModes = [
|
||||
"Fade",
|
||||
"Flash",
|
||||
"Music"
|
||||
];
|
||||
const patternTypes = [
|
||||
"Solid",
|
||||
"Up",
|
||||
"Down",
|
||||
"Center",
|
||||
"Stripe",
|
||||
"Out",
|
||||
"In",
|
||||
"Rotate",
|
||||
"Spiral"
|
||||
];
|
||||
const patternId = Math.round(Math.random()*255).toString(16).padStart(2,"0");
|
||||
const ColorPicker = () => {
|
||||
const config = vanX.reactive({
|
||||
'id': patternId,
|
||||
'mode': 0,
|
||||
'type': 0,
|
||||
'speed': 10,
|
||||
'colors':[],
|
||||
});
|
||||
van.derive(() => {});
|
||||
};
|
||||
</script>
|
||||
<script type="text/javascript" id="appcode" src="app.js"></script>
|
||||
</html>
|
Loading…
Add table
Add a link
Reference in a new issue