echo" --force-i2c-conflict Continue even when another process owns /dev/i2c-1."
exit0
;;
*)echo"ERROR: unknown option: $arg" >&2;exit2;;
esac
done
for required in app/pigway_pi_control.py app/pigway_pi_control_api.py app/oled_font_5x7.bin web/index.html config/pigway-pi-control.conf systemd/pigway-pi-control.service systemd/pigway-pi-control-api.service;do
if[ ! -f "$required"];then
echo"ERROR: required file missing: $required" >&2
@@ -30,12 +43,21 @@ i2c_conflict_pids() {
I2C_CONFLICTS="$(i2c_conflict_pids)"
if[ -n "$I2C_CONFLICTS"];then
echo"ERROR: /dev/i2c-1 is already in use by another process:" >&2
if["$FORCE_I2C_CONFLICT" -eq 1];then
echo"WARNING: /dev/i2c-1 is already in use by another process:" >&2
else
echo"ERROR: /dev/i2c-1 is already in use by another process:" >&2
fi
whileread -r pid;do
ps -p "$pid" -o pid=,user=,comm=,args= >&2||true
done<<<"$I2C_CONFLICTS"
echo"Stop or reconfigure the conflicting OLED/LED controller, then run the installer again." >&2
exit1
if["$FORCE_I2C_CONFLICT" -eq 1];then
echo"WARNING: continuing because --force-i2c-conflict was specified; no conflicting process will be stopped." >&2
else
echo"Stop or reconfigure the conflicting OLED/LED controller, then run the installer again." >&2
echo"If concurrent control is intentional, rerun with --force-i2c-conflict." >&2
<header><div><h1>Pigway Pi Control</h1><divclass="sub"id="identity">正在连接 Raspberry Pi…</div></div><divclass="live"><spanclass="dot"id="dot"></span><spanid="liveText">离线</span></div></header>
async function loadLogs(){try{const d=await get('/api/v1/logs?limit=200');rows('logRows',d.logs.reverse().map(x=>`<divclass="log">${esc(x.message)}</div>`))}catch(e){$('logRows').innerHTML=`<divclass="error">${esc(e.message)}</div>`}}
async function loadConfig(){try{const d=await get('/api/v1/config');original=d.config;$('configRows').innerHTML=Object.entries(original).map(([section,values])=>`<detailsclass="config-section"><summary>[${esc(section)}]</summary><divclass="fields">${Object.entries(values).map(([k,v])=>`<divclass="field"><label>${esc(k)}<inputdata-section="${esc(section)}"data-key="${esc(k)}"value="${esc(v)}"></label></div>`).join('')}</div></details>`).join('')}catch(e){$('configRows').innerHTML=`<divclass="error">${esc(e.message)}</div>`}}
<header><div><h1>PIGWay Pi Control</h1><divclass="sub"id="identity">正在连接树莓派…</div></div><divclass="live"><spanclass="dot"id="dot"></span><spanid="liveText">离线</span></div></header>
function ownerName(id){const [type,suffix]=String(id||'').split('|',2);return(ownerNames[type]||type)+(suffix?` · ${suffix}`:'')}function rgbName(mode){if(rgbNames[mode])return rgbNames[mode];const p=String(mode||'').split('_'),groups={CPU:'处理器',POWER:'供电',MEMORY:'内存',STORAGE:'存储',NETWORK:'网络',SERVICE:'服务'},levels={WARNING:'普通告警',CRITICAL:'严重告警',EMERGENCY:'紧急告警'};return`${groups[p[0]]||p[0]} · ${levels[p[1]]||p[1]}闪烁`}
function field(section,key,value,extra=''){const id=`${section}.${key}`,opts=options[id],rule=numberRules[id],label=labels[id]||key,help=hints[id]||'';const control=opts?`<selectdata-section="${esc(section)}"data-key="${esc(key)}">${opts.map(([v,l])=>`<optionvalue="${v}"${String(value)===v?'selected':''}>${l}</option>`).join('')}</select>`:`<input${rule?'type="number"':''}${rule?`min="${rule[0]}"max="${rule[1]}"step="${rule[2]}"`:''}data-section="${esc(section)}"data-key="${esc(key)}"value="${esc(value)}">`;return`<divclass="field ${extra}"><label>${esc(label)}${control}</label>${help?`<divclass="hint">${esc(help)}</div>`:''}</div>`}
function rgbGroup(values,prefix,title){const r=values[`${prefix}_r`],g=values[`${prefix}_g`],b=values[`${prefix}_b`];return`<divclass="group"><divclass="group-head"><h3>${title}</h3><spanclass="muted">RGB 自定义颜色</span></div><divclass="rgb-row"><spanclass="swatch"data-swatch="${prefix}"style="background:rgb(${r},${g},${b})"></span>${['r','g','b'].map(c=>field('led',`${prefix}_${c}`,values[`${prefix}_${c}`])).join('')}</div></div>`}
function normalGroup(v){return`<divclass="group"><divclass="group-head"><div><h3>正常运行灯效</h3><divclass="hint">没有告警时显示;告警出现后自动让位。</div></div></div><divclass="fields">${field('led','normal_mode',v.normal_mode,'span2')}<divclass="normal-field span2"data-modes="solid flash">${rgbGroup(v,'normal','自定义颜色')}</div><divclass="normal-field"data-modes="flash">${field('led','normal_flash_on_ms',v.normal_flash_on_ms)}</div><divclass="normal-field"data-modes="flash">${field('led','normal_flash_off_ms',v.normal_flash_off_ms)}</div><divclass="normal-field"data-modes="flow breathe marquee rainbow colorful">${field('led','normal_effect_speed',v.normal_effect_speed)}</div><divclass="normal-field"data-modes="flow breathe">${field('led','normal_effect_color',v.normal_effect_color)}</div></div></div>`}
function renderLed(v){const flash=['warning','critical','emergency'].map((x,i)=>`<divclass="group"><h3>${['普通告警','严重告警','紧急告警'][i]}闪烁节奏</h3><divclass="fields">${field('led',`${x}_flash_on_ms`,v[`${x}_flash_on_ms`])}${field('led',`${x}_flash_off_ms`,v[`${x}_flash_off_ms`])}</div></div>`).join(''),advanced=v.write_delay_ms===undefined?'':`<divclass="group"><h3>硬件高级参数</h3><divclass="fields">${field('led','write_delay_ms',v.write_delay_ms)}</div></div>`;return normalGroup(v)+`<divclass="group"><divclass="group-head"><div><h3>告警对象颜色</h3><divclass="hint">告警对象决定颜色,严重程度决定闪烁速度。</div></div></div>${alertColorGroups.map(([p,t])=>rgbGroup(v,p,t)).join('')}</div>`+flash+advanced}
function renderGeneral(s,v){const [t,d]=sectionMeta[s]||[s,''];return`<divclass="group"><divclass="group-head"><div><h3>${t}</h3><divclass="hint">${d}</div></div></div><divclass="fields">${Object.entries(v).map(([k,x])=>field(s,k,x)).join('')||'<divclass="muted">暂无配置项</div>'}</div></div>`}
function updateConditional(){const mode=document.querySelector('[data-section="led"][data-key="normal_mode"]')?.value||'off';document.querySelectorAll('.normal-field').forEach(x=>x.classList.toggle('hidden',!x.dataset.modes.split(' ').includes(mode)));updateSwatches()}function updateSwatches(){document.querySelectorAll('[data-swatch]').forEach(s=>{const p=s.dataset.swatch,read=c=>Number(document.querySelector(`[data-section="led"][data-key="${p}_${c}"]`)?.value||0);s.style.background=`rgb(${read('r')},${read('g')},${read('b')})`})}
function renderConfig(){const ss=sectionOrder.filter(s=>original[s]);$('configMenu').innerHTML=ss.map((s,i)=>`<buttonclass="${i===0?'active':''}"data-config-section="${s}">${sectionMeta[s]?.[0]||s}</button>`).join('');$('configRows').innerHTML=ss.map((s,i)=>`<sectionclass="config-page ${i===0?'active':''}"data-config-page="${s}"><h2>${sectionMeta[s]?.[0]||s}</h2><pclass="muted">${sectionMeta[s]?.[1]||''}</p>${s==='led'?renderLed(original[s]):renderGeneral(s,original[s])}</section>`).join('');$('configMenu').onclick=e=>{const b=e.target.closest('button');if(!b)return;document.querySelectorAll('[data-config-section]').forEach(x=>x.classList.toggle('active',x===b));document.querySelectorAll('[data-config-page]').forEach(x=>x.classList.toggle('active',x.dataset.configPage===b.dataset.configSection))};$('configRows').oninput=e=>{if(e.target.matches('[data-section="led"]'))updateConditional();markChanged()};updateConditional()}
function markChanged(){let n=0;document.querySelectorAll('#configRows [data-section]').forEach(i=>{if(i.value!==original[i.dataset.section][i.dataset.key])n++});$('saveState').textContent=n?`有 ${n} 项尚未保存`:'尚未修改';$('saveState').className=n?'':'muted'}
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.