OpenWrt support for Xiaomi AX9000

made alpine-fan-control work


changes made after installing packages
/usr/lib/lua/model/cbi/alpine-fan-control.lua

-- Copyright 2008 Steven Barth <steven@midlink.org>
-- Copyright 2008 Jo-Philipp Wich <jow@openwrt.org>
-- Licensed to the public under the Apache License 2.0.

local fs  = require "nixio.fs" 
local sys = require "luci.sys"

local m, s, p, ok
local cur_temp, cur_speed

local msg = translate("Service status:") .. " "

local service_running = (luci.sys.call("pgrep -f \"/alpine-fan-controller\" > /dev/null"))==0
if service_running then
    msg = msg .. "<span style=\"color:green;font-weight:bold\">" .. translate("Active") .. "</span>"
else
    msg = msg .. "<span style=\"color:red;font-weight:bold\">" .. translate("Inactive") .. "</span>"
end

msg = msg .. "<br /><br />" .. translate("Current temperature:") .. " "

function get_cur_temp()
	local FILE_TEMP
	local uci = (require "luci.model.uci").cursor()
	local tmp_sens = uci:get("alpine-fan-control", "@alpine-fan-control[0]", "tmp_sens") or ""
	if tmp_sens ~= "" then
		FILE_TEMP = luci.sys.exec("grep -l -F " .. tmp_sens .. " /sys/class/thermal/thermal_zone*/type 2>/dev/null") or ""
		if FILE_TEMP ~= "" then
			FILE_TEMP = luci.sys.exec("dirname '" .. FILE_TEMP .. "' 2>/dev/null | xargs echo -n") or ""
			FILE_TEMP = FILE_TEMP .. "/temp"
			local cur_temp = luci.sys.exec("cat " .. FILE_TEMP .. " 2>/dev/null") or ""
			if cur_temp ~= "" then
				cur_temp = tonumber(cur_temp)
				cur_temp = cur_temp / 1000.0
				return tostring(cur_temp)
			end
		end
	end
	return ""
end
ok, cur_temp = pcall(get_cur_temp)
if ok and cur_temp ~= nil and cur_temp ~= "" then
	msg = msg .. cur_temp .. " °C"
end

msg = msg .. "<br /><br />" .. translate("Current fan speed:") .. " "

function get_cur_speed()
	local FILE_SPEED
	local uci = (require "luci.model.uci").cursor()
	local drv_speed_max = uci:get("alpine-fan-control", "@alpine-fan-control[0]", "drv_speed_max") or "255"
	local fan_cont = uci:get("alpine-fan-control", "@alpine-fan-control[0]", "fan_cont") or ""
	if fan_cont ~= "" then
		FILE_SPEED = luci.sys.exec("grep -l -F " .. fan_cont .. " /sys/class/hwmon/hwmon*/name 2>/dev/null") or ""
		if FILE_SPEED ~= "" then
			FILE_SPEED = luci.sys.exec("dirname '" .. FILE_SPEED .. "' 2>/dev/null | xargs echo -n") or ""
			FILE_SPEED = FILE_SPEED .. "/pwm1"
			local cur_speed = luci.sys.exec("cat " .. FILE_SPEED .. " 2>/dev/null") or ""
			if cur_speed ~= "" then
				cur_speed = tonumber(cur_speed)
				drv_speed_max = tonumber(drv_speed_max)
				return tostring(math.floor(cur_speed * 100.0 / drv_speed_max))
			end
		end
	end
	return ""
end
ok, cur_speed = pcall(get_cur_speed)
if ok and cur_speed ~= nil and cur_speed ~= "" then
	msg = msg .. cur_speed .. "%"
end

m = Map("alpine-fan-control", translate("Fan Control"),	msg)

s = m:section(TypedSection, "alpine-fan-control", translate("Settings"))
s.addremove = false
s.anonymous = true

e = s:option(Flag, "enable", translate("Enabled"), translate("Enables or disables the fan control daemon."))
e.rmempty = false
function e.write(self, section, value)
    if value == "1" then
        luci.sys.call("/etc/init.d/alpine-fan-control start >/dev/null")
    else
        luci.sys.call("/etc/init.d/alpine-fan-control stop >/dev/null")
    end
    return Flag.write(self, section, value)
end

dbg = s:option(Flag, "debug", translate("Debug"), translate("Enables or disables debugging output."))
dbg.datatype = "uinteger"
dbg.default = "0"
dbg.rmempty = false
dbg.optional = false

min_temp = s:option(Value, "min_temp", translate("min_temp"), translate("Temperature for minimum fan state (Celsius)"))
min_temp.datatype = "uinteger"
min_temp.default = "40"
min_temp.rmempty = false
min_temp.optional = false

min_speed = s:option(Value, "min_speed", translate("min_speed"), translate("Fan speed at minimum fan state (Percents)"))
min_speed.datatype = "uinteger"
min_speed.default = "60"
min_speed.rmempty = false
min_speed.optional = false

