From 7f7e02b0854172bfdf077be88209fc3945330af6 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 13 Nov 2026 03:15:01 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=8E=89=20Sync=202026-11-13=2003:15:01?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- luci-app-fancontrol/Makefile | 53 +- .../luci-static/resources/view/fancontrol.css | 171 ------ .../luci-static/resources/view/fancontrol.js | 463 ---------------- .../resources/view/fancontrol/log.js | 88 +++ .../resources/view/fancontrol/overview.js | 329 ++++++++++++ .../resources/view/fancontrol/settings.js | 76 +++ .../po/templates/fancontrol.pot | 261 +++++++++ luci-app-fancontrol/po/zh_Hans/fancontrol.po | 508 ++++++++---------- .../root/etc/config/fancontrol | 10 + .../root/etc/init.d/fancontrol | 44 ++ luci-app-fancontrol/root/usr/sbin/fancontrold | 249 +++++++++ .../luci/menu.d/luci-app-fancontrol.json | 36 +- .../share/rpcd/acl.d/luci-app-fancontrol.json | 18 +- .../root/usr/share/rpcd/ucode/luci.fancontrol | 223 ++++++++ {modemdata => luci-app-modemdata}/LICENSE | 0 .../Makefile | 2 +- .../luci-static/resources/icons/ctime_new.svg | 0 .../resources/icons/mobile-signal-000-000.svg | 0 .../resources/icons/mobile-signal-000-020.svg | 0 .../resources/icons/mobile-signal-020-040.svg | 0 .../resources/icons/mobile-signal-040-060.svg | 0 .../resources/icons/mobile-signal-060-080.svg | 0 .../resources/icons/mobile-signal-080-100.svg | 0 .../luci-static/resources/icons/mybts.svg | 0 .../luci-static/resources/icons/sim_new.svg | 0 .../luci-static/resources/icons/termometr.svg | 0 .../resources/view/modem/modemconfig.js | 0 .../resources/view/modem/modemdefine.js | 0 .../resources/view/modem/modemdiagnostics.js | 0 .../resources/view/modem/modempreview.js | 0 .../resources/view/modem/modemupdate.js | 0 .../po/cs/luci-app-modemdata.po | 0 .../po/da/luci-app-modemdata.po | 0 .../po/de/luci-app-modemdata.po | 0 .../po/es/luci-app-modemdata.po | 0 .../po/fr/luci-app-modemdata.po | 0 .../po/it/luci-app-modemdata.po | 0 .../po/ja/luci-app-modemdata.po | 0 .../po/ko/luci-app-modemdata.po | 0 .../po/nl/luci-app-modemdata.po | 0 .../po/pl/luci-app-modemdata.po | 0 .../po/pt_BR/luci-app-modemdata.po | 0 .../po/ru/luci-app-modemdata.po | 0 .../po/tr/luci-app-modemdata.po | 0 .../po/vi/luci-app-modemdata.po | 0 luci-app-modemdata/po/zh-cn | 1 + .../po/zh_Hans/modemdata.po | 0 .../po/zh_Hant/modemdata.po | 0 .../root/etc/config/defmodems | 0 .../root/etc/config/modemdata | 0 .../root/etc/modem/modemlist.json | 0 .../root/etc/uci-defaults/setup_modemdata.sh | 0 .../root/usr/bin/md_modemmanager | 0 .../root/usr/bin/md_serial_ecm | 0 .../root/usr/bin/md_uqmi | 0 .../share/luci/menu.d/luci-app-modemdata.json | 0 .../share/rpcd/acl.d/luci-app-modemdata.json | 0 modemdata/README.md | 328 ----------- modemdata/luci-app-modemdata/LICENSE | 21 - 59 files changed, 1601 insertions(+), 1280 deletions(-) delete mode 100644 luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.css delete mode 100644 luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js create mode 100644 luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol/log.js create mode 100644 luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol/overview.js create mode 100644 luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol/settings.js create mode 100644 luci-app-fancontrol/po/templates/fancontrol.pot create mode 100644 luci-app-fancontrol/root/etc/config/fancontrol create mode 100755 luci-app-fancontrol/root/etc/init.d/fancontrol create mode 100755 luci-app-fancontrol/root/usr/sbin/fancontrold create mode 100644 luci-app-fancontrol/root/usr/share/rpcd/ucode/luci.fancontrol rename {modemdata => luci-app-modemdata}/LICENSE (100%) rename {modemdata/luci-app-modemdata => luci-app-modemdata}/Makefile (96%) rename {modemdata/luci-app-modemdata => luci-app-modemdata}/htdocs/luci-static/resources/icons/ctime_new.svg (100%) rename {modemdata/luci-app-modemdata => luci-app-modemdata}/htdocs/luci-static/resources/icons/mobile-signal-000-000.svg (100%) rename {modemdata/luci-app-modemdata => luci-app-modemdata}/htdocs/luci-static/resources/icons/mobile-signal-000-020.svg (100%) rename {modemdata/luci-app-modemdata => luci-app-modemdata}/htdocs/luci-static/resources/icons/mobile-signal-020-040.svg (100%) rename {modemdata/luci-app-modemdata => luci-app-modemdata}/htdocs/luci-static/resources/icons/mobile-signal-040-060.svg (100%) rename {modemdata/luci-app-modemdata => luci-app-modemdata}/htdocs/luci-static/resources/icons/mobile-signal-060-080.svg (100%) rename {modemdata/luci-app-modemdata => luci-app-modemdata}/htdocs/luci-static/resources/icons/mobile-signal-080-100.svg (100%) rename {modemdata/luci-app-modemdata => luci-app-modemdata}/htdocs/luci-static/resources/icons/mybts.svg (100%) rename {modemdata/luci-app-modemdata => luci-app-modemdata}/htdocs/luci-static/resources/icons/sim_new.svg (100%) rename {modemdata/luci-app-modemdata => luci-app-modemdata}/htdocs/luci-static/resources/icons/termometr.svg (100%) rename {modemdata/luci-app-modemdata => luci-app-modemdata}/htdocs/luci-static/resources/view/modem/modemconfig.js (100%) rename {modemdata/luci-app-modemdata => luci-app-modemdata}/htdocs/luci-static/resources/view/modem/modemdefine.js (100%) rename {modemdata/luci-app-modemdata => luci-app-modemdata}/htdocs/luci-static/resources/view/modem/modemdiagnostics.js (100%) rename {modemdata/luci-app-modemdata => luci-app-modemdata}/htdocs/luci-static/resources/view/modem/modempreview.js (100%) rename {modemdata/luci-app-modemdata => luci-app-modemdata}/htdocs/luci-static/resources/view/modem/modemupdate.js (100%) rename {modemdata/luci-app-modemdata => luci-app-modemdata}/po/cs/luci-app-modemdata.po (100%) rename {modemdata/luci-app-modemdata => luci-app-modemdata}/po/da/luci-app-modemdata.po (100%) rename {modemdata/luci-app-modemdata => luci-app-modemdata}/po/de/luci-app-modemdata.po (100%) rename {modemdata/luci-app-modemdata => luci-app-modemdata}/po/es/luci-app-modemdata.po (100%) rename {modemdata/luci-app-modemdata => luci-app-modemdata}/po/fr/luci-app-modemdata.po (100%) rename {modemdata/luci-app-modemdata => luci-app-modemdata}/po/it/luci-app-modemdata.po (100%) rename {modemdata/luci-app-modemdata => luci-app-modemdata}/po/ja/luci-app-modemdata.po (100%) rename {modemdata/luci-app-modemdata => luci-app-modemdata}/po/ko/luci-app-modemdata.po (100%) rename {modemdata/luci-app-modemdata => luci-app-modemdata}/po/nl/luci-app-modemdata.po (100%) rename {modemdata/luci-app-modemdata => luci-app-modemdata}/po/pl/luci-app-modemdata.po (100%) rename {modemdata/luci-app-modemdata => luci-app-modemdata}/po/pt_BR/luci-app-modemdata.po (100%) rename {modemdata/luci-app-modemdata => luci-app-modemdata}/po/ru/luci-app-modemdata.po (100%) rename {modemdata/luci-app-modemdata => luci-app-modemdata}/po/tr/luci-app-modemdata.po (100%) rename {modemdata/luci-app-modemdata => luci-app-modemdata}/po/vi/luci-app-modemdata.po (100%) create mode 120000 luci-app-modemdata/po/zh-cn rename {modemdata/luci-app-modemdata => luci-app-modemdata}/po/zh_Hans/modemdata.po (100%) rename {modemdata/luci-app-modemdata => luci-app-modemdata}/po/zh_Hant/modemdata.po (100%) rename {modemdata/luci-app-modemdata => luci-app-modemdata}/root/etc/config/defmodems (100%) rename {modemdata/luci-app-modemdata => luci-app-modemdata}/root/etc/config/modemdata (100%) rename {modemdata/luci-app-modemdata => luci-app-modemdata}/root/etc/modem/modemlist.json (100%) rename {modemdata/luci-app-modemdata => luci-app-modemdata}/root/etc/uci-defaults/setup_modemdata.sh (100%) rename {modemdata/luci-app-modemdata => luci-app-modemdata}/root/usr/bin/md_modemmanager (100%) mode change 100644 => 100755 rename {modemdata/luci-app-modemdata => luci-app-modemdata}/root/usr/bin/md_serial_ecm (100%) mode change 100644 => 100755 rename {modemdata/luci-app-modemdata => luci-app-modemdata}/root/usr/bin/md_uqmi (100%) mode change 100644 => 100755 rename {modemdata/luci-app-modemdata => luci-app-modemdata}/root/usr/share/luci/menu.d/luci-app-modemdata.json (100%) rename {modemdata/luci-app-modemdata => luci-app-modemdata}/root/usr/share/rpcd/acl.d/luci-app-modemdata.json (100%) delete mode 100644 modemdata/README.md delete mode 100644 modemdata/luci-app-modemdata/LICENSE diff --git a/luci-app-fancontrol/Makefile b/luci-app-fancontrol/Makefile index b187fdf7d..ba484f4b0 100644 --- a/luci-app-fancontrol/Makefile +++ b/luci-app-fancontrol/Makefile @@ -1,21 +1,48 @@ -# -# Copyright (C) 2008-2014 The LuCI Team -# -# This is free software, licensed under the Apache License, Version 2.0 . -# - include $(TOPDIR)/rules.mk -LUCI_TITLE:=LuCI support for the generic PWM fan controller -LUCI_DEPENDS:=+luci-base +fancontrol +PKG_NAME:=luci-app-fancontrol +PKG_VERSION:=1.0.0 +PKG_RELEASE:=5 +PKG_LICENSE:=Apache-2.0 +PKG_MAINTAINER:=Dante + +LUCI_TITLE:=LuCI support for BPI-R4 PWM Fan Control +LUCI_DEPENDS:=+luci-base +ucode +ucode-mod-fs +ucode-mod-uci +ucode-mod-ubus LUCI_PKGARCH:=all -PKG_LICENSE:=Apache-2.0 -PKG_MAINTAINER:=JiaY-shi +define Package/$(PKG_NAME)/conffiles +/etc/config/fancontrol +endef -PKG_NAME:=luci-app-fancontrol -PKG_VERSION:=2.2.0 -PKG_RELEASE:=4 +define Package/$(PKG_NAME)/postinst +#!/bin/sh +[ -n "$${IPKG_INSTROOT}" ] || { + chmod 755 /etc/init.d/fancontrol /usr/sbin/fancontrold 2>/dev/null + /etc/init.d/rpcd restart + /etc/init.d/fancontrol enable + /etc/init.d/fancontrol restart +} +exit 0 +endef + +define Package/$(PKG_NAME)/prerm +#!/bin/sh +if [ -z "$${IPKG_INSTROOT}" ]; then + /etc/init.d/fancontrol stop + /etc/init.d/fancontrol disable +fi +exit 0 +endef + +define Package/$(PKG_NAME)/postrm +#!/bin/sh +if [ -z "$${IPKG_INSTROOT}" ]; then + rm -f /var/log/fancontrol.log* + rm -f /var/run/fancontrol.* +fi +exit 0 +endef include $(TOPDIR)/feeds/luci/luci.mk + # call BuildPackage - OpenWrt buildroot signature diff --git a/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.css b/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.css deleted file mode 100644 index fa77bb072..000000000 --- a/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.css +++ /dev/null @@ -1,171 +0,0 @@ -.fancontrol-runtime { - margin-bottom: 1.5rem; -} - -.fancontrol-status { - width: 100%; - max-width: 1200px; - overflow: hidden; - border: 1px solid rgba(127, 127, 127, .28); - border-radius: 6px; -} - -.fancontrol-status-head { - display: flex; - align-items: center; - justify-content: space-between; - min-height: 0; - padding: .4rem .7rem; - background: rgba(127, 127, 127, .08); - border-bottom: 1px solid rgba(127, 127, 127, .22); -} - -.fancontrol-status-title { - font-size: .88rem; - font-weight: 600; -} - -.fancontrol-metrics { - display: grid; - grid-template-columns: repeat(3, minmax(0, 1fr)); -} - -.fancontrol-metric { - min-width: 0; - min-height: 0; - padding: .55rem .7rem; - border-right: 1px solid rgba(127, 127, 127, .2); -} - -.fancontrol-metric:last-child { - border-right: 0; -} - -.fancontrol-metric-label, -.fancontrol-detail-label { - display: block; - color: var(--text-color-medium, inherit); - font-size: .72rem; -} - -.fancontrol-metric-reading { - display: flex; - align-items: baseline; - gap: .3rem; - min-height: 0; - margin-top: .12rem; - white-space: nowrap; -} - -.fancontrol-metric-value { - font-size: 1.18rem; - font-weight: 600; - line-height: 1.3; -} - -.fancontrol-metric-unit { - overflow: hidden; - color: var(--text-color-medium, inherit); - font-size: .72rem; - text-overflow: ellipsis; -} - -.fancontrol-chart-wrap { - height: 72px; - margin-top: .4rem; -} - -.fancontrol-chart { - display: block; - width: 100%; - height: 100%; - color: var(--text-color-medium, #666); - background: rgba(127, 127, 127, .025); - border: 1px solid rgba(127, 127, 127, .24); -} - -.fancontrol-chart-temperature { - --fancontrol-chart-line: #c74b45; - --fancontrol-chart-fill: rgba(199, 75, 69, .2); -} - -.fancontrol-chart-pwm { - --fancontrol-chart-line: #1976d2; - --fancontrol-chart-fill: rgba(25, 118, 210, .2); -} - -.fancontrol-chart-rpm { - --fancontrol-chart-line: #2f8a57; - --fancontrol-chart-fill: rgba(47, 138, 87, .2); -} - -.fancontrol-details { - display: grid; - grid-template-columns: repeat(3, minmax(0, 1fr)); - gap: .3rem 1rem; - padding: .42rem .7rem; - background: rgba(127, 127, 127, .04); - border-top: 1px solid rgba(127, 127, 127, .2); -} - -.fancontrol-detail { - display: grid; - grid-template-columns: minmax(7rem, .34fr) minmax(0, 1fr); - align-items: baseline; - gap: .45rem; - min-width: 0; -} - -.fancontrol-detail-value { - min-width: 0; - overflow-wrap: anywhere; -} - -.fancontrol-ok { - color: var(--success-color-medium, #00884a); -} - -@media (max-width: 700px) { - .fancontrol-metrics { - grid-template-columns: repeat(2, minmax(0, 1fr)); - } - - .fancontrol-metric:nth-child(2) { - border-right: 0; - } - - .fancontrol-metric:last-child { - grid-column: 1 / -1; - border-top: 1px solid rgba(127, 127, 127, .2); - } - - .fancontrol-details { - grid-template-columns: 1fr; - } - -} - -@media (max-width: 420px) { - .fancontrol-metrics { - grid-template-columns: 1fr; - } - - .fancontrol-metric, - .fancontrol-metric:nth-child(2) { - border-right: 0; - border-top: 1px solid rgba(127, 127, 127, .2); - } - - .fancontrol-metric:first-child { - border-top: 0; - } - - .fancontrol-metric:last-child { - grid-column: auto; - } - - .fancontrol-detail { - grid-template-columns: 1fr; - gap: .15rem; - } -} diff --git a/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js b/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js deleted file mode 100644 index c270c4cdd..000000000 --- a/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js +++ /dev/null @@ -1,463 +0,0 @@ -'use strict'; -'require dom'; -'require form'; -'require fs'; -'require poll'; -'require rpc'; -'require uci'; -'require view'; - -const callServiceList = rpc.declare({ - object: 'service', - method: 'list', - params: [ 'name' ], - expect: { '': {} } -}); - -const HISTORY_WINDOW_MS = 2 * 60 * 1000; -const TIME_GRID_INTERVAL_MS = 10 * 1000; -const TIME_LABEL_INTERVAL_MS = 30 * 1000; -const VALUE_GRID_DIVISIONS = 4; - -function serviceRunning(result) { - const instances = result?.fancontrol?.instances || {}; - - return Object.keys(instances).some(function(name) { - return instances[name].running === true; - }); -} - -function parseStatus(text) { - const status = {}; - - String(text || '').split(/\n/).forEach(function(line) { - const separator = line.indexOf('='); - - if (separator > 0) - status[line.substring(0, separator)] = line.substring(separator + 1); - }); - - return status; -} - -function validStatusNumber(value) { - const number = Number(value); - - return Number.isFinite(number) && number >= 0 ? number : null; -} - -function appendHistory(history, running, status) { - const now = Date.now(); - const temperature = validStatusNumber(status.temperature_mc); - const pwm = validStatusNumber(status.pwm); - const rpm = validStatusNumber(status.rpm); - - history.push({ - time: now, - temperature: running && temperature != null ? temperature / 1000 : null, - pwm: running && pwm != null ? pwm * 100 / 255 : null, - rpm: running ? rpm : null - }); - - while (history.length && history[0].time < now - HISTORY_WINDOW_MS) - history.shift(); -} - -function chartScale(history, key, minimumMax, step) { - let maximum = minimumMax; - - history.forEach(function(sample) { - if (sample[key] != null) - maximum = Math.max(maximum, sample[key]); - }); - - return Math.ceil(maximum / step) * step; -} - -function drawChart(canvas, history, key, options) { - const style = window.getComputedStyle(canvas); - const width = Math.max(canvas.clientWidth, 1); - const height = Math.max(canvas.clientHeight, 1); - const ratio = Math.min(window.devicePixelRatio || 1, 2); - const context = canvas.getContext('2d'); - const padding = { left: 28, right: 5, top: 6, bottom: 14 }; - const plotWidth = Math.max(width - padding.left - padding.right, 1); - const plotHeight = Math.max(height - padding.top - padding.bottom, 1); - const plotBottom = padding.top + plotHeight; - const now = history.length ? history[history.length - 1].time : Date.now(); - const start = now - HISTORY_WINDOW_MS; - const maximum = chartScale(history, key, options.minimumMax, options.step); - const lineColor = style.getPropertyValue('--fancontrol-chart-line').trim() || '#1976d2'; - const fillColor = style.getPropertyValue('--fancontrol-chart-fill').trim() || 'rgba(25, 118, 210, .2)'; - const labelColor = style.color || '#666'; - const gridColor = 'rgba(127, 127, 127, .2)'; - let segment = []; - - canvas.width = Math.round(width * ratio); - canvas.height = Math.round(height * ratio); - context.setTransform(ratio, 0, 0, ratio, 0, 0); - context.clearRect(0, 0, width, height); - - context.strokeStyle = gridColor; - context.lineWidth = 1; - context.beginPath(); - for (let elapsed = 0; elapsed <= HISTORY_WINDOW_MS; elapsed += TIME_GRID_INTERVAL_MS) { - const x = padding.left + plotWidth * elapsed / HISTORY_WINDOW_MS; - - context.moveTo(x, padding.top); - context.lineTo(x, plotBottom); - } - for (let division = 0; division <= VALUE_GRID_DIVISIONS; division++) { - const y = padding.top + plotHeight * division / VALUE_GRID_DIVISIONS; - - context.moveTo(padding.left, y); - context.lineTo(padding.left + plotWidth, y); - } - context.stroke(); - - context.fillStyle = labelColor; - context.font = '9px sans-serif'; - context.textAlign = 'right'; - context.textBaseline = 'top'; - context.fillText(options.format(maximum), padding.left - 4, padding.top - 1); - context.textBaseline = 'bottom'; - context.fillText(options.format(0), padding.left - 4, plotBottom + 1); - context.textBaseline = 'middle'; - context.fillText(options.format(maximum / 2), padding.left - 4, - padding.top + plotHeight / 2); - - context.textBaseline = 'bottom'; - for (let elapsed = 0; elapsed <= HISTORY_WINDOW_MS; elapsed += TIME_LABEL_INTERVAL_MS) { - const x = padding.left + plotWidth * elapsed / HISTORY_WINDOW_MS; - const remaining = (HISTORY_WINDOW_MS - elapsed) / 1000; - - context.textAlign = elapsed === 0 ? 'left' : - elapsed === HISTORY_WINDOW_MS ? 'right' : 'center'; - context.fillText(remaining ? '-%ds'.format(remaining) : '0', x, height); - } - - function point(sample) { - return { - x: padding.left + (sample.time - start) / HISTORY_WINDOW_MS * plotWidth, - y: plotBottom - sample[key] / maximum * plotHeight - }; - } - - function drawSegment() { - if (!segment.length) - return; - - if (segment.length > 1) { - context.beginPath(); - context.moveTo(segment[0].x, plotBottom); - segment.forEach(function(item) { context.lineTo(item.x, item.y); }); - context.lineTo(segment[segment.length - 1].x, plotBottom); - context.closePath(); - context.fillStyle = fillColor; - context.fill(); - - context.beginPath(); - segment.forEach(function(item, index) { - if (index) - context.lineTo(item.x, item.y); - else - context.moveTo(item.x, item.y); - }); - context.strokeStyle = lineColor; - context.lineWidth = 1.5; - context.lineJoin = 'round'; - context.stroke(); - } else { - context.beginPath(); - context.arc(segment[0].x, segment[0].y, 2, 0, Math.PI * 2); - context.fillStyle = lineColor; - context.fill(); - } - - segment = []; - } - - history.forEach(function(sample) { - if (sample[key] == null) { - drawSegment(); - return; - } - - segment.push(point(sample)); - }); - drawSegment(); -} - -function statusMetric(title, value, unit, chartClass) { - const canvas = E('canvas', { - 'class': 'fancontrol-chart fancontrol-chart-%s'.format(chartClass), - 'role': 'img', - 'aria-label': title - }); - const children = [ - E('span', { 'class': 'fancontrol-metric-label' }, title), - E('div', { 'class': 'fancontrol-metric-reading' }, [ - E('span', { 'class': 'fancontrol-metric-value' }, value), - unit ? E('span', { 'class': 'fancontrol-metric-unit' }, unit) : '' - ]), - E('div', { 'class': 'fancontrol-chart-wrap' }, canvas) - ]; - - return { - node: E('div', { 'class': 'fancontrol-metric' }, children), - canvas: canvas - }; -} - -function statusDetail(title, value) { - return E('div', { 'class': 'fancontrol-detail' }, [ - E('span', { 'class': 'fancontrol-detail-label' }, title), - E('span', { 'class': 'fancontrol-detail-value' }, value) - ]); -} - -function renderStatus(node, running, status, history) { - const temp = Number(status.temperature_mc); - const pwm = Number(status.pwm); - const rpm = Number(status.rpm); - const tempValid = Number.isFinite(temp) && temp >= 0; - const pwmValid = Number.isFinite(pwm) && pwm >= 0; - const rpmValid = Number.isFinite(rpm) && rpm >= 0; - const pwmPercent = pwmValid ? Math.round(pwm * 100 / 255) : null; - const fault = status.error - ? E('span', { 'class': 'label warning' }, status.error) - : E('span', { 'class': 'fancontrol-ok' }, _('None')); - const temperatureMetric = statusMetric(_('Temperature'), - tempValid ? '%.1f'.format(temp / 1000) : '-', tempValid ? '°C' : '', 'temperature'); - const pwmMetric = statusMetric(_('PWM output'), pwmValid ? String(pwm) : '-', - pwmValid ? '/ 255 · %d%%'.format(pwmPercent) : '', 'pwm'); - const rpmMetric = statusMetric(_('Fan speed'), rpmValid ? String(rpm) : '-', - rpmValid ? 'RPM' : '', 'rpm'); - - dom.content(node, E('div', { 'class': 'fancontrol-status' }, [ - E('div', { 'class': 'fancontrol-status-head' }, [ - E('span', { 'class': 'fancontrol-status-title' }, _('Runtime Status')), - E('span', { 'class': running ? 'label success' : 'label warning' }, - running ? _('Running') : _('Stopped')) - ]), - E('div', { 'class': 'fancontrol-metrics' }, [ - temperatureMetric.node, - pwmMetric.node, - rpmMetric.node - ]), - E('div', { 'class': 'fancontrol-details' }, [ - statusDetail(_('Temperature input'), status.thermal_file || '-'), - statusDetail(_('PWM device'), status.fan_file || '-'), - statusDetail(_('Fault'), fault) - ]) - ])); - - drawChart(temperatureMetric.canvas, history, 'temperature', { - minimumMax: 100, - step: 10, - format: function(value) { return '%d°'.format(value); } - }); - drawChart(pwmMetric.canvas, history, 'pwm', { - minimumMax: 100, - step: 100, - format: function(value) { return '%d%%'.format(value); } - }); - drawChart(rpmMetric.canvas, history, 'rpm', { - minimumMax: 1000, - step: 500, - format: function(value) { return String(value); } - }); -} - -function validateAutoPath(sectionId, value) { - if (value === 'auto' || value?.startsWith('/')) - return true; - - return _('Enter "auto" or an absolute sysfs path.'); -} - -return view.extend({ - load() { - return uci.load('fancontrol'); - }, - - render() { - let m, s, o; - let startTemp, fullTemp, startSpeed, maxSpeed, kickSpeed, kickMs; - - m = new form.Map('fancontrol', _('Fan Control'), - _('Configure a continuous PWM curve based on device temperature. Automatic hardware detection is suitable for most devices.')); - - s = m.section(form.TypedSection, 'fancontrol', _('Runtime Status')); - s.anonymous = true; - s.addremove = false; - s.render = function() { - const history = []; - const node = E('div', { 'class': 'cbi-section fancontrol-runtime' }, [ - E('link', { - 'rel': 'stylesheet', - 'href': L.resource('view/fancontrol.css') - }), - E('div', { 'class': 'cbi-section-node' }, _('Collecting data...')) - ]); - const statusNode = node.lastElementChild; - - poll.add(function() { - return Promise.all([ - L.resolveDefault(callServiceList('fancontrol'), {}), - L.resolveDefault(fs.read_direct('/var/run/fancontrol.status'), '') - ]).then(function(result) { - const running = serviceRunning(result[0]); - const status = parseStatus(result[1]); - - appendHistory(history, running, status); - renderStatus(statusNode, running, status, history); - }); - }, 5); - - return node; - }; - - s = m.section(form.NamedSection, 'settings', 'fancontrol', _('Settings')); - s.addremove = false; - s.tab('curve', _('Temperature Curve'), - _('Controls when the fan starts and how its speed rises with temperature.')); - s.tab('hardware', _('Hardware'), - _('Automatic detection works on most devices. Select explicit sysfs entries only when necessary.')); - s.tab('safety', _('Safety'), - _('Controls startup assistance and fail-safe behavior. Full-speed safety defaults are recommended.')); - - o = s.taboption('curve', form.Flag, 'enabled', _('Enable')); - o.default = o.disabled; - o.rmempty = false; - o.description = _('Run the controller and apply this temperature curve.'); - - startTemp = s.taboption('curve', form.Value, 'start_temp', _('Start temperature')); - startTemp.default = '45'; - startTemp.datatype = 'range(-40,200)'; - startTemp.rmempty = false; - startTemp.description = _('The fan remains off below this temperature and starts at the minimum running PWM when it is reached. Unit: °C.'); - - fullTemp = s.taboption('curve', form.Value, 'full_speed_temp', _('Full-speed temperature')); - fullTemp.default = '85'; - fullTemp.datatype = 'range(-39,250)'; - fullTemp.rmempty = false; - fullTemp.description = _('PWM rises linearly and reaches the configured maximum at this temperature. Unit: °C.'); - fullTemp.validate = function(sectionId, value) { - if (Number(value) <= Number(startTemp.formvalue(sectionId))) - return _('Full-speed temperature must be higher than start temperature.'); - return true; - }; - - o = s.taboption('curve', form.Value, 'hysteresis', _('Stop hysteresis')); - o.default = '3'; - o.datatype = 'range(0,50)'; - o.rmempty = false; - o.description = _('After starting, the fan stops only after temperature falls this many degrees below the start temperature. This prevents rapid on/off cycling.'); - - startSpeed = s.taboption('curve', form.Value, 'start_speed', _('Minimum running PWM')); - startSpeed.default = '64'; - startSpeed.datatype = 'range(1,255)'; - startSpeed.rmempty = false; - startSpeed.description = _('Lowest PWM used while the fan is running. Increase it if the fan stalls or cannot maintain rotation. Range: 1–255.'); - - maxSpeed = s.taboption('curve', form.Value, 'max_speed', _('Maximum PWM')); - maxSpeed.default = '255'; - maxSpeed.datatype = 'range(1,255)'; - maxSpeed.rmempty = false; - maxSpeed.description = _('Highest PWM allowed by the curve. A value of 255 means 100% duty cycle.'); - maxSpeed.validate = function(sectionId, value) { - if (Number(value) < Number(startSpeed.formvalue(sectionId))) - return _('Maximum PWM must not be lower than minimum running PWM.'); - return true; - }; - - o = s.taboption('curve', form.Value, 'interval', _('Polling interval')); - o.default = '5'; - o.datatype = 'range(1,300)'; - o.rmempty = false; - o.description = _('How often the temperature is read and the PWM output is updated. Unit: seconds.'); - - o = s.taboption('hardware', form.Value, 'thermal_file', _('Temperature input')); - o.default = 'auto'; - o.rmempty = false; - o.validate = validateAutoPath; - o.description = _('Use "auto" to select a CPU or SoC thermal sensor, or enter an absolute thermal zone or hwmon tempN_input path.'); - - o = s.taboption('hardware', form.Value, 'thermal_zone', _('Thermal zone type')); - o.default = 'auto'; - o.rmempty = false; - o.description = _('Preferred thermal zone type when temperature input is automatic, for example cpu_top_thermal. Leave as "auto" unless several sensors are available.'); - - o = s.taboption('hardware', form.Value, 'fan_file', _('PWM output')); - o.default = 'auto'; - o.rmempty = false; - o.validate = validateAutoPath; - o.description = _('Use "auto" to select a writable hwmon pwmN output, or enter its absolute path. cooling_device cur_state is not a PWM output.'); - - o = s.taboption('hardware', form.Value, 'fan_hwmon', _('Hwmon device name')); - o.default = 'auto'; - o.rmempty = false; - o.description = _('Preferred hwmon name when PWM output is automatic. The pwmfan driver is preferred by default.'); - - o = s.taboption('hardware', form.Value, 'enable_file', _('PWM mode control')); - o.default = 'auto'; - o.rmempty = false; - o.validate = validateAutoPath; - o.description = _('Optional pwmN_enable path used to select manual PWM mode. With "auto", the matching control is used when available.'); - - o = s.taboption('hardware', form.Value, 'cooling_device', _('Kernel cooling device')); - o.default = 'auto'; - o.value('auto'); - o.value('none'); - o.rmempty = false; - o.validate = function(sectionId, value) { - return value === 'none' ? true : validateAutoPath(sectionId, value); - }; - o.description = _('Use auto for a single pwm-fan device, an explicit cooling_device path for multiple fans, or none when kernel fan control is already handled elsewhere.'); - - o = s.taboption('hardware', form.Value, 'temp_div', _('Temperature divisor')); - o.default = '1000'; - o.value('1'); - o.value('1000'); - o.datatype = 'range(1,1000000)'; - o.rmempty = false; - o.description = _('Divides the raw sensor value before control calculations. Standard Linux temperature inputs use 1000; sensors reporting whole degrees use 1.'); - - kickSpeed = s.taboption('safety', form.Value, 'kick_speed', _('Start kick PWM')); - kickSpeed.default = '255'; - kickSpeed.datatype = 'range(0,255)'; - kickSpeed.rmempty = false; - kickSpeed.description = _('Brief PWM applied when a stopped fan starts. It helps fans that cannot start reliably at low duty. Set to 0 together with a zero duration to disable.'); - - kickMs = s.taboption('safety', form.Value, 'kick_ms', _('Start kick duration')); - kickMs.default = '500'; - kickMs.datatype = 'range(0,10000)'; - kickMs.rmempty = false; - kickMs.description = _('How long the startup kick is applied. Unit: milliseconds.'); - kickMs.validate = function(sectionId, value) { - if (Number(value) > 0 && Number(kickSpeed.formvalue(sectionId)) === 0) - return _('Start kick PWM must be non-zero when a kick duration is configured.'); - return true; - }; - - o = s.taboption('safety', form.Value, 'fail_safe_speed', _('Sensor failure PWM')); - o.default = '255'; - o.datatype = 'range(0,255)'; - o.rmempty = false; - o.description = _('PWM used when the temperature sensor cannot be read. Keeping 255 is the safest choice.'); - - o = s.taboption('safety', form.Value, 'exit_speed', _('Service exit PWM')); - o.default = '255'; - o.datatype = 'range(0,255)'; - o.rmempty = false; - o.description = _('PWM written when the service stops. Keeping the fan at full speed avoids losing cooling after an unexpected exit.'); - - o = s.taboption('safety', form.Flag, 'debug', _('Debug logging')); - o.default = o.disabled; - o.rmempty = false; - o.description = _('Write temperature, PWM and RPM values to the system log at every polling interval.'); - - return m.render(); - } -}); diff --git a/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol/log.js b/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol/log.js new file mode 100644 index 000000000..fc9842965 --- /dev/null +++ b/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol/log.js @@ -0,0 +1,88 @@ +'use strict'; +'require view'; +'require rpc'; +'require poll'; +'require ui'; + +var callGetLogs = rpc.declare({ + object: 'luci.fancontrol', + method: 'get_logs', + params: [ 'lines' ] +}); + +var callClearLogs = rpc.declare({ + object: 'luci.fancontrol', + method: 'clear_logs' +}); + +return view.extend({ + handleSaveApply: null, + handleSave: null, + handleReset: null, + + render: function() { + var area = E('textarea', { + id: 'fancontrol_log_content', + style: 'width:100%; height:460px; background:#f9f9f9; color:#222; padding:12px; border:1px solid #ccc; border-radius:4px; font-family:monospace; font-size:12px; line-height:1.5; overflow-y:auto; white-space:pre-wrap; word-break:break-all;', + readonly: 'readonly' + }, _('正在加载日志...')); + + var btnRefresh = E('button', { + 'class': 'btn cbi-button cbi-button-action', + 'style': 'margin-right:10px;', + click: ui.createHandlerFn(this, 'updateLogs') + }, _('刷新日志')); + + var btnClear = E('button', { + 'class': 'btn cbi-button cbi-button-reset', + click: ui.createHandlerFn(this, 'handleClearLogs') + }, _('清空日志')); + + poll.add(L.bind(this.updateLogs, this), 5); + + // 初始加载一次日志 + this.updateLogs(); + + return E('div', { 'class': 'cbi-map' }, [ + E('h2', {}, _('风扇控制') + ' - ' + _('运行日志')), + E('div', { 'class': 'cbi-map-descr' }, _('实时展示温控守护进程的调速动作、温度变化与自检日志。')), + E('fieldset', { 'class': 'cbi-section' }, [ + E('legend', {}, _('系统日志')), + E('div', { 'class': 'cbi-section-node' }, [ + E('div', { 'style': 'margin-bottom:10px;' }, [ btnRefresh, btnClear ]), + area + ]) + ]) + ]); + }, + + updateLogs: function() { + return callGetLogs(100).then(function(res) { + var area = document.getElementById('fancontrol_log_content'); + if (area) { + if (res && res.logs && res.logs.length > 0) { + area.value = res.logs.join('\n'); + area.scrollTop = area.scrollHeight; + } else { + area.value = _('暂无日志记录。'); + } + } + }).catch(function(err) { + var area = document.getElementById('fancontrol_log_content'); + if (area) area.value = _('读取日志失败: %s').format(err.message); + }); + }, + + handleClearLogs: function() { + return callClearLogs().then(L.bind(function(res) { + if (res && res.success) { + ui.addNotification(null, E('p', _('日志已成功清空。')), 'info'); + this.updateLogs(); + } else { + ui.addNotification(null, E('p', _('清空日志失败。')), 'error'); + } + }, this)).catch(function(err) { + ui.addNotification(null, E('p', _('RPC 请求失败: %s').format(err.message)), 'error'); + }); + } +}); diff --git a/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol/overview.js b/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol/overview.js new file mode 100644 index 000000000..7bee47baf --- /dev/null +++ b/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol/overview.js @@ -0,0 +1,329 @@ +'use strict'; +'require view'; +'require rpc'; +'require poll'; +'require ui'; +'require dom'; + +var callGetStatus = rpc.declare({ + object: 'luci.fancontrol', + method: 'get_status' +}); + +var callSetLevel = rpc.declare({ + object: 'luci.fancontrol', + method: 'set_level', + params: [ 'level' ] +}); + +var callServiceAction = rpc.declare({ + object: 'luci.fancontrol', + method: 'service', + params: [ 'action' ] +}); + +return view.extend({ + handleSaveApply: null, + handleSave: null, + handleReset: null, + + load: function() { + return callGetStatus(); + }, + + render: function(status) { + var container = E('div', { 'class': 'cbi-map' }, [ + E('h2', {}, _('风扇控制') + ' - ' + _('状态概览')), + E('div', { 'class': 'cbi-map-descr' }, _('实时监控香蕉派 BPI-R4 处理器温度与 PWM 风扇运行档位,提供硬件即时测试功能。')) + ]); + + // 运行状态卡片 + var statusSection = E('fieldset', { 'class': 'cbi-section' }, [ + E('legend', {}, _('运行状态')), + E('div', { 'class': 'cbi-section-node' }, [ + E('table', { 'class': 'table cbi-section-table' }, [ + E('tr', { 'class': 'tr' }, [ + E('td', { 'class': 'td left', 'width': '33%' }, _('服务状态')), + E('td', { 'class': 'td left' }, [ + E('span', { id: 'fan-service-state', style: 'font-weight:bold; margin-right:15px;' }, '-'), + E('button', { + id: 'fan-restart-btn', + 'class': 'btn cbi-button cbi-button-reload', + click: ui.createHandlerFn(this, 'handleServiceAction', 'restart') + }, _('重启服务')) + ]) + ]), + E('tr', { 'class': 'tr' }, [ + E('td', { 'class': 'td left' }, _('运行模式')), + E('td', { 'class': 'td left', 'id': 'fan-service-mode', 'style': 'font-weight:bold;' }, '-') + ]), + E('tr', { 'class': 'tr' }, [ + E('td', { 'class': 'td left' }, _('处理器温度')), + E('td', { 'class': 'td left', 'id': 'fan-cpu-temp', 'style': 'font-size:16px; font-weight:bold;' }, '-') + ]), + E('tr', { 'class': 'tr' }, [ + E('td', { 'class': 'td left' }, _('当前风扇档位')), + E('td', { 'class': 'td left', 'id': 'fan-cur-level', 'style': 'font-weight:bold;' }, '-') + ]), + E('tr', { 'class': 'tr' }, [ + E('td', { 'class': 'td left' }, _('脉宽占空比')), + E('td', { 'class': 'td left', 'id': 'fan-pwm-duty' }, '-') + ]) + ]) + ]) + ]); + container.appendChild(statusSection); + + // 当前策略配置卡片 + var policySection = E('fieldset', { 'class': 'cbi-section' }, [ + E('legend', {}, _('当前策略配置')), + E('div', { 'class': 'cbi-section-node' }, [ + E('table', { 'class': 'table cbi-section-table' }, [ + E('tr', { 'class': 'tr', id: 'policy-row-interval' }, [ + E('td', { 'class': 'td left', 'width': '33%' }, _('检测轮询周期')), + E('td', { 'class': 'td left', 'id': 'policy-interval' }, '-') + ]), + E('tr', { 'class': 'tr', id: 'policy-row-spin' }, [ + E('td', { 'class': 'td left' }, _('启动除尘自检')), + E('td', { 'class': 'td left', 'id': 'policy-start-spin' }, '-') + ]), + E('tr', { 'class': 'tr', id: 'policy-row-low' }, [ + E('td', { 'class': 'td left' }, _('低速触发阈值')), + E('td', { 'class': 'td left', 'id': 'policy-temp-low' }, '-') + ]), + E('tr', { 'class': 'tr', id: 'policy-row-med' }, [ + E('td', { 'class': 'td left' }, _('中速触发阈值')), + E('td', { 'class': 'td left', 'id': 'policy-temp-med' }, '-') + ]), + E('tr', { 'class': 'tr', id: 'policy-row-high' }, [ + E('td', { 'class': 'td left' }, _('全速触发阈值')), + E('td', { 'class': 'td left', 'id': 'policy-temp-high' }, '-') + ]), + E('tr', { 'class': 'tr', id: 'policy-row-hyst' }, [ + E('td', { 'class': 'td left' }, _('换挡回差温差')), + E('td', { 'class': 'td left', 'id': 'policy-hysteresis' }, '-') + ]), + E('tr', { 'class': 'tr', id: 'policy-row-manual' }, [ + E('td', { 'class': 'td left' }, _('预设运转档位')), + E('td', { 'class': 'td left', 'id': 'policy-manual-level' }, '-') + ]), + E('tr', { 'class': 'tr', id: 'policy-row-kernel' }, [ + E('td', { 'class': 'td left' }, _('控制驱动策略')), + E('td', { 'class': 'td left', 'id': 'policy-kernel-desc' }, _('系统内核设备树原生驱动接管')) + ]) + ]) + ]) + ]); + container.appendChild(policySection); + + // 快速测试面板 + var testSection = E('fieldset', { 'class': 'cbi-section' }, [ + E('legend', {}, _('硬件快速测试')), + E('div', { 'class': 'cbi-section-node' }, [ + E('p', { 'style': 'margin-bottom:15px; color:#666;' }, + _('点击按钮可即时下发档位进行硬件测试,测试结果立即生效并在状态栏回显。若服务处于运行中,后续温控检测将按设定规则恢复。')), + E('div', { 'style': 'display:flex; gap:10px; flex-wrap:wrap;' }, [ + E('button', { + 'class': 'btn cbi-button cbi-button-reset', + click: ui.createHandlerFn(this, 'handleSetLevel', 0) + }, _('停止风扇 (0 级)')), + E('button', { + 'class': 'btn cbi-button cbi-button-action', + click: ui.createHandlerFn(this, 'handleSetLevel', 1) + }, _('低速测试 (1 级)')), + E('button', { + 'class': 'btn cbi-button cbi-button-action', + click: ui.createHandlerFn(this, 'handleSetLevel', 2) + }, _('中速测试 (2 级)')), + E('button', { + 'class': 'btn cbi-button cbi-button-apply', + click: ui.createHandlerFn(this, 'handleSetLevel', 3) + }, _('全速测试 (3 级)')) + ]) + ]) + ]); + container.appendChild(testSection); + + // 初始刷新 + this.updateStatusView(status); + + // 启动定时轮询 (每 3 秒) + poll.add(L.bind(function() { + return callGetStatus().then(L.bind(function(res) { + this.updateStatusView(res); + }, this)); + }, this), 3); + + return container; + }, + + updateStatusView: function(status) { + if (!status) return; + + var stateEl = document.getElementById('fan-service-state'); + if (stateEl) { + if (status.state === 'managed') { + stateEl.textContent = _('运行中 [受控]') + (status.pid ? ' [PID: ' + status.pid + ']' : ''); + stateEl.style.color = '#2e7d32'; + } else if (status.state === 'unmanaged') { + stateEl.textContent = _('运行中 [非受控]') + (status.pid ? ' [PID: ' + status.pid + ']' : ''); + stateEl.style.color = '#ef6c00'; + } else { + stateEl.textContent = _('已停止'); + stateEl.style.color = '#c62828'; + } + } + + var modeEl = document.getElementById('fan-service-mode'); + if (modeEl) { + if (status.mode === 'auto') { + modeEl.textContent = _('阶梯温控模式'); + modeEl.style.color = '#1565c0'; + } else if (status.mode === 'manual') { + modeEl.textContent = _('手动定速模式'); + modeEl.style.color = '#6a1b9a'; + } else if (status.mode === 'kernel') { + modeEl.textContent = _('内核接管模式'); + modeEl.style.color = '#455a64'; + } else { + modeEl.textContent = status.mode || '-'; + modeEl.style.color = '#333'; + } + } + + var tempEl = document.getElementById('fan-cpu-temp'); + if (tempEl) { + var tempVal = parseFloat(status.temp) || 0; + tempEl.textContent = tempVal.toFixed(1) + ' °C'; + if (tempVal >= 75) { + tempEl.style.color = '#c62828'; + } else if (tempVal >= 60) { + tempEl.style.color = '#ef6c00'; + } else { + tempEl.style.color = '#2e7d32'; + } + } + + var levelEl = document.getElementById('fan-cur-level'); + if (levelEl) { + var lvl = parseInt(status.level) || 0; + var lvlName = ''; + switch (lvl) { + case 0: lvlName = _('0 级 [停转]'); break; + case 1: lvlName = _('1 级 [低速 31%]'); break; + case 2: lvlName = _('2 级 [中速 50%]'); break; + case 3: lvlName = _('3 级 [全速 100%]'); break; + default: lvlName = lvl + ' ' + _('级'); break; + } + levelEl.textContent = lvlName; + } + + var pwmEl = document.getElementById('fan-pwm-duty'); + if (pwmEl) { + var pwmVal = parseInt(status.pwm) || 0; + var pct = Math.round((pwmVal / 255) * 100); + pwmEl.textContent = pwmVal + ' / 255 (' + pct + '%)'; + } + + // 策略配置数据更新与模式显隐 + var curMode = status.mode || 'auto'; + var isAuto = (curMode === 'auto'); + var isManual = (curMode === 'manual'); + var isKernel = (curMode === 'kernel'); + + var setRowDisplay = function(id, visible) { + var el = document.getElementById(id); + if (el) el.style.display = visible ? '' : 'none'; + }; + + setRowDisplay('policy-row-interval', isAuto); + setRowDisplay('policy-row-spin', isAuto || isManual); + setRowDisplay('policy-row-low', isAuto); + setRowDisplay('policy-row-med', isAuto); + setRowDisplay('policy-row-high', isAuto); + setRowDisplay('policy-row-hyst', isAuto); + setRowDisplay('policy-row-manual', isManual); + setRowDisplay('policy-row-kernel', isKernel); + + var intervalValEl = document.getElementById('policy-interval'); + if (intervalValEl && status.interval) { + intervalValEl.textContent = status.interval + ' ' + _('秒'); + } + + var spinValEl = document.getElementById('policy-start-spin'); + if (spinValEl) { + spinValEl.textContent = (status.start_spin == 1) ? _('已开启 (开机全速 2 秒)') : _('未开启'); + } + + if (isAuto) { + var low = parseInt(status.temp_low) || 45; + var med = parseInt(status.temp_med) || 60; + var high = parseInt(status.temp_high) || 75; + var hyst = parseInt(status.hysteresis) || 3; + + var lowEl = document.getElementById('policy-temp-low'); + if (lowEl) lowEl.textContent = low + ' °C (' + _('低于 %d °C 停转').format(low - hyst) + ')'; + + var medEl = document.getElementById('policy-temp-med'); + if (medEl) medEl.textContent = med + ' °C (' + _('低于 %d °C 降回低速').format(med - hyst) + ')'; + + var highEl = document.getElementById('policy-temp-high'); + if (highEl) highEl.textContent = high + ' °C (' + _('低于 %d °C 降回中速').format(high - hyst) + ')'; + + var hystEl = document.getElementById('policy-hysteresis'); + if (hystEl) hystEl.textContent = hyst + ' °C (' + _('防临界点振荡') + ')'; + } else if (isManual) { + var mLevelEl = document.getElementById('policy-manual-level'); + if (mLevelEl) { + var ml = parseInt(status.manual_level) || 0; + var mlName = ''; + switch (ml) { + case 0: mlName = _('0 级 [停转 0%]'); break; + case 1: mlName = _('1 级 [低速 31%]'); break; + case 2: mlName = _('2 级 [中速 50%]'); break; + case 3: mlName = _('3 级 [全速 100%]'); break; + default: mlName = ml + ' ' + _('级'); break; + } + mLevelEl.textContent = mlName; + } + } + }, + + handleSetLevel: function(lvl) { + ui.showModal(null, [ + E('p', { 'class': 'spinning' }, _('正在下发测试档位: %d 级 ...').format(lvl)) + ]); + + return callSetLevel(lvl).then(L.bind(function(res) { + ui.hideModal(); + if (res && res.success) { + ui.addNotification(null, E('p', _('风扇测试档位下发成功: %d 级').format(lvl)), 'info'); + return callGetStatus().then(L.bind(this.updateStatusView, this)); + } else { + ui.addNotification(null, E('p', _('风扇档位下发失败,请检查硬件节点。')), 'error'); + } + }, this)).catch(function(err) { + ui.hideModal(); + ui.addNotification(null, E('p', _('RPC 请求失败: %s').format(err.message)), 'error'); + }); + }, + + handleServiceAction: function(action) { + ui.showModal(null, [ + E('p', { 'class': 'spinning' }, _('正在执行服务操作: %s ...').format(action)) + ]); + + return callServiceAction(action).then(L.bind(function(res) { + ui.hideModal(); + if (res && res.success) { + ui.addNotification(null, E('p', _('服务操作成功执行。')), 'info'); + return callGetStatus().then(L.bind(this.updateStatusView, this)); + } else { + ui.addNotification(null, E('p', _('服务操作执行失败。')), 'error'); + } + }, this)).catch(function(err) { + ui.hideModal(); + ui.addNotification(null, E('p', _('RPC 请求失败: %s').format(err.message)), 'error'); + }); + } +}); diff --git a/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol/settings.js b/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol/settings.js new file mode 100644 index 000000000..8cfd2a4d7 --- /dev/null +++ b/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol/settings.js @@ -0,0 +1,76 @@ +'use strict'; +'require view'; +'require form'; + +return view.extend({ + render: function() { + var m, s, o; + + m = new form.Map('fancontrol', _('风扇控制') + ' - ' + _('温控配置'), + _('配置香蕉派 BPI-R4 的 PWM 智能风扇调速策略与温度响应区间。')); + + s = m.section(form.NamedSection, 'global', 'fancontrol', _('参数设置')); + s.anonymous = false; + + o = s.option(form.Flag, 'enabled', _('启用服务'), + _('开启风扇温控自动守护服务。')); + o.rmempty = false; + o.default = '1'; + + o = s.option(form.ListValue, 'mode', _('工作模式'), + _('选择风扇的控制策略模式。')); + o.value('auto', _('阶梯温控模式')); + o.value('manual', _('手动定速模式')); + o.value('kernel', _('内核接管模式')); + o.default = 'auto'; + + o = s.option(form.Value, 'interval', _('检测周期 (秒)'), + _('温度轮询检测的时间间隔,推荐 3 到 10 秒。')); + o.datatype = 'range(1, 60)'; + o.default = '5'; + o.depends('mode', 'auto'); + + o = s.option(form.Flag, 'start_spin', _('启动自检清洁'), + _('服务启动时全速转动 2 秒,清洁积灰并确保轴承起步运转。')); + o.default = '1'; + o.depends('mode', 'auto'); + o.depends('mode', 'manual'); + + // 手动模式档位 + o = s.option(form.ListValue, 'manual_level', _('手动常开档位'), + _('指定风扇持续运转的固定档位。')); + o.value('0', _('0 级 [停转 0%]')); + o.value('1', _('1 级 [低速 31%]')); + o.value('2', _('2 级 [中速 50%]')); + o.value('3', _('3 级 [全速 100%]')); + o.default = '2'; + o.depends('mode', 'manual'); + + // 阶梯模式各档温度 + o = s.option(form.Value, 'temp_low', _('1 级低速触发温度 (°C)'), + _('温度达到此数值时启动 1 级低速 (约 31% 占空比),低于该值停转。')); + o.datatype = 'range(30, 90)'; + o.default = '45'; + o.depends('mode', 'auto'); + + o = s.option(form.Value, 'temp_med', _('2 级中速触发温度 (°C)'), + _('温度达到此数值时提升至 2 级中速 (约 50% 占空比)。')); + o.datatype = 'range(35, 100)'; + o.default = '60'; + o.depends('mode', 'auto'); + + o = s.option(form.Value, 'temp_high', _('3 级全速触发温度 (°C)'), + _('温度达到此数值时提升至 3 级全速 (100% 占空比)。')); + o.datatype = 'range(40, 110)'; + o.default = '75'; + o.depends('mode', 'auto'); + + o = s.option(form.Value, 'hysteresis', _('换挡回差温度 (°C)'), + _('降温换挡的滞后温差。例如 60°C 升至中速,需降至 57°C 才会降回低速,防止临界点频繁启停。')); + o.datatype = 'range(1, 10)'; + o.default = '3'; + o.depends('mode', 'auto'); + + return m.render(); + } +}); diff --git a/luci-app-fancontrol/po/templates/fancontrol.pot b/luci-app-fancontrol/po/templates/fancontrol.pot new file mode 100644 index 000000000..dc142fe80 --- /dev/null +++ b/luci-app-fancontrol/po/templates/fancontrol.pot @@ -0,0 +1,261 @@ +msgid "" +msgstr "Content-Type: text/plain; charset=UTF-8\n" + +msgid "风扇控制" +msgstr "" + +msgid "状态概览" +msgstr "" + +msgid "温控配置" +msgstr "" + +msgid "运行日志" +msgstr "" + +msgid "实时监控香蕉派 BPI-R4 处理器温度与 PWM 风扇运行档位,提供硬件即时测试功能。" +msgstr "" + +msgid "运行状态" +msgstr "" + +msgid "服务状态" +msgstr "" + +msgid "运行中 [受控]" +msgstr "" + +msgid "运行中 [非受控]" +msgstr "" + +msgid "已停止" +msgstr "" + +msgid "重启服务" +msgstr "" + +msgid "运行模式" +msgstr "" + +msgid "阶梯温控模式" +msgstr "" + +msgid "手动定速模式" +msgstr "" + +msgid "内核接管模式" +msgstr "" + +msgid "处理器温度" +msgstr "" + +msgid "当前风扇档位" +msgstr "" + +msgid "脉宽占空比" +msgstr "" + +msgid "0 级 [停转]" +msgstr "" + +msgid "1 级 [低速 31%]" +msgstr "" + +msgid "2 级 [中速 50%]" +msgstr "" + +msgid "3 级 [全速 100%]" +msgstr "" + +msgid "级" +msgstr "" + +msgid "硬件快速测试" +msgstr "" + +msgid "点击按钮可即时下发档位进行硬件测试,测试结果立即生效并在状态栏回显。若服务处于运行中,后续温控检测将按设定规则恢复。" +msgstr "" + +msgid "停止风扇 (0 级)" +msgstr "" + +msgid "低速测试 (1 级)" +msgstr "" + +msgid "中速测试 (2 级)" +msgstr "" + +msgid "全速测试 (3 级)" +msgstr "" + +msgid "正在下发测试档位: %d 级 ..." +msgstr "" + +msgid "风扇测试档位下发成功: %d 级" +msgstr "" + +msgid "风扇档位下发失败,请检查硬件节点。" +msgstr "" + +msgid "正在执行服务操作: %s ..." +msgstr "" + +msgid "服务操作成功执行。" +msgstr "" + +msgid "服务操作执行失败。" +msgstr "" + +msgid "RPC 请求失败: %s" +msgstr "" + +msgid "配置香蕉派 BPI-R4 的 PWM 智能风扇调速策略与温度响应区间。" +msgstr "" + +msgid "参数设置" +msgstr "" + +msgid "启用服务" +msgstr "" + +msgid "开启风扇温控自动守护服务。" +msgstr "" + +msgid "工作模式" +msgstr "" + +msgid "选择风扇的控制策略模式。" +msgstr "" + +msgid "检测周期 (秒)" +msgstr "" + +msgid "温度轮询检测的时间间隔,推荐 3 到 10 秒。" +msgstr "" + +msgid "启动自检清洁" +msgstr "" + +msgid "服务启动时全速转动 2 秒,清洁积灰并确保轴承起步运转。" +msgstr "" + +msgid "手动常开档位" +msgstr "" + +msgid "指定风扇持续运转的固定档位。" +msgstr "" + +msgid "0 级 [停转 0%]" +msgstr "" + +msgid "1 级 [低速 31%]" +msgstr "" + +msgid "2 级 [中速 50%]" +msgstr "" + +msgid "3 级 [全速 100%]" +msgstr "" + +msgid "1 级低速触发温度 (°C)" +msgstr "" + +msgid "温度达到此数值时启动 1 级低速 (约 31% 占空比),低于该值停转。" +msgstr "" + +msgid "2 级中速触发温度 (°C)" +msgstr "" + +msgid "温度达到此数值时提升至 2 级中速 (约 50% 占空比)。" +msgstr "" + +msgid "3 级全速触发温度 (°C)" +msgstr "" + +msgid "温度达到此数值时提升至 3 级全速 (100% 占空比)。" +msgstr "" + +msgid "换挡回差温度 (°C)" +msgstr "" + +msgid "降温换挡的滞后温差。例如 60°C 升至中速,需降至 57°C 才会降回低速,防止临界点频繁启停。" +msgstr "" + +msgid "正在加载日志..." +msgstr "" + +msgid "刷新日志" +msgstr "" + +msgid "清空日志" +msgstr "" + +msgid "实时展示温控守护进程的调速动作、温度变化与自检日志。" +msgstr "" + +msgid "系统日志" +msgstr "" + +msgid "暂无日志记录。" +msgstr "" + +msgid "读取日志失败: %s" +msgstr "" + +msgid "日志已成功清空。" +msgstr "" + +msgid "清空日志失败。" +msgstr "" + +msgid "当前策略配置" +msgstr "" + +msgid "检测轮询周期" +msgstr "" + +msgid "启动除尘自检" +msgstr "" + +msgid "低速触发阈值" +msgstr "" + +msgid "中速触发阈值" +msgstr "" + +msgid "全速触发阈值" +msgstr "" + +msgid "换挡回差温差" +msgstr "" + +msgid "预设运转档位" +msgstr "" + +msgid "控制驱动策略" +msgstr "" + +msgid "系统内核设备树原生驱动接管" +msgstr "" + +msgid "秒" +msgstr "" + +msgid "已开启 (开机全速 2 秒)" +msgstr "" + +msgid "未开启" +msgstr "" + +msgid "低于 %d °C 停转" +msgstr "" + +msgid "低于 %d °C 降回低速" +msgstr "" + +msgid "低于 %d °C 降回中速" +msgstr "" + +msgid "防临界点振荡" +msgstr "" + diff --git a/luci-app-fancontrol/po/zh_Hans/fancontrol.po b/luci-app-fancontrol/po/zh_Hans/fancontrol.po index be72c0edf..7a378855d 100644 --- a/luci-app-fancontrol/po/zh_Hans/fancontrol.po +++ b/luci-app-fancontrol/po/zh_Hans/fancontrol.po @@ -1,293 +1,261 @@ msgid "" -msgstr "" -"Project-Id-Version: luci-app-fancontrol 2.2.0\n" -"PO-Revision-Date: 2026-07-30 00:00+0800\n" -"Last-Translator: JiaY-shi \n" -"Language-Team: Chinese (Simplified Han script)\n" -"Language: zh_Hans\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=1; plural=0;\n" +msgstr "Content-Type: text/plain; charset=UTF-8\n" -msgid "Kernel cooling device" -msgstr "内核散热设备" - -msgid "Use auto for a single pwm-fan device, an explicit cooling_device path for multiple fans, or none when kernel fan control is already handled elsewhere." -msgstr "单个 pwm-fan 设备使用 auto;多个风扇需指定对应的 cooling_device 路径;已通过其他方式处理内核风扇控制时使用 none。" - -#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:173 -msgid "" -"After starting, the fan stops only after temperature falls this many degrees " -"below the start temperature. This prevents rapid on/off cycling." -msgstr "风扇启动后,温度需要降到比启动温度低这么多度时才会停转,避免风扇频繁启停。" - -#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:143 -msgid "" -"Automatic detection works on most devices. Select explicit sysfs entries " -"only when necessary." -msgstr "自动检测适用于大多数设备,仅在必要时手动指定 sysfs 项。" - -#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:238 -msgid "" -"Brief PWM applied when a stopped fan starts. It helps fans that cannot start " -"reliably at low duty. Set to 0 together with a zero duration to disable." -msgstr "风扇从停止状态启动时短暂使用的 PWM,可帮助低占空比下无法可靠启动的风扇。将此项和助推时长都设为 0 可禁用。" - -#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:122 -msgid "Collecting data..." -msgstr "正在采集数据..." - -#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:111 -msgid "" -"Configure a continuous PWM curve based on device temperature. Automatic " -"hardware detection is suitable for most devices." -msgstr "根据设备温度配置连续 PWM 调速曲线。自动硬件检测适用于大多数设备。" - -#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:145 -msgid "" -"Controls startup assistance and fail-safe behavior. Full-speed safety " -"defaults are recommended." -msgstr "控制启动助推和故障保护行为,建议保留默认的全速安全设置。" - -#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:141 -msgid "Controls when the fan starts and how its speed rises with temperature." -msgstr "控制风扇何时启动,以及转速如何随温度升高。" - -#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:263 -msgid "Debug logging" -msgstr "调试日志" - -#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:232 -msgid "" -"Divides the raw sensor value before control calculations. Standard Linux " -"temperature inputs use 1000; sensors reporting whole degrees use 1." -msgstr "进行控制计算前用于换算传感器原始值。标准 Linux 温度输入使用 1000,直接报告摄氏度的传感器使用 1。" - -#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:147 -msgid "Enable" -msgstr "启用" - -#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:98 -msgid "Enter \"auto\" or an absolute sysfs path." -msgstr "请输入 \"auto\" 或 sysfs 绝对路径。" - -#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:110 -#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/root/usr/share/luci/menu.d/luci-app-fancontrol.json:3 -msgid "Fan Control" +msgid "风扇控制" msgstr "风扇控制" -#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:84 -msgid "Fan speed" -msgstr "风扇转速" +msgid "状态概览" +msgstr "状态概览" -#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:89 -msgid "Fault" -msgstr "故障" +msgid "温控配置" +msgstr "温控配置" -#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:158 -msgid "Full-speed temperature" -msgstr "全速温度" +msgid "运行日志" +msgstr "运行日志" -#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:165 -msgid "Full-speed temperature must be higher than start temperature." -msgstr "全速温度必须高于启动温度。" +msgid "实时监控香蕉派 BPI-R4 处理器温度与 PWM 风扇运行档位,提供硬件即时测试功能。" +msgstr "实时监控香蕉派 BPI-R4 处理器温度与 PWM 风扇运行档位,提供硬件即时测试功能。" -#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/root/usr/share/rpcd/acl.d/luci-app-fancontrol.json:3 -msgid "Grant access to Fan Control" -msgstr "授予风扇控制访问权限" - -#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:142 -msgid "Hardware" -msgstr "硬件" - -#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:185 -msgid "Highest PWM allowed by the curve. A value of 255 means 100% duty cycle." -msgstr "温控曲线允许的最高 PWM,255 表示 100% 占空比。" - -#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:244 -msgid "How long the startup kick is applied. Unit: milliseconds." -msgstr "启动助推持续的时间,单位为毫秒。" - -#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:196 -msgid "" -"How often the temperature is read and the PWM output is updated. Unit: " -"seconds." -msgstr "读取温度并更新 PWM 输出的时间间隔,单位为秒。" - -#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:215 -msgid "Hwmon device name" -msgstr "Hwmon 设备名称" - -#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:179 -msgid "" -"Lowest PWM used while the fan is running. Increase it if the fan stalls or " -"cannot maintain rotation. Range: 1–255." -msgstr "风扇运行时使用的最低 PWM。如果风扇停转或无法持续旋转,请提高此值。范围:1–255。" - -#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:181 -msgid "Maximum PWM" -msgstr "最大 PWM" - -#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:188 -msgid "Maximum PWM must not be lower than minimum running PWM." -msgstr "最大 PWM 不得低于最低运行 PWM。" - -#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:175 -msgid "Minimum running PWM" -msgstr "最低运行 PWM" - -#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:71 -msgid "None" -msgstr "无" - -#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:224 -msgid "" -"Optional pwmN_enable path used to select manual PWM mode. With \"auto\", the " -"matching control is used when available." -msgstr "用于选择手动 PWM 模式的可选 pwmN_enable 路径。设为 \"auto\" 时会自动使用匹配的控制项。" - -#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:88 -msgid "PWM device" -msgstr "PWM 设备" - -#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:220 -msgid "PWM mode control" -msgstr "PWM 模式控制" - -#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:82 -#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:209 -msgid "PWM output" -msgstr "PWM 输出" - -#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:162 -msgid "" -"PWM rises linearly and reaches the configured maximum at this temperature. " -"Unit: °C." -msgstr "PWM 随温度线性升高,并在此温度达到设定的最大值,单位为 °C。" - -#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:255 -msgid "" -"PWM used when the temperature sensor cannot be read. Keeping 255 is the " -"safest choice." -msgstr "温度传感器读取失败时使用的 PWM。保持 255 是最安全的选择。" - -#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:261 -msgid "" -"PWM written when the service stops. Keeping the fan at full speed avoids " -"losing cooling after an unexpected exit." -msgstr "服务停止时写入的 PWM。保持风扇全速可避免服务意外退出后失去散热。" - -#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:192 -msgid "Polling interval" -msgstr "轮询间隔" - -#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:218 -msgid "" -"Preferred hwmon name when PWM output is automatic. The pwmfan driver is " -"preferred by default." -msgstr "自动选择 PWM 输出时优先使用的 hwmon 名称,默认优先选择 pwmfan 驱动。" - -#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:207 -msgid "" -"Preferred thermal zone type when temperature input is automatic, for example " -"cpu_top_thermal. Leave as \"auto\" unless several sensors are available." -msgstr "自动选择温度输入时优先使用的 thermal zone 类型,例如 cpu_top_thermal。除非存在多个传感器,否则保持 \"auto\"。" - -#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:150 -msgid "Run the controller and apply this temperature curve." -msgstr "运行风扇控制器并应用此温控曲线。" - -#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:77 -msgid "Running" -msgstr "运行中" - -#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:75 -#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:113 -msgid "Runtime Status" +msgid "运行状态" msgstr "运行状态" -#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:144 -msgid "Safety" -msgstr "安全" +msgid "服务状态" +msgstr "服务状态" -#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:251 -msgid "Sensor failure PWM" -msgstr "传感器故障 PWM" +msgid "运行中 [受控]" +msgstr "运行中 [受控]" -#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:257 -msgid "Service exit PWM" -msgstr "服务退出 PWM" +msgid "运行中 [非受控]" +msgstr "运行中 [非受控]" -#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:138 -msgid "Settings" -msgstr "设置" - -#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:234 -msgid "Start kick PWM" -msgstr "启动助推 PWM" - -#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:247 -msgid "Start kick PWM must be non-zero when a kick duration is configured." -msgstr "配置启动助推时长时,启动助推 PWM 不能为零。" - -#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:240 -msgid "Start kick duration" -msgstr "启动助推时长" - -#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:152 -msgid "Start temperature" -msgstr "启动温度" - -#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:169 -msgid "Stop hysteresis" -msgstr "停转回差" - -#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:77 -msgid "Stopped" +msgid "已停止" msgstr "已停止" -#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:80 -msgid "Temperature" -msgstr "温度" +msgid "重启服务" +msgstr "重启服务" -#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:140 -msgid "Temperature Curve" -msgstr "温控曲线" +msgid "运行模式" +msgstr "运行模式" -#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:226 -msgid "Temperature divisor" -msgstr "温度除数" +msgid "阶梯温控模式" +msgstr "阶梯温控模式" -#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:87 -#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:198 -msgid "Temperature input" -msgstr "温度输入" +msgid "手动定速模式" +msgstr "手动定速模式" -#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:156 -msgid "" -"The fan remains off below this temperature and starts at the minimum running " -"PWM when it is reached. Unit: °C." -msgstr "低于此温度时风扇保持停止;达到此温度时,以最低运行 PWM 启动。单位为 °C。" +msgid "内核接管模式" +msgstr "内核接管模式" -#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:204 -msgid "Thermal zone type" -msgstr "温度区域类型" +msgid "处理器温度" +msgstr "处理器温度" -#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:202 -msgid "" -"Use \"auto\" to select a CPU or SoC thermal sensor, or enter an absolute " -"thermal zone or hwmon tempN_input path." -msgstr "使用 \"auto\" 自动选择 CPU 或 SoC 温度传感器,也可输入 thermal zone 或 hwmon tempN_input 的绝对路径。" +msgid "当前风扇档位" +msgstr "当前风扇档位" -#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:213 -msgid "" -"Use \"auto\" to select a writable hwmon pwmN output, or enter its absolute " -"path. cooling_device cur_state is not a PWM output." -msgstr "使用 \"auto\" 自动选择可写的 hwmon pwmN 输出,也可输入其绝对路径。cooling_device cur_state 不是 PWM 输出。" +msgid "脉宽占空比" +msgstr "脉宽占空比" + +msgid "0 级 [停转]" +msgstr "0 级 [停转]" + +msgid "1 级 [低速 31%]" +msgstr "1 级 [低速 31%]" + +msgid "2 级 [中速 50%]" +msgstr "2 级 [中速 50%]" + +msgid "3 级 [全速 100%]" +msgstr "3 级 [全速 100%]" + +msgid "级" +msgstr "级" + +msgid "硬件快速测试" +msgstr "硬件快速测试" + +msgid "点击按钮可即时下发档位进行硬件测试,测试结果立即生效并在状态栏回显。若服务处于运行中,后续温控检测将按设定规则恢复。" +msgstr "点击按钮可即时下发档位进行硬件测试,测试结果立即生效并在状态栏回显。若服务处于运行中,后续温控检测将按设定规则恢复。" + +msgid "停止风扇 (0 级)" +msgstr "停止风扇 (0 级)" + +msgid "低速测试 (1 级)" +msgstr "低速测试 (1 级)" + +msgid "中速测试 (2 级)" +msgstr "中速测试 (2 级)" + +msgid "全速测试 (3 级)" +msgstr "全速测试 (3 级)" + +msgid "正在下发测试档位: %d 级 ..." +msgstr "正在下发测试档位: %d 级 ..." + +msgid "风扇测试档位下发成功: %d 级" +msgstr "风扇测试档位下发成功: %d 级" + +msgid "风扇档位下发失败,请检查硬件节点。" +msgstr "风扇档位下发失败,请检查硬件节点。" + +msgid "正在执行服务操作: %s ..." +msgstr "正在执行服务操作: %s ..." + +msgid "服务操作成功执行。" +msgstr "服务操作成功执行。" + +msgid "服务操作执行失败。" +msgstr "服务操作执行失败。" + +msgid "RPC 请求失败: %s" +msgstr "RPC 请求失败: %s" + +msgid "配置香蕉派 BPI-R4 的 PWM 智能风扇调速策略与温度响应区间。" +msgstr "配置香蕉派 BPI-R4 的 PWM 智能风扇调速策略与温度响应区间。" + +msgid "参数设置" +msgstr "参数设置" + +msgid "启用服务" +msgstr "启用服务" + +msgid "开启风扇温控自动守护服务。" +msgstr "开启风扇温控自动守护服务。" + +msgid "工作模式" +msgstr "工作模式" + +msgid "选择风扇的控制策略模式。" +msgstr "选择风扇的控制策略模式。" + +msgid "检测周期 (秒)" +msgstr "检测周期 (秒)" + +msgid "温度轮询检测的时间间隔,推荐 3 到 10 秒。" +msgstr "温度轮询检测的时间间隔,推荐 3 到 10 秒。" + +msgid "启动自检清洁" +msgstr "启动自检清洁" + +msgid "服务启动时全速转动 2 秒,清洁积灰并确保轴承起步运转。" +msgstr "服务启动时全速转动 2 秒,清洁积灰并确保轴承起步运转。" + +msgid "手动常开档位" +msgstr "手动常开档位" + +msgid "指定风扇持续运转的固定档位。" +msgstr "指定风扇持续运转的固定档位。" + +msgid "0 级 [停转 0%]" +msgstr "0 级 [停转 0%]" + +msgid "1 级 [低速 31%]" +msgstr "1 级 [低速 31%]" + +msgid "2 级 [中速 50%]" +msgstr "2 级 [中速 50%]" + +msgid "3 级 [全速 100%]" +msgstr "3 级 [全速 100%]" + +msgid "1 级低速触发温度 (°C)" +msgstr "1 级低速触发温度 (°C)" + +msgid "温度达到此数值时启动 1 级低速 (约 31% 占空比),低于该值停转。" +msgstr "温度达到此数值时启动 1 级低速 (约 31% 占空比),低于该值停转。" + +msgid "2 级中速触发温度 (°C)" +msgstr "2 级中速触发温度 (°C)" + +msgid "温度达到此数值时提升至 2 级中速 (约 50% 占空比)。" +msgstr "温度达到此数值时提升至 2 级中速 (约 50% 占空比)。" + +msgid "3 级全速触发温度 (°C)" +msgstr "3 级全速触发温度 (°C)" + +msgid "温度达到此数值时提升至 3 级全速 (100% 占空比)。" +msgstr "温度达到此数值时提升至 3 级全速 (100% 占空比)。" + +msgid "换挡回差温度 (°C)" +msgstr "换挡回差温度 (°C)" + +msgid "降温换挡的滞后温差。例如 60°C 升至中速,需降至 57°C 才会降回低速,防止临界点频繁启停。" +msgstr "降温换挡的滞后温差。例如 60°C 升至中速,需降至 57°C 才会降回低速,防止临界点频繁启停。" + +msgid "正在加载日志..." +msgstr "正在加载日志..." + +msgid "刷新日志" +msgstr "刷新日志" + +msgid "清空日志" +msgstr "清空日志" + +msgid "实时展示温控守护进程的调速动作、温度变化与自检日志。" +msgstr "实时展示温控守护进程的调速动作、温度变化与自检日志。" + +msgid "系统日志" +msgstr "系统日志" + +msgid "暂无日志记录。" +msgstr "暂无日志记录。" + +msgid "读取日志失败: %s" +msgstr "读取日志失败: %s" + +msgid "日志已成功清空。" +msgstr "日志已成功清空。" + +msgid "清空日志失败。" +msgstr "清空日志失败。" + +msgid "当前策略配置" +msgstr "当前策略配置" + +msgid "检测轮询周期" +msgstr "检测轮询周期" + +msgid "启动除尘自检" +msgstr "启动除尘自检" + +msgid "低速触发阈值" +msgstr "低速触发阈值" + +msgid "中速触发阈值" +msgstr "中速触发阈值" + +msgid "全速触发阈值" +msgstr "全速触发阈值" + +msgid "换挡回差温差" +msgstr "换挡回差温差" + +msgid "预设运转档位" +msgstr "预设运转档位" + +msgid "控制驱动策略" +msgstr "控制驱动策略" + +msgid "系统内核设备树原生驱动接管" +msgstr "系统内核设备树原生驱动接管" + +msgid "秒" +msgstr "秒" + +msgid "已开启 (开机全速 2 秒)" +msgstr "已开启 (开机全速 2 秒)" + +msgid "未开启" +msgstr "未开启" + +msgid "低于 %d °C 停转" +msgstr "低于 %d °C 停转" + +msgid "低于 %d °C 降回低速" +msgstr "低于 %d °C 降回低速" + +msgid "低于 %d °C 降回中速" +msgstr "低于 %d °C 降回中速" + +msgid "防临界点振荡" +msgstr "防临界点振荡" -#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:266 -msgid "" -"Write temperature, PWM and RPM values to the system log at every polling " -"interval." -msgstr "每次轮询时将温度、PWM 和 RPM 数值写入系统日志。" diff --git a/luci-app-fancontrol/root/etc/config/fancontrol b/luci-app-fancontrol/root/etc/config/fancontrol new file mode 100644 index 000000000..ebc31a9c6 --- /dev/null +++ b/luci-app-fancontrol/root/etc/config/fancontrol @@ -0,0 +1,10 @@ +config fancontrol 'global' + option enabled '1' + option mode 'auto' + option interval '5' + option start_spin '1' + option temp_low '45' + option temp_med '60' + option temp_high '75' + option hysteresis '3' + option manual_level '2' diff --git a/luci-app-fancontrol/root/etc/init.d/fancontrol b/luci-app-fancontrol/root/etc/init.d/fancontrol new file mode 100755 index 000000000..215c0a602 --- /dev/null +++ b/luci-app-fancontrol/root/etc/init.d/fancontrol @@ -0,0 +1,44 @@ +#!/bin/sh /etc/rc.common +# SPDX-License-Identifier: Apache-2.0 +# Copyright (C) 2026 Dante + +USE_PROCD=1 +START=95 +STOP=10 + +PROG="/usr/sbin/fancontrold" +CONFIG="fancontrol" + +start_service() { + local enabled + enabled=$(uci -q get "${CONFIG}.global.enabled" || echo 0) + [ "${enabled}" = "1" ] || return 0 + + chmod 755 "${PROG}" 2>/dev/null + + # 启动前清理任何可能残留的孤儿进程 + killall -q fancontrold 2>/dev/null + + procd_open_instance "main" + procd_set_param command "${PROG}" + procd_set_param respawn 3600 5 0 + procd_set_param stdout 1 + procd_set_param stderr 1 + procd_close_instance +} + +stop_service() { + killall -q fancontrold 2>/dev/null + if [ -f "/sys/class/thermal/thermal_zone0/policy" ]; then + echo "step_wise" > "/sys/class/thermal/thermal_zone0/policy" 2>/dev/null + fi +} + +reload_service() { + stop + start +} + +service_triggers() { + procd_add_reload_trigger "${CONFIG}" +} diff --git a/luci-app-fancontrol/root/usr/sbin/fancontrold b/luci-app-fancontrol/root/usr/sbin/fancontrold new file mode 100755 index 000000000..2f958540b --- /dev/null +++ b/luci-app-fancontrol/root/usr/sbin/fancontrold @@ -0,0 +1,249 @@ +#!/bin/sh +# SPDX-License-Identifier: Apache-2.0 +# Copyright (C) 2026 Dante + +LOG_FILE="/var/log/fancontrol.log" +CONFIG="fancontrol" + +log_msg() { + local level="$1" + local msg="$2" + local timestamp + timestamp="$(date '+%Y-%m-%d %H:%M:%S')" + echo "${timestamp} [${level}] ${msg}" >> "${LOG_FILE}" + + # 保持日志行数在 200 行以内 + if [ -f "${LOG_FILE}" ]; then + local lines + lines=$(wc -l < "${LOG_FILE}" 2>/dev/null || echo 0) + if [ "${lines}" -gt 200 ]; then + local tmp_log + tmp_log="$(tail -n 100 "${LOG_FILE}")" + echo "${tmp_log}" > "${LOG_FILE}" + fi + fi +} + +# 寻找硬件节点 +find_nodes() { + THERMAL_ZONE="" + COOLING_DEV="" + HWMON_PWM="" + + # 优先寻找 thermal_zone0 + if [ -f "/sys/class/thermal/thermal_zone0/temp" ]; then + THERMAL_ZONE="/sys/class/thermal/thermal_zone0" + else + for z in /sys/class/thermal/thermal_zone*; do + if [ -f "${z}/temp" ]; then + THERMAL_ZONE="${z}" + break + fi + done + fi + + # 寻找 pwm-fan 冷却设备 + for c in /sys/class/thermal/cooling_device*; do + if [ -f "${c}/type" ]; then + local ctype + ctype=$(cat "${c}/type" 2>/dev/null) + if [ "${ctype}" = "pwm-fan" ] || [ "${ctype}" = "pwmfan" ]; then + COOLING_DEV="${c}" + break + fi + fi + done + + # 寻找 hwmon pwm 节点 + for h in /sys/class/hwmon/hwmon*; do + if [ -f "${h}/name" ]; then + local hname + hname=$(cat "${h}/name" 2>/dev/null) + if [ "${hname}" = "pwmfan" ] && [ -f "${h}/pwm1" ]; then + HWMON_PWM="${h}/pwm1" + break + fi + fi + done +} + +get_temperature() { + if [ -n "${THERMAL_ZONE}" ] && [ -f "${THERMAL_ZONE}/temp" ]; then + cat "${THERMAL_ZONE}/temp" 2>/dev/null || echo "0" + else + echo "0" + fi +} + +set_fan_level() { + local lvl="$1" + if [ -n "${COOLING_DEV}" ] && [ -w "${COOLING_DEV}/cur_state" ]; then + echo "${lvl}" > "${COOLING_DEV}/cur_state" 2>/dev/null + fi + if [ -n "${HWMON_PWM}" ] && [ -w "${HWMON_PWM}" ]; then + local pwm_val=0 + case "${lvl}" in + 0) pwm_val=0 ;; + 1) pwm_val=80 ;; + 2) pwm_val=128 ;; + 3) pwm_val=255 ;; + *) pwm_val=0 ;; + esac + echo "${pwm_val}" > "${HWMON_PWM}" 2>/dev/null + fi +} + +get_level_name() { + case "$1" in + 0) echo "0 级 (停转)" ;; + 1) echo "1 级 (低速 31%)" ;; + 2) echo "2 级 (中速 50%)" ;; + 3) echo "3 级 (全速 100%)" ;; + *) echo "$1 级" ;; + esac +} + +calc_level() { + local temp="$1" + local cur="$2" + local l1="$3" + local l2="$4" + local l3="$5" + local h="$6" + + if [ "${cur}" -le 0 ]; then + if [ "${temp}" -ge "${l3}" ]; then + echo 3 + elif [ "${temp}" -ge "${l2}" ]; then + echo 2 + elif [ "${temp}" -ge "${l1}" ]; then + echo 1 + else + echo 0 + fi + elif [ "${cur}" -eq 1 ]; then + if [ "${temp}" -ge "${l3}" ]; then + echo 3 + elif [ "${temp}" -ge "${l2}" ]; then + echo 2 + elif [ "${temp}" -lt "$((l1 - h))" ]; then + echo 0 + else + echo 1 + fi + elif [ "${cur}" -eq 2 ]; then + if [ "${temp}" -ge "${l3}" ]; then + echo 3 + elif [ "${temp}" -lt "$((l1 - h))" ]; then + echo 0 + elif [ "${temp}" -lt "$((l2 - h))" ]; then + echo 1 + else + echo 2 + fi + else + if [ "${temp}" -lt "$((l1 - h))" ]; then + echo 0 + elif [ "${temp}" -lt "$((l2 - h))" ]; then + echo 1 + elif [ "${temp}" -lt "$((l3 - h))" ]; then + echo 2 + else + echo 3 + fi + fi +} + +cleanup() { + if [ -n "${THERMAL_ZONE}" ] && [ -w "${THERMAL_ZONE}/policy" ]; then + echo "step_wise" > "${THERMAL_ZONE}/policy" 2>/dev/null + fi + log_msg "信息" "风扇控制守护服务已停止,已恢复内核温控策略" + exit 0 +} + +trap cleanup INT TERM QUIT + +find_nodes + +if [ -z "${COOLING_DEV}" ] && [ -z "${HWMON_PWM}" ]; then + log_msg "错误" "未找到可用的风扇硬件控制节点,服务退出" + exit 1 +fi + +enabled=$(uci -q get ${CONFIG}.global.enabled || echo 1) +if [ "${enabled}" != "1" ]; then + log_msg "信息" "服务配置为未启用状态,退出" + exit 0 +fi + +mode=$(uci -q get ${CONFIG}.global.mode || echo "auto") +interval=$(uci -q get ${CONFIG}.global.interval || echo 5) +start_spin=$(uci -q get ${CONFIG}.global.start_spin || echo 1) +temp_low=$(uci -q get ${CONFIG}.global.temp_low || echo 45) +temp_med=$(uci -q get ${CONFIG}.global.temp_med || echo 60) +temp_high=$(uci -q get ${CONFIG}.global.temp_high || echo 75) +hysteresis=$(uci -q get ${CONFIG}.global.hysteresis || echo 3) +manual_level=$(uci -q get ${CONFIG}.global.manual_level || echo 2) + +[ "${interval}" -lt 1 ] 2>/dev/null && interval=1 +[ "${interval}" -gt 60 ] 2>/dev/null && interval=60 + +log_msg "信息" "风扇控制守护服务启动,运行模式: ${mode},检测周期: ${interval} 秒" + +# 启动自检 +if [ "${start_spin}" = "1" ] && [ "${mode}" != "kernel" ]; then + log_msg "信息" "执行启动自检,全速运转 2 秒以清洁防尘" + set_fan_level 3 + sleep 2 +fi + +# 非内核模式下切换为用户空间接管,防止内核 step_wise 依据 40°C 强制拉高转速 +if [ "${mode}" != "kernel" ] && [ -n "${THERMAL_ZONE}" ] && [ -w "${THERMAL_ZONE}/policy" ]; then + echo "user_space" > "${THERMAL_ZONE}/policy" 2>/dev/null +fi + +current_level=-1 + +while true; do + case "${mode}" in + "kernel") + # 内核托管模式 + if [ -n "${THERMAL_ZONE}" ] && [ -w "${THERMAL_ZONE}/policy" ]; then + echo "step_wise" > "${THERMAL_ZONE}/policy" 2>/dev/null + fi + sleep "${interval}" + ;; + + "manual") + if [ "${current_level}" -ne "${manual_level}" ]; then + set_fan_level "${manual_level}" + current_level="${manual_level}" + lvl_name=$(get_level_name "${manual_level}") + log_msg "信息" "手动定速模式生效,当前档位设定为: ${lvl_name}" + fi + sleep "${interval}" + ;; + + "auto"|*) + raw_t=$(get_temperature) + # 单位毫摄氏度转整数摄氏度 + t_c=$((raw_t / 1000)) + t_dec=$(((raw_t % 1000) / 100)) + target_lvl=$(calc_level "${t_c}" "${current_level}" "${temp_low}" "${temp_med}" "${temp_high}" "${hysteresis}") + + if [ "${target_lvl}" -ne "${current_level}" ]; then + set_fan_level "${target_lvl}" + old_name=$(get_level_name "${current_level}") + new_name=$(get_level_name "${target_lvl}") + if [ "${current_level}" -eq -1 ]; then + log_msg "信息" "初始化风扇档位: 当前温度 ${t_c}.${t_dec} °C,设定为 ${new_name}" + else + log_msg "信息" "温度变化 (${t_c}.${t_dec} °C): 档位由 ${old_name} 调整为 ${new_name}" + fi + current_level="${target_lvl}" + fi + sleep "${interval}" + ;; + esac +done diff --git a/luci-app-fancontrol/root/usr/share/luci/menu.d/luci-app-fancontrol.json b/luci-app-fancontrol/root/usr/share/luci/menu.d/luci-app-fancontrol.json index 88d2f9e17..a25bcad4c 100644 --- a/luci-app-fancontrol/root/usr/share/luci/menu.d/luci-app-fancontrol.json +++ b/luci-app-fancontrol/root/usr/share/luci/menu.d/luci-app-fancontrol.json @@ -1,13 +1,37 @@ { - "admin/services/fancontrol": { - "title": "Fan Control", - "order": 90, + "admin/system/fancontrol": { + "title": "风扇控制", + "order": 65, "action": { - "type": "view", - "path": "fancontrol" + "type": "firstchild" }, "depends": { - "acl": [ "luci-app-fancontrol" ] + "acl": [ "luci-app-fancontrol" ], + "uci": { "fancontrol": true } + } + }, + "admin/system/fancontrol/overview": { + "title": "状态概览", + "order": 10, + "action": { + "type": "view", + "path": "fancontrol/overview" + } + }, + "admin/system/fancontrol/settings": { + "title": "温控配置", + "order": 20, + "action": { + "type": "view", + "path": "fancontrol/settings" + } + }, + "admin/system/fancontrol/log": { + "title": "运行日志", + "order": 30, + "action": { + "type": "view", + "path": "fancontrol/log" } } } diff --git a/luci-app-fancontrol/root/usr/share/rpcd/acl.d/luci-app-fancontrol.json b/luci-app-fancontrol/root/usr/share/rpcd/acl.d/luci-app-fancontrol.json index 579af283e..5f9ac21fa 100644 --- a/luci-app-fancontrol/root/usr/share/rpcd/acl.d/luci-app-fancontrol.json +++ b/luci-app-fancontrol/root/usr/share/rpcd/acl.d/luci-app-fancontrol.json @@ -1,17 +1,21 @@ { "luci-app-fancontrol": { - "description": "Grant access to Fan Control", + "description": "Grant access to BPI-R4 fan control procedures", "read": { - "uci": [ "fancontrol" ], "ubus": { - "service": [ "list" ] + "luci.fancontrol": [ "*" ] }, - "file": { - "/var/run/fancontrol.status": [ "read" ] - } + "uci": [ + "fancontrol" + ] }, "write": { - "uci": [ "fancontrol" ] + "ubus": { + "luci.fancontrol": [ "*" ] + }, + "uci": [ + "fancontrol" + ] } } } diff --git a/luci-app-fancontrol/root/usr/share/rpcd/ucode/luci.fancontrol b/luci-app-fancontrol/root/usr/share/rpcd/ucode/luci.fancontrol new file mode 100644 index 000000000..c932dc37d --- /dev/null +++ b/luci-app-fancontrol/root/usr/share/rpcd/ucode/luci.fancontrol @@ -0,0 +1,223 @@ +'use strict'; + +import { access, readfile, writefile, stat, popen } from 'fs'; +import { cursor } from 'uci'; +import { connect } from 'ubus'; + +const UCI_CONFIG = 'fancontrol'; +const LOG_FILE = '/var/log/fancontrol.log'; +const THERMAL_ZONE_TEMP = '/sys/class/thermal/thermal_zone0/temp'; +const COOLING_DEV_CUR = '/sys/class/thermal/cooling_device0/cur_state'; +const HWMON_PWM = '/sys/class/hwmon/hwmon1/pwm1'; + +function get_procd_status() { + let u = connect(); + if (!u) return { running: false, pid: null, state: 'stopped' }; + + let res = u.call('service', 'list', { name: 'fancontrol' }); + u.disconnect(); + + if (res && res['fancontrol'] && res['fancontrol'].instances) { + let instances = res['fancontrol'].instances; + for (let name in instances) { + let inst = instances[name]; + if (inst && inst.running) { + return { running: true, pid: inst.pid, state: 'managed' }; + } + } + } + + // 检查是否有残留孤儿进程 + let p = popen('pidof /usr/sbin/fancontrold', 'r'); + if (p) { + let pid_str = p.read('all'); + p.close(); + if (pid_str && length(trim(pid_str)) > 0) { + let pids = split(trim(pid_str), /\s+/); + if (length(pids) > 0 && pids[0] != '') { + return { running: true, pid: int(pids[0]), state: 'unmanaged' }; + } + } + } + + return { running: false, pid: null, state: 'stopped' }; +} + +function get_hardware_info() { + let temp_c = 0.0; + if (access(THERMAL_ZONE_TEMP, 'r')) { + let raw_t = readfile(THERMAL_ZONE_TEMP); + if (raw_t) { + let raw_val = int(trim(raw_t)); + temp_c = sprintf('%.1f', raw_val / 1000.0); + } + } + + let cur_level = 0; + if (access(COOLING_DEV_CUR, 'r')) { + let raw_lvl = readfile(COOLING_DEV_CUR); + if (raw_lvl) cur_level = int(trim(raw_lvl)); + } + + let pwm_val = 0; + if (access(HWMON_PWM, 'r')) { + let raw_pwm = readfile(HWMON_PWM); + if (raw_pwm) pwm_val = int(trim(raw_pwm)); + } else { + // 依据档位估算 + if (cur_level == 1) pwm_val = 80; + else if (cur_level == 2) pwm_val = 128; + else if (cur_level == 3) pwm_val = 255; + } + + return { + temp: temp_c, + level: cur_level, + pwm: pwm_val + }; +} + +const methods = { + get_status: { + call: function() { + let proc = get_procd_status(); + let hw = get_hardware_info(); + let uci_c = cursor(); + let enabled = int(uci_c.get(UCI_CONFIG, 'global', 'enabled') || '0'); + let mode = uci_c.get(UCI_CONFIG, 'global', 'mode') || 'auto'; + let interval = int(uci_c.get(UCI_CONFIG, 'global', 'interval') || '5'); + + let start_spin = int(uci_c.get(UCI_CONFIG, 'global', 'start_spin') || '1'); + let temp_low = int(uci_c.get(UCI_CONFIG, 'global', 'temp_low') || '45'); + let temp_med = int(uci_c.get(UCI_CONFIG, 'global', 'temp_med') || '60'); + let temp_high = int(uci_c.get(UCI_CONFIG, 'global', 'temp_high') || '75'); + let hysteresis = int(uci_c.get(UCI_CONFIG, 'global', 'hysteresis') || '3'); + let manual_level = int(uci_c.get(UCI_CONFIG, 'global', 'manual_level') || '2'); + + return { + running: proc.running, + state: proc.state, + pid: proc.pid, + enabled: enabled, + mode: mode, + interval: interval, + start_spin: start_spin, + temp_low: temp_low, + temp_med: temp_med, + temp_high: temp_high, + hysteresis: hysteresis, + manual_level: manual_level, + temp: hw.temp, + level: hw.level, + pwm: hw.pwm + }; + } + }, + + set_level: { + args: { level: 0 }, + call: function(req) { + let lvl = int(req.args?.level ?? 0); + if (lvl < 0) lvl = 0; + if (lvl > 3) lvl = 3; + + let applied = false; + if (access(COOLING_DEV_CUR, 'w')) { + try { + writefile(COOLING_DEV_CUR, lvl + "\n"); + applied = true; + } catch (e) {} + } + + if (!applied && access(HWMON_PWM, 'w')) { + let pwm_val = 0; + if (lvl == 1) pwm_val = 80; + else if (lvl == 2) pwm_val = 128; + else if (lvl == 3) pwm_val = 255; + try { + writefile(HWMON_PWM, pwm_val + "\n"); + applied = true; + } catch (e) {} + } + + // 记录测试日志 + try { + let p = popen('date "+%Y-%m-%d %H:%M:%S"', 'r'); + let ts = p ? trim(p.read('all')) : ''; + if (p) p.close(); + let line = ts + ' [测试] 手动即时下发风扇档位: ' + lvl + ' 级\n'; + let old_content = readfile(LOG_FILE) || ''; + writefile(LOG_FILE, old_content + line); + } catch (e) {} + + return { success: applied, level: lvl }; + } + }, + + get_logs: { + args: { lines: 100 }, + call: function(req) { + let lines_limit = int(req.args?.lines ?? 100); + let logs = []; + try { + if (access(LOG_FILE, 'r')) { + let log_stat = stat(LOG_FILE); + let content = ''; + if (log_stat && log_stat.size > 65536) { + let fd = popen('tail -c 65536 ' + LOG_FILE, 'r'); + if (fd) { + content = fd.read('all'); + fd.close(); + } + } else { + content = readfile(LOG_FILE); + } + if (content) { + let all_lines = split(content, "\n"); + let total = length(all_lines); + let start = (total > lines_limit) ? (total - lines_limit) : 0; + for (let i = start; i < total; i++) { + if (all_lines[i] && length(trim(all_lines[i])) > 0) { + push(logs, all_lines[i]); + } + } + } + } + } catch (e) {} + return { logs: logs }; + } + }, + + clear_logs: { + call: function() { + try { + writefile(LOG_FILE, ''); + return { success: true }; + } catch (e) { + return { success: false, error: e }; + } + } + }, + + service: { + args: { action: 'restart' }, + call: function(req) { + let action = req.args?.action || 'restart'; + if (action != 'start' && action != 'stop' && action != 'restart') { + return { success: false, error: 'Invalid action' }; + } + + if (action == 'restart' || action == 'stop') { + // 强杀孤儿残留进程 + let p = popen('killall -9 fancontrold', 'r'); + if (p) p.close(); + } + + let rc = system('/etc/init.d/fancontrol ' + action + ' >/dev/null 2>&1'); + system('sleep 1'); + return { success: (rc === 0) }; + } + } +}; + +return { 'luci.fancontrol': methods }; diff --git a/modemdata/LICENSE b/luci-app-modemdata/LICENSE similarity index 100% rename from modemdata/LICENSE rename to luci-app-modemdata/LICENSE diff --git a/modemdata/luci-app-modemdata/Makefile b/luci-app-modemdata/Makefile similarity index 96% rename from modemdata/luci-app-modemdata/Makefile rename to luci-app-modemdata/Makefile index f3723c6fa..ef21e6167 100644 --- a/modemdata/luci-app-modemdata/Makefile +++ b/luci-app-modemdata/Makefile @@ -8,7 +8,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=luci-app-modemdata PKG_VERSION:=1.0.32 -PKG_RELEASE:=20260709 +PKG_RELEASE:=1 LUCI_TITLE:=LuCI JS Support for modemdata application MAINTAINER:=Rafał Wabik <4Rafal@gmail.com> LUCI_DESCRIPTION:=Detailed information about the cellular modem and parameters of the mobile internet connection. diff --git a/modemdata/luci-app-modemdata/htdocs/luci-static/resources/icons/ctime_new.svg b/luci-app-modemdata/htdocs/luci-static/resources/icons/ctime_new.svg similarity index 100% rename from modemdata/luci-app-modemdata/htdocs/luci-static/resources/icons/ctime_new.svg rename to luci-app-modemdata/htdocs/luci-static/resources/icons/ctime_new.svg diff --git a/modemdata/luci-app-modemdata/htdocs/luci-static/resources/icons/mobile-signal-000-000.svg b/luci-app-modemdata/htdocs/luci-static/resources/icons/mobile-signal-000-000.svg similarity index 100% rename from modemdata/luci-app-modemdata/htdocs/luci-static/resources/icons/mobile-signal-000-000.svg rename to luci-app-modemdata/htdocs/luci-static/resources/icons/mobile-signal-000-000.svg diff --git a/modemdata/luci-app-modemdata/htdocs/luci-static/resources/icons/mobile-signal-000-020.svg b/luci-app-modemdata/htdocs/luci-static/resources/icons/mobile-signal-000-020.svg similarity index 100% rename from modemdata/luci-app-modemdata/htdocs/luci-static/resources/icons/mobile-signal-000-020.svg rename to luci-app-modemdata/htdocs/luci-static/resources/icons/mobile-signal-000-020.svg diff --git a/modemdata/luci-app-modemdata/htdocs/luci-static/resources/icons/mobile-signal-020-040.svg b/luci-app-modemdata/htdocs/luci-static/resources/icons/mobile-signal-020-040.svg similarity index 100% rename from modemdata/luci-app-modemdata/htdocs/luci-static/resources/icons/mobile-signal-020-040.svg rename to luci-app-modemdata/htdocs/luci-static/resources/icons/mobile-signal-020-040.svg diff --git a/modemdata/luci-app-modemdata/htdocs/luci-static/resources/icons/mobile-signal-040-060.svg b/luci-app-modemdata/htdocs/luci-static/resources/icons/mobile-signal-040-060.svg similarity index 100% rename from modemdata/luci-app-modemdata/htdocs/luci-static/resources/icons/mobile-signal-040-060.svg rename to luci-app-modemdata/htdocs/luci-static/resources/icons/mobile-signal-040-060.svg diff --git a/modemdata/luci-app-modemdata/htdocs/luci-static/resources/icons/mobile-signal-060-080.svg b/luci-app-modemdata/htdocs/luci-static/resources/icons/mobile-signal-060-080.svg similarity index 100% rename from modemdata/luci-app-modemdata/htdocs/luci-static/resources/icons/mobile-signal-060-080.svg rename to luci-app-modemdata/htdocs/luci-static/resources/icons/mobile-signal-060-080.svg diff --git a/modemdata/luci-app-modemdata/htdocs/luci-static/resources/icons/mobile-signal-080-100.svg b/luci-app-modemdata/htdocs/luci-static/resources/icons/mobile-signal-080-100.svg similarity index 100% rename from modemdata/luci-app-modemdata/htdocs/luci-static/resources/icons/mobile-signal-080-100.svg rename to luci-app-modemdata/htdocs/luci-static/resources/icons/mobile-signal-080-100.svg diff --git a/modemdata/luci-app-modemdata/htdocs/luci-static/resources/icons/mybts.svg b/luci-app-modemdata/htdocs/luci-static/resources/icons/mybts.svg similarity index 100% rename from modemdata/luci-app-modemdata/htdocs/luci-static/resources/icons/mybts.svg rename to luci-app-modemdata/htdocs/luci-static/resources/icons/mybts.svg diff --git a/modemdata/luci-app-modemdata/htdocs/luci-static/resources/icons/sim_new.svg b/luci-app-modemdata/htdocs/luci-static/resources/icons/sim_new.svg similarity index 100% rename from modemdata/luci-app-modemdata/htdocs/luci-static/resources/icons/sim_new.svg rename to luci-app-modemdata/htdocs/luci-static/resources/icons/sim_new.svg diff --git a/modemdata/luci-app-modemdata/htdocs/luci-static/resources/icons/termometr.svg b/luci-app-modemdata/htdocs/luci-static/resources/icons/termometr.svg similarity index 100% rename from modemdata/luci-app-modemdata/htdocs/luci-static/resources/icons/termometr.svg rename to luci-app-modemdata/htdocs/luci-static/resources/icons/termometr.svg diff --git a/modemdata/luci-app-modemdata/htdocs/luci-static/resources/view/modem/modemconfig.js b/luci-app-modemdata/htdocs/luci-static/resources/view/modem/modemconfig.js similarity index 100% rename from modemdata/luci-app-modemdata/htdocs/luci-static/resources/view/modem/modemconfig.js rename to luci-app-modemdata/htdocs/luci-static/resources/view/modem/modemconfig.js diff --git a/modemdata/luci-app-modemdata/htdocs/luci-static/resources/view/modem/modemdefine.js b/luci-app-modemdata/htdocs/luci-static/resources/view/modem/modemdefine.js similarity index 100% rename from modemdata/luci-app-modemdata/htdocs/luci-static/resources/view/modem/modemdefine.js rename to luci-app-modemdata/htdocs/luci-static/resources/view/modem/modemdefine.js diff --git a/modemdata/luci-app-modemdata/htdocs/luci-static/resources/view/modem/modemdiagnostics.js b/luci-app-modemdata/htdocs/luci-static/resources/view/modem/modemdiagnostics.js similarity index 100% rename from modemdata/luci-app-modemdata/htdocs/luci-static/resources/view/modem/modemdiagnostics.js rename to luci-app-modemdata/htdocs/luci-static/resources/view/modem/modemdiagnostics.js diff --git a/modemdata/luci-app-modemdata/htdocs/luci-static/resources/view/modem/modempreview.js b/luci-app-modemdata/htdocs/luci-static/resources/view/modem/modempreview.js similarity index 100% rename from modemdata/luci-app-modemdata/htdocs/luci-static/resources/view/modem/modempreview.js rename to luci-app-modemdata/htdocs/luci-static/resources/view/modem/modempreview.js diff --git a/modemdata/luci-app-modemdata/htdocs/luci-static/resources/view/modem/modemupdate.js b/luci-app-modemdata/htdocs/luci-static/resources/view/modem/modemupdate.js similarity index 100% rename from modemdata/luci-app-modemdata/htdocs/luci-static/resources/view/modem/modemupdate.js rename to luci-app-modemdata/htdocs/luci-static/resources/view/modem/modemupdate.js diff --git a/modemdata/luci-app-modemdata/po/cs/luci-app-modemdata.po b/luci-app-modemdata/po/cs/luci-app-modemdata.po similarity index 100% rename from modemdata/luci-app-modemdata/po/cs/luci-app-modemdata.po rename to luci-app-modemdata/po/cs/luci-app-modemdata.po diff --git a/modemdata/luci-app-modemdata/po/da/luci-app-modemdata.po b/luci-app-modemdata/po/da/luci-app-modemdata.po similarity index 100% rename from modemdata/luci-app-modemdata/po/da/luci-app-modemdata.po rename to luci-app-modemdata/po/da/luci-app-modemdata.po diff --git a/modemdata/luci-app-modemdata/po/de/luci-app-modemdata.po b/luci-app-modemdata/po/de/luci-app-modemdata.po similarity index 100% rename from modemdata/luci-app-modemdata/po/de/luci-app-modemdata.po rename to luci-app-modemdata/po/de/luci-app-modemdata.po diff --git a/modemdata/luci-app-modemdata/po/es/luci-app-modemdata.po b/luci-app-modemdata/po/es/luci-app-modemdata.po similarity index 100% rename from modemdata/luci-app-modemdata/po/es/luci-app-modemdata.po rename to luci-app-modemdata/po/es/luci-app-modemdata.po diff --git a/modemdata/luci-app-modemdata/po/fr/luci-app-modemdata.po b/luci-app-modemdata/po/fr/luci-app-modemdata.po similarity index 100% rename from modemdata/luci-app-modemdata/po/fr/luci-app-modemdata.po rename to luci-app-modemdata/po/fr/luci-app-modemdata.po diff --git a/modemdata/luci-app-modemdata/po/it/luci-app-modemdata.po b/luci-app-modemdata/po/it/luci-app-modemdata.po similarity index 100% rename from modemdata/luci-app-modemdata/po/it/luci-app-modemdata.po rename to luci-app-modemdata/po/it/luci-app-modemdata.po diff --git a/modemdata/luci-app-modemdata/po/ja/luci-app-modemdata.po b/luci-app-modemdata/po/ja/luci-app-modemdata.po similarity index 100% rename from modemdata/luci-app-modemdata/po/ja/luci-app-modemdata.po rename to luci-app-modemdata/po/ja/luci-app-modemdata.po diff --git a/modemdata/luci-app-modemdata/po/ko/luci-app-modemdata.po b/luci-app-modemdata/po/ko/luci-app-modemdata.po similarity index 100% rename from modemdata/luci-app-modemdata/po/ko/luci-app-modemdata.po rename to luci-app-modemdata/po/ko/luci-app-modemdata.po diff --git a/modemdata/luci-app-modemdata/po/nl/luci-app-modemdata.po b/luci-app-modemdata/po/nl/luci-app-modemdata.po similarity index 100% rename from modemdata/luci-app-modemdata/po/nl/luci-app-modemdata.po rename to luci-app-modemdata/po/nl/luci-app-modemdata.po diff --git a/modemdata/luci-app-modemdata/po/pl/luci-app-modemdata.po b/luci-app-modemdata/po/pl/luci-app-modemdata.po similarity index 100% rename from modemdata/luci-app-modemdata/po/pl/luci-app-modemdata.po rename to luci-app-modemdata/po/pl/luci-app-modemdata.po diff --git a/modemdata/luci-app-modemdata/po/pt_BR/luci-app-modemdata.po b/luci-app-modemdata/po/pt_BR/luci-app-modemdata.po similarity index 100% rename from modemdata/luci-app-modemdata/po/pt_BR/luci-app-modemdata.po rename to luci-app-modemdata/po/pt_BR/luci-app-modemdata.po diff --git a/modemdata/luci-app-modemdata/po/ru/luci-app-modemdata.po b/luci-app-modemdata/po/ru/luci-app-modemdata.po similarity index 100% rename from modemdata/luci-app-modemdata/po/ru/luci-app-modemdata.po rename to luci-app-modemdata/po/ru/luci-app-modemdata.po diff --git a/modemdata/luci-app-modemdata/po/tr/luci-app-modemdata.po b/luci-app-modemdata/po/tr/luci-app-modemdata.po similarity index 100% rename from modemdata/luci-app-modemdata/po/tr/luci-app-modemdata.po rename to luci-app-modemdata/po/tr/luci-app-modemdata.po diff --git a/modemdata/luci-app-modemdata/po/vi/luci-app-modemdata.po b/luci-app-modemdata/po/vi/luci-app-modemdata.po similarity index 100% rename from modemdata/luci-app-modemdata/po/vi/luci-app-modemdata.po rename to luci-app-modemdata/po/vi/luci-app-modemdata.po diff --git a/luci-app-modemdata/po/zh-cn b/luci-app-modemdata/po/zh-cn new file mode 120000 index 000000000..8d69574dd --- /dev/null +++ b/luci-app-modemdata/po/zh-cn @@ -0,0 +1 @@ +zh_Hans \ No newline at end of file diff --git a/modemdata/luci-app-modemdata/po/zh_Hans/modemdata.po b/luci-app-modemdata/po/zh_Hans/modemdata.po similarity index 100% rename from modemdata/luci-app-modemdata/po/zh_Hans/modemdata.po rename to luci-app-modemdata/po/zh_Hans/modemdata.po diff --git a/modemdata/luci-app-modemdata/po/zh_Hant/modemdata.po b/luci-app-modemdata/po/zh_Hant/modemdata.po similarity index 100% rename from modemdata/luci-app-modemdata/po/zh_Hant/modemdata.po rename to luci-app-modemdata/po/zh_Hant/modemdata.po diff --git a/modemdata/luci-app-modemdata/root/etc/config/defmodems b/luci-app-modemdata/root/etc/config/defmodems similarity index 100% rename from modemdata/luci-app-modemdata/root/etc/config/defmodems rename to luci-app-modemdata/root/etc/config/defmodems diff --git a/modemdata/luci-app-modemdata/root/etc/config/modemdata b/luci-app-modemdata/root/etc/config/modemdata similarity index 100% rename from modemdata/luci-app-modemdata/root/etc/config/modemdata rename to luci-app-modemdata/root/etc/config/modemdata diff --git a/modemdata/luci-app-modemdata/root/etc/modem/modemlist.json b/luci-app-modemdata/root/etc/modem/modemlist.json similarity index 100% rename from modemdata/luci-app-modemdata/root/etc/modem/modemlist.json rename to luci-app-modemdata/root/etc/modem/modemlist.json diff --git a/modemdata/luci-app-modemdata/root/etc/uci-defaults/setup_modemdata.sh b/luci-app-modemdata/root/etc/uci-defaults/setup_modemdata.sh similarity index 100% rename from modemdata/luci-app-modemdata/root/etc/uci-defaults/setup_modemdata.sh rename to luci-app-modemdata/root/etc/uci-defaults/setup_modemdata.sh diff --git a/modemdata/luci-app-modemdata/root/usr/bin/md_modemmanager b/luci-app-modemdata/root/usr/bin/md_modemmanager old mode 100644 new mode 100755 similarity index 100% rename from modemdata/luci-app-modemdata/root/usr/bin/md_modemmanager rename to luci-app-modemdata/root/usr/bin/md_modemmanager diff --git a/modemdata/luci-app-modemdata/root/usr/bin/md_serial_ecm b/luci-app-modemdata/root/usr/bin/md_serial_ecm old mode 100644 new mode 100755 similarity index 100% rename from modemdata/luci-app-modemdata/root/usr/bin/md_serial_ecm rename to luci-app-modemdata/root/usr/bin/md_serial_ecm diff --git a/modemdata/luci-app-modemdata/root/usr/bin/md_uqmi b/luci-app-modemdata/root/usr/bin/md_uqmi old mode 100644 new mode 100755 similarity index 100% rename from modemdata/luci-app-modemdata/root/usr/bin/md_uqmi rename to luci-app-modemdata/root/usr/bin/md_uqmi diff --git a/modemdata/luci-app-modemdata/root/usr/share/luci/menu.d/luci-app-modemdata.json b/luci-app-modemdata/root/usr/share/luci/menu.d/luci-app-modemdata.json similarity index 100% rename from modemdata/luci-app-modemdata/root/usr/share/luci/menu.d/luci-app-modemdata.json rename to luci-app-modemdata/root/usr/share/luci/menu.d/luci-app-modemdata.json diff --git a/modemdata/luci-app-modemdata/root/usr/share/rpcd/acl.d/luci-app-modemdata.json b/luci-app-modemdata/root/usr/share/rpcd/acl.d/luci-app-modemdata.json similarity index 100% rename from modemdata/luci-app-modemdata/root/usr/share/rpcd/acl.d/luci-app-modemdata.json rename to luci-app-modemdata/root/usr/share/rpcd/acl.d/luci-app-modemdata.json diff --git a/modemdata/README.md b/modemdata/README.md deleted file mode 100644 index 08a31a016..000000000 --- a/modemdata/README.md +++ /dev/null @@ -1,328 +0,0 @@ -# luci-app-modemdata -![GitHub release (latest by date)](https://img.shields.io/github/v/release/4IceG/luci-app-modemdata?style=flat-square) -![GitHub stars](https://img.shields.io/github/stars/4IceG/luci-app-modemdata?style=flat-square) -![GitHub forks](https://img.shields.io/github/forks/4IceG/luci-app-modemdata?style=flat-square) -![GitHub All Releases](https://img.shields.io/github/downloads/4IceG/luci-app-modemdata/total) - - -### Languages available / Dostępne języki - -Czech language Danish language German language -Spanish language French language Italian language -Japanese language Korean language Dutch language -Polish language Brazilian Portuguese language Russian language Vietnamese language Simplified Chinese language Traditional Chinese language Traditional Chinese language - -> [!NOTE] -> -LuCI JS interface for [modemdata](https://github.com/obsy/modemdata) package. In the future, it will replace luci-app-3ginfo-lite. -> -> Interfejs LuCI JS dla pakietu [modemdata](https://github.com/obsy/modemdata). W przyszłości zastąpi on pakiet luci-app-3ginfo-lite. - -> [!IMPORTANT] -> ***Due to the layout, the package is dedicated to the luci-theme-bootstrap.*** -> -> ***Ze względu na układ pakiet dedykowany jest dla motywu luci-theme-bootstrap.*** - -> [!IMPORTANT] -> ***Required Packages:*** -> -> ***serial:*** -> sms-tool, kmod-usb-serial-option, kmod-usb-serial -> -> ***QMI/MBIM protocol:*** -> kmod-usb-net-qmi-wwan / kmod-usb-net-cdc-mbim, uqmi -> -> ***ModemManager protocol:*** -> modemmanager -> -> ***hilink (ecm):*** -> wget-ssl - -### Supported usb devices (serial mode): - -
- Pokaż | Show me - -``` bash -03f0581d -# Huawei E3272/E3276 -# Huawei ME906s-158 -# HP lt4112 Gobi 4G Module (Huawei ME906E) -# HP lt4132 LTE/HSPA+ 4G Module (Huawei ME906s) - -03f0a31d -# Huawei E3272/E3276 -# Huawei ME906s-158 -# HP lt4112 Gobi 4G Module (Huawei ME906E) -# HP lt4132 LTE/HSPA+ 4G Module (Huawei ME906s) - -05c69026 -# ASKEY WWHC050 - -05c69215 -# Quectel EC20 -# Quectel EC25 - -05c69625 -# Yuge CLM920 NC_5 - -05c6f601 -# MEIG SLM750-V - -0e8d7126 -# Fibocom FM350-GL - -0e8d7127 -# Fibocom FM350-GL - -119968a2 -# Sierra Wireless MC7710 - -11999071 -# DW5809e Dell Wireless 5809e Gobi 4G LTE Mobile Broadband Card (EM7305) -# DW5811e Snapdragon X7 LTE (EM7455B) -# Sierra Wireless EM7455 - -11999091 -# Sierra Wireless EM7565 (generic) - -119990d3 -# Sierra Wireless EM9190 - -12d11506 -# Huawei E3272/E3276 -# Huawei ME906s-158 -# HP lt4112 Gobi 4G Module (Huawei ME906E) -# HP lt4132 LTE/HSPA+ 4G Module (Huawei ME906s) - -12d1155e -# Huawei E3272/E3276 -# Huawei ME906s-158 -# HP lt4112 Gobi 4G Module (Huawei ME906E) -# HP lt4132 LTE/HSPA+ 4G Module (Huawei ME906s) - -12d1156c -# Huawei E3272/E3276 -# Huawei ME906s-158 -# HP lt4112 Gobi 4G Module (Huawei ME906E) -# HP lt4132 LTE/HSPA+ 4G Module (Huawei ME906s) - -12d115c1 -# Huawei E3272/E3276 -# Huawei ME906s-158 -# HP lt4112 Gobi 4G Module (Huawei ME906E) -# HP lt4132 LTE/HSPA+ 4G Module (Huawei ME906s) - -1435d181 -# WNC D18QC -# WNC D19QA - -1435d191 -# WNC D18QC -# WNC D19QA - -16907588 -# ASKEY WWHC050 - -19d20167 -# ZTE MF821 - -19d20189 -# ZTE MF28D -# ZTE MF290 - -19d21275 -# ZTE P685M -# ZTE ZM8630A - -19d21432 -# ZTE MF286 -# ZTE MF286A - -19d21485 -# ZTE MF286D -# ZTE MF289F - -19d21489 -# ZTE MF286R - -1bc71040 -# Telit LM940 - -1bc71201 -# Telit LE910-EUG - -1e0e9000 -# SIMCOM SIM7906 -# SIMCOM SIM8200EA-M2 - -1e0e9001 -# SIMCOM SIM7906 -# SIMCOM SIM8200EA-M2 - -1e0e9003 -# SIMCOM SIM7906 -# SIMCOM SIM8200EA-M2 - -1e2d00b3 -# Cinterion MV31-W -# Thales MV31-W -# T99W175 - -1e2d00b7 -# Cinterion MV31-W -# Thales MV31-W -# T99W175 - -20202033 -# BroadMobi BM806U - -2c7c0125 -# Quectel EC20 -# Quectel EC25 - -2c7c0306 -# Quectel EP06 -# Quectel EG06 -# Quectel EM06 - -2c7c030b -# Quectel EP06 -# Quectel EG06 -# Quectel EM06 - -2c7c0512 -# Quectel EG18-EA -# Quectel EM12-G -# Quectel EM160R-GL - -2c7c0620 -# Quectel EG18-EA -# Quectel EM12-G -# Quectel EM160R-GL - -2c7c0800 -# Quectel RG500Q-EA -# Quectel RG502Q-EA -# Quectel RM500U-CNV -# Quectel RM520N-GL - -2c7c0801 -# Quectel RG500Q-EA -# Quectel RG502Q-EA -# Quectel RM500U-CNV -# Quectel RM520N-GL - -2c7c0900 -# Quectel RG500Q-EA -# Quectel RG502Q-EA -# Quectel RM500U-CNV -# Quectel RM520N-GL - -2c7c6026 -# Quectel EC200T-EU - -2cb70007 -# Fibocom L850 - -2cb70104 -# Fibocom FM150 - -2cb70104 -# Fibocom FM190 - -2cd20001 -# Mikrotik R11e-LTE -# COPS - -2cd20004 -# Mikrotik R11e-LTE6 - -413c81b1 -# DW5809e Dell Wireless 5809e Gobi 4G LTE Mobile Broadband Card (EM7305) -# DW5811e Snapdragon X7 LTE (EM7455B) -# Sierra Wireless EM7455 - -413c81b6 -# DW5809e Dell Wireless 5809e Gobi 4G LTE Mobile Broadband Card (EM7305) -# DW5811e Snapdragon X7 LTE (EM7455B) -# Sierra Wireless EM7455 - -413c81d7 -# DW5821e Snapdragon X20 LTE - -8087095a -# Fibocom L860 -``` -
- -### Supported pci devices (serial mode): - - -
- Pokaż | Show me - -``` bash -105be0b0 -# Dell DW5930e -# Foxconn T99W175 - -17cb0306 -# Quectel RM500Q-AE - -17cb0308 -# Quectel RG500Q-EA -# Quectel RG502Q-EA -# Quectel RM500U-CNV -# Quectel RM520N-GL -# Quectel RM520N-GLAP - -17cb5201 -# Quectel RG500Q-EA -# Quectel RG502Q-EA -# Quectel RM500U-CNV -# Quectel RM520N-GL - -1eac1004 -# Quectel RM520N-GLAP -``` -
- - -### Design concept / Koncepcja wyglądu - -> "Modem(s)" window / Okno Modem(-ów): - -![](https://github.com/4IceG/Personal_data/blob/master/zrzuty/modemdata/mdlast3a.png?raw=true) - -dark - -![](https://github.com/4IceG/Personal_data/blob/master/zrzuty/modemdata/mdlast3b.png?raw=true) - -![](https://github.com/4IceG/Personal_data/blob/master/zrzuty/modemdata/BTSearch.png?raw=true) - -![](https://github.com/4IceG/Personal_data/blob/master/zrzuty/modemdata/modemdata_upd_modal.png?raw=true) - -> "Diagnostics" window / Okno diagnostyki: - -![](https://github.com/4IceG/Personal_data/blob/master/zrzuty/modemdata/mdd1.png?raw=true) - -> "Defined modems" window / Okno zdefiniowanych modem(-ów): - -![](https://github.com/4IceG/Personal_data/blob/master/zrzuty/modemdata/mlaswer.png?raw=true) - -![](https://github.com/4IceG/Personal_data/blob/master/zrzuty/modemdata/masdad.png?raw=true) - -![](https://github.com/4IceG/Personal_data/blob/master/zrzuty/modemdata/md3d.png?raw=true) - -> "Configuration" window / Okno konfiguracji pakietu: - -![](https://github.com/4IceG/Personal_data/blob/master/zrzuty/modemdata/Package%20configuration%20bts.png?raw=true) - -> "Package update and support" window / Okno aktualizacji pakietu i wsparcia: - -![](https://github.com/4IceG/Personal_data/blob/master/zrzuty/modemdata/md5b.png?raw=true) - -### Thanks to / Podziękowania dla -- [obsy (Cezary Jackiewicz)](https://github.com/obsy) -- [Users of the eko.one.pl forum](https://eko.one.pl/forum/viewtopic.php?id=20096) diff --git a/modemdata/luci-app-modemdata/LICENSE b/modemdata/luci-app-modemdata/LICENSE deleted file mode 100644 index 67028ac34..000000000 --- a/modemdata/luci-app-modemdata/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2025 Rafał Wabik (IceG) - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE.