Rules can be limited to schedule blocks

Each rule has the blocks it acts in (none: always; without a running
schedule always). Stored as a block mask (schedule format 2, version 1
records are converted), in JSON as the blocks' start times. Version 2.7.1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
mangavalkandClaude Opus 5.5 committed 2026-10-15 22:25:00 +02:00
1 parent 87e242bf89
commit 53e9057d02
11 files changed
+166 -19

No files matched your search

+10 -4
View File
@@ -98,7 +98,7 @@ details{margin-top:10px}summary{cursor:pointer;color:var(--dim);font-size:13px;m
<div id="sc_blocks"></div><div class="row"><button class="s" onclick="scAddBlock()">Add block</button></div>
<p class="hint">Every day: a block runs from its time until the next block (the last one continues after midnight). Its pages rotate, each for its time. Without blocks, or switched off, the chosen page (Show tab) is shown. A page chosen by hand is shown until the next block starts.</p></div>
<div class="card"><h2>Rules</h2><div id="sc_rules"></div><div class="row"><button class="s" onclick="scAddRule()">Add rule</button></div>
<p class="hint">Home Assistant states that bring a page to the front (needs the token in the Home Assistant tab). The first rule that shows a page wins, over the schedule and a page chosen by hand. Texts are compared without case; above and below compare numbers. Rules only act while the display is on.</p></div>
<p class="hint">Home Assistant states that bring a page to the front (needs the token in the Home Assistant tab). The first rule that shows a page wins, over the schedule and a page chosen by hand. Texts are compared without case; above and below compare numbers. Rules only act while the display is on, and only in the blocks ticked for them (none ticked: always; without a running schedule the blocks don't matter).</p></div>
<button class="b" onclick="scSave()">Save schedule and rules</button></section>
<section id="t-stopwatch"><div class="card"><h2>Stopwatch</h2>
@@ -267,7 +267,10 @@ function sinp(o,k,type,props){const i=mk('input',Object.assign({type:type||'text
function ssel(o,k,opts,after){const s=mk('select');opts.forEach(([v,t])=>s.append(mk('option',{value:v,textContent:t})));if(!opts.some(x=>x[0]==o[k]))s.append(mk('option',{value:o[k],textContent:o[k]+' (gone)'}));
s.value=o[k];s.onchange=()=>{o[k]=s.value;schedDirty=true;if(after)after()};return s}
function pageOpts(){return pageList.map(p=>[p.name,p.name]).concat(bitmapList.map(b=>[b.name,b.name+' (bitmap)']))}
function schedBuild(){if(!sched)return;$('sc_en').checked=sched.enabled;const d=$('sc_blocks'),opts=pageOpts();d.innerHTML='';
function schedLink(){sched.rules.forEach(r=>{if(!r._b)r._b=(r.blocks||[]).map(t=>sched.blocks.find(b=>b.start==t)).filter(Boolean)})}
function schedPayload(){return{enabled:sched.enabled,blocks:sched.blocks,rules:sched.rules.map(r=>{const o=Object.assign({},r);
o.blocks=sched.blocks.filter(b=>(r._b||[]).includes(b)).map(b=>b.start);delete o._b;return o})}}
function schedBuild(){if(!sched)return;schedLink();$('sc_en').checked=sched.enabled;const d=$('sc_blocks'),opts=pageOpts();d.innerHTML='';
sched.blocks.forEach((b,i)=>{const t=sinp(b,'start','time');t.onchange=()=>{sched.blocks.sort(bySlot);schedDirty=true;schedBuild()};
const ef=mk('input',{type:'checkbox',checked:b.effect});ef.onchange=()=>{b.effect=ef.checked;schedDirty=true};
const box=mk('div',{className:'el'},mk('div',{className:'row'},mk('b',{textContent:'From'}),t,mk('label',{},ef,' switch with the refresh effect'),
@@ -285,13 +288,16 @@ function schedBuild(){if(!sched)return;$('sc_en').checked=sched.enabled;const d=
sinp(ru,'attribute','text',{placeholder:'(the state)',size:10,maxLength:32}),ssel(ru,'compare',RCMP),sinp(ru,'value','text',{size:12,maxLength:32,placeholder:'printing'}),
btn('Test',async()=>{const v=await api('/api/ha/value?entity='+encodeURIComponent(ru.entity)+'&attribute='+encodeURIComponent(ru.attribute));state.textContent='now: '+v.value})),
mk('div',{className:'row'},'then',ssel(ru,'page',opts),ssel(ru,'mode',RMODE,()=>{secs.style.display=ru.mode=='while'?'none':''}),secs,
'read every',sinp(ru,'interval','number',{style:'width:5em',min:2,max:600}),'s',state,btn('Remove',()=>{sched.rules.splice(i,1);schedDirty=true;schedBuild()},'d'))))});
'read every',sinp(ru,'interval','number',{style:'width:5em',min:2,max:600}),'s',state,btn('Remove',()=>{sched.rules.splice(i,1);schedDirty=true;schedBuild()},'d')),
sched.blocks.length?mk('div',{className:'row'},'only in the blocks',...sched.blocks.map(b=>{const c=mk('input',{type:'checkbox',checked:ru._b.includes(b)});
c.onchange=()=>{ru._b=c.checked?ru._b.concat([b]):ru._b.filter(x=>x!==b);schedDirty=true};return mk('label',{},c,' '+b.start)}),
mk('span',{className:'hint',textContent:'(none ticked: always)'})):''))});
if(!sched.rules.length)rd.append(mk('p',{className:'hint',textContent:'No rules.'}))}
function scAddBlock(){if(sched.blocks.length>=8){msg('8 blocks at most',true);return}
sched.blocks.push({start:'12:00',effect:true,items:[{page:settings.page,seconds:30}]});sched.blocks.sort(bySlot);schedDirty=true;schedBuild()}
function scAddRule(){if(sched.rules.length>=8){msg('8 rules at most',true);return}
sched.rules.push({entity:'',attribute:'',compare:'eq',value:'',page:settings.page,mode:'while',seconds:30,interval:10});schedDirty=true;schedBuild()}
async function scSave(){sched.blocks.sort(bySlot);sched=await post('/api/schedule',sched);schedDirty=false;schedBuild();msg('Schedule saved');loadLists()}
async function scSave(){sched.blocks.sort(bySlot);sched=await post('/api/schedule',schedPayload());schedDirty=false;schedBuild();msg('Schedule saved');loadLists()}
async function backToSchedule(){await post('/api/schedule/back');msg('Back to the schedule');setTimeout(pollStatusOnce,500)}
async function pollStatusOnce(){try{status=await api('/api/status');$('sc_now').textContent='Showing: '+status.shown+' ('+status.shown_status+')';$('shownnow').textContent=$('sc_now').textContent}catch(e){}}
// page editor: the ESP32 draws the preview and returns the element boxes