med_temp = s:option(Value, "med_temp", translate("med_temp"), translate("Temperature for medium fan state (Celsius)"))
med_temp.datatype = "uinteger"
med_temp.default = "45"
med_temp.rmempty = false
med_temp.optional = false

med_speed = s:option(Value, "med_speed", translate("med_speed"), translate("Fan speed at medium fan state (Percents)"))
med_speed.datatype = "uinteger"
med_speed.default = "80"
med_speed.rmempty = false
med_speed.optional = false

max_temp = s:option(Value, "max_temp", translate("max_temp"), translate("Temperature for maximum fan state (Celsius)"))
max_temp.datatype = "uinteger"
max_temp.default = "50"
max_temp.rmempty = false
max_temp.optional = false

max_speed = s:option(Value, "max_speed", translate("max_speed"), translate("Fan speed at maximum fan state (default: 100%)"))
max_speed.datatype = "uinteger"
max_speed.default = "100"
max_speed.rmempty = false
max_speed.optional = false

interval = s:option(Value, "interval", translate("interval"), translate("Interval for hysteresis adjustment (seconds)"))
interval.datatype = "uinteger"
interval.default = "5"
interval.rmempty = false
interval.optional = false

temp_hyst = s:option(Value, "temp_hyst", translate("temp_hyst"), translate("Hysteresis value (Celsius)"))
temp_hyst.datatype = "integer"
temp_hyst.default = "0"
temp_hyst.rmempty = false
temp_hyst.optional = false

tmp_sens = s:option(Value, "tmp_sens", translate("tmp_sens"), translate("Temperature sensor name (default: tmp75)"))
tmp_sens.datatype = "string"
tmp_sens.default = "tmp75"
tmp_sens.rmempty = false
tmp_sens.optional = false

fan_cont = s:option(Value, "fan_cont", translate("fan_cont"), translate("Fan controller name (default: emc230)"))
fan_cont.datatype = "string"
fan_cont.default = "emc230"
fan_cont.rmempty = false
fan_cont.optional = false

return m

/usr/bin/alpine-fan-controller

#!/bin/sh

. /lib/functions.sh

CFGNAME=alpine-fan-control

function get_opt() {
	local opt_name=$1
	local def_value=$2
	local config="@$CFGNAME[0]"
	echo "$( uci_get $CFGNAME "$config" $opt_name $def_value )"
}

# set temps (in degrees C) and corresponding fan speeds 
# in ascending order and with the same amount of values

TEMP_MIN=$(  get_opt min_temp  40 )
SPEED_MIN=$( get_opt min_speed 60 )

TEMP_MED=$(  get_opt med_temp  45 )
SPEED_MED=$( get_opt med_speed 80 )

TEMP_MAX=$(  get_opt max_temp  50 )
SPEED_MAX=$( get_opt max_speed 100 )

TEMP_SENSOR=$( get_opt tmp_sens tmp75  )
FAN_CONT=$(    get_opt fan_cont emc230 )

HYSTERESIS=$( get_opt temp_hyst 0 )
SLEEP_INTERVAL=$( get_opt interval 5 )
DEBUG=$( get_opt debug 0 )

DRV_SPEED_MIN=$( get_opt drv_speed_min 0 )
DRV_SPEED_MAX=$( get_opt drv_speed_max 180 )
CFG_SPEED_MAX=180

LOGPREFIX="alpine-fan-controller"
TEMP_AT_LAST_SPEED_CHANGE=0

function log_error {
	logger "$LOGPREFIX: ERROR: $1"
}

function log_debug {
	if [ "$DEBUG" = "1" ]; then
		logger "$LOGPREFIX: $1"
	fi
}

if [ -z "$TEMP_MED" ]; then
	log_error "Cannot read option 'med_temp' form config!"
	exit 1
fi

# checking for privileges
if [ $UID -ne 0 ]; then
	log_error "Writing to sysfs requires privileges, relaunch as root!"
	echo "Writing to sysfs requires privileges, relaunch as root"
	exit 1
fi

OPTLIST=""
OPTLIST="$OPTLIST [$TEMP_MIN $SPEED_MIN]"
OPTLIST="$OPTLIST [$TEMP_MED $SPEED_MED]"
OPTLIST="$OPTLIST [$TEMP_MAX $SPEED_MAX]"
OPTLIST="$OPTLIST $HYSTERESIS $SLEEP_INTERVAL $DEBUG"
OPTLIST="$OPTLIST $TEMP_SENSOR $FAN_CONT"

# Perform conversion to millidegrees Celsius
TEMP_MIN=$(( $TEMP_MIN * 1000 ))
TEMP_MED=$(( $TEMP_MED * 1000 ))
TEMP_MAX=$(( $TEMP_MAX * 1000 ))

FILE_SPEED=$( grep -l -F $FAN_CONT /sys/class/hwmon/hwmon*/name 2>/dev/null )
[ -f "$FILE_SPEED" ] || { log_error "fan controller $FAN_CONT not found" ; exit 1; }
FILE_SPEED=$( dirname "$FILE_SPEED" 2>/dev/null )
FILE_SPEED=$FILE_SPEED/pwm1
[ -f "$FILE_SPEED" ] || { log_error "Fan controller $FAN_CONT not found" ; exit 1; }

FILE_TEMP=$( grep -l -F $TEMP_SENSOR /sys/class/thermal/thermal_zone*/type 2>/dev/null )
[ -f "$FILE_TEMP" ] || { log_error "thermal sensor $TEMP_SENSOR not found" ; exit 1; }
FILE_TEMP=$( dirname "$FILE_TEMP" 2>/dev/null )
FILE_TEMP=$FILE_TEMP/temp
[ -f "$FILE_TEMP" ] || { log_error "Thermal sensor $TEMP_SENSOR not found" ; exit 1; }

function get_speed {
	local speed=$(cat $FILE_SPEED)
	speed=$(( $CFG_SPEED_MAX * $speed / $DRV_SPEED_MAX ))
	echo $speed
}

function internal_set_speed {
	local speed=$1
	local value=$(( $DRV_SPEED_MAX * $speed / $CFG_SPEED_MAX ))
	[ $value -lt $DRV_SPEED_MIN ] && value=$DRV_SPEED_MIN
	[ $value -gt $DRV_SPEED_MAX ] && value=$DRV_SPEED_MAX
	echo $value > $FILE_SPEED
}

PREV_SET_SPEED=

function set_speed {
	local NEW_SPEED=$1
	local interpolated=$2
	local CUR_SPEED
	local TEMP tmp

	[ "$NEW_SPEED" = "$PREV_SET_SPEED" ] && return
	
	CUR_SPEED=$( get_speed )
	log_debug "current speed: $CUR_SPEED, requested to set speed to $NEW_SPEED"

	TEMP=$(cat $FILE_TEMP)
	[ "$interpolated" = "1" ] && log_debug "interpolated speed value for temperature $TEMP is: $NEW_SPEED"
	tmp=$TEMP_AT_LAST_SPEED_CHANGE
	if [ -z "$tmp" ] || [ "$TEMP" -gt "$tmp" ] || [ $(( TEMP + HYSTERESIS )) -le "$tmp" ]; then
		log_debug "current temp: $TEMP, temp at last change was $TEMP_AT_LAST_SPEED_CHANGE, changing speed to $NEW_SPEED"
		internal_set_speed $NEW_SPEED
		TEMP_AT_LAST_SPEED_CHANGE=$TEMP
	else
		log_debug "not changing speed, we just did at $TEMP_AT_LAST_SPEED_CHANGE, next change when below $((TEMP_AT_LAST_SPEED_CHANGE - HYSTERESIS))"
	fi
	PREV_SET_SPEED=$NEW_SPEED
}

function set_speed_adv {
	# interpolate linearly
	local LOWERTEMP=$1
	local LOWERSPEED=$2
	local HIGHERTEMP=$3
	local HIGHERSPEED=$4
	local TEMP=$5
	[ -z "$TEMP" ] && TEMP=$(cat $FILE_TEMP)
	local value=$(( ( $TEMP - $LOWERTEMP ) * ( $HIGHERSPEED - $LOWERSPEED ) ))
	local SPEED=$(( $value / ( $HIGHERTEMP - $LOWERTEMP ) + $LOWERSPEED ))
	set_speed "$SPEED" 1
}

TEMP_PREV=

function interpolate_speed {
	local TEMP=$(cat $FILE_TEMP)

	if [ "$TEMP" != "$TEMP_PREV" ]; then
		log_debug "current temp: $TEMP"
		TEMP_PREV=$TEMP
	fi

	if [ $TEMP -le $TEMP_MIN ]; then
		# below first point in list, set to min speed
		set_speed $SPEED_MIN
		return
	fi
	if [ $TEMP -gt $TEMP_MAX ]; then
		# above last point in list, set to max speed
		set_speed $SPEED_MAX
		return
	fi
	if [ $TEMP -le $TEMP_MAX ]; then
		set_speed_adv $TEMP_MED $SPEED_MED $TEMP_MAX $SPEED_MAX $TEMP
		return
	fi
	if [ $TEMP -le $TEMP_MED ]; then
		set_speed_adv $TEMP_MIN $SPEED_MIN $TEMP_MED $SPEED_MED $TEMP
		return
	fi
}

function reset_on_exit {
	echo "exiting, going to max fan speed for safety..."
	internal_set_speed 100
	exit 0
}

# always try to reset fans on exit
trap "reset_on_exit" SIGINT SIGTERM

function run_daemon {
	while true; do
		interpolate_speed
		sleep $SLEEP_INTERVAL
	done
}

logger "$LOGPREFIX: run daemon:" $OPTLIST
# finally start the loop
run_daemon

/etc/config/alpine-fan-control


config alpine-fan-control
	option enable '1'
	option debug '0'
	option min_temp '40'
	option min_speed '20'
	option med_temp '45'
	option med_speed '50'
	option max_temp '50'
	option max_speed '100'
	option interval '5'
	option temp_hyst '0'
	option tmp_sens 'cpu0'
	option fan_cont 'emc230'
	option drv_speed_min '0'
	option drv_speed_max '180'

6 Likes