ambientd/ambientd.c3

269 lines
6.6 KiB
Text
Raw Normal View History

module ambientd;
2024-10-22 22:51:50 +02:00
import std::io;
import std::io::path;
import std::os::env;
import std::os::posix;
import std::collections::list;
import std::collections::object;
import std::encoding::ini;
2024-10-22 22:51:50 +02:00
import std::thread;
import std::time;
import std::math;
import libc;
2024-10-22 22:51:50 +02:00
alias StrList = List{String};
2024-10-22 22:51:50 +02:00
const String DEF_LIGHT_SENSOR_PATH = "/sys/bus/iio/devices/iio:device0/in_illuminance_raw";
const String DEF_BACKLIGHT_PATH = "/sys/class/backlight/intel_backlight";
const float DEF_SCREEN_NIT = 400.0;
const float DEF_SENSOR_K = 0.98;
const int DEF_UPDATE_INTERVAL_MS = 200;
2024-10-22 22:51:50 +02:00
const String[*] CONFIG_PATHS = {
"$XDG_CONFIG_HOME/ambientd/ambientd.ini",
"$HOME/.config/ambientd/ambientd.ini",
"/etc/ambientd/ambientd.ini",
};
2024-10-22 22:51:50 +02:00
2024-10-24 23:37:52 +02:00
String light_sensor_path = DEF_LIGHT_SENSOR_PATH;
String backlight_path = DEF_BACKLIGHT_PATH;
float backlight_max_luminance = DEF_SCREEN_NIT;
float light_sensor_k = DEF_SENSOR_K;
int update_interval_ms = DEF_UPDATE_INTERVAL_MS;
2024-10-22 22:51:50 +02:00
struct LuminanceFilter {
float x; // estimate
float p; // error
float r; // mesurement noise
float q; // process noise
2024-10-22 22:51:50 +02:00
}
fn float LuminanceFilter.update(&self, int reading, float dt)
2024-10-22 22:51:50 +02:00
{
float x_1 = self.x;
float p_1 = self.p + self.q * dt;
float k = p_1 / (p_1 + self.r);
self.x = x_1 + k * ((float)reading - x_1);
self.p = (1.0 - k) * p_1;
return self.x;
}
2024-10-22 22:51:50 +02:00
fn void LuminanceFilter.init(&self, int reading)
{
self.p = 0.0;
self.x = (float)reading;
2024-10-22 22:51:50 +02:00
}
// TODO: this should be configurable
LuminanceFilter filter = {.r = 5.0, .q = 0.1};
fn String? String.to_expanded_path(&str, Allocator allocator, String[] extra_env = {})
2024-10-22 22:51:50 +02:00
{
@pool() {
StrList l;
l.tinit();
Path str_path = str.to_tpath()!;
l.push(str_path.basename());
Path? p = str_path.parent();
while (true) {
Path? x;
if (try p) {
String n = p.basename();
l.push("/");
2024-10-22 22:51:50 +02:00
// env variable
if (n.starts_with("$")) {
String? e = env::tget_var(n[1..]);
if (catch e) {
// var is not in regular environment, check extra_env
bool in_extra = false;
foreach_r (j: extra_env) {
if (j.starts_with(n[1..])) {
l.push(j.strip_suffix(n[1..])[1..]);
in_extra = true;
break;
}
}
if (!in_extra) {
return NOT_FOUND~;
}
} else {
l.push(e);
}
} else {
l.push(n);
}
x = p.parent();
} else {
break;
}
p = x;
}
l.reverse();
String path;
path = string::tjoin(l.array_view(), path);
return (String)path::new(allocator, path);
};
2024-10-22 22:51:50 +02:00
}
fn void update_config()
2024-10-29 13:32:02 +01:00
{
bool config_found = false;
foreach (c: CONFIG_PATHS) {
String? p = c.to_expanded_path(tmem);
if (catch e = p) {
io::eprintfn("Error expanding path %s: %s", c, e);
continue;
}
if (!file::exists(p)) {
io::printfn("%s not present", p);
continue;
}
// Read file
File f = file::open(p, "r")!!;
Object*? o = ini::load_temp(&f).get(ini::GLOBAL);
if (catch e = o) {
io::eprintfn("Error reading config file %s: %s", p, e);
continue;
}
// Read backlight device folder
String? backlight = o.get_string("backlight");
if (catch e = backlight) {
io::eprintfn("Error reading backlight path from config: %s", e);
continue;
}
if (!file::is_dir(backlight)) {
io::eprintfn("Error setting the backlight path: path should be a directory");
continue;
}
backlight_path = backlight;
// Read light sensor device path
String? sensor = o.get_string("sensor");
if (catch e = sensor) {
io::eprintfn("Error reading luminance sensor path from config: %s", e);
continue;
}
light_sensor_path = sensor;
// Optional configurations
// Read the max screen brightness
if (try x = o.get_string("max_brightness").to_float()) {
backlight_max_luminance = (float)x;
}
// Read the light sensor factor
if (try x = o.get_string("sensor_factor").to_float()) {
light_sensor_k = (float)x;
}
// Read the light sensor factor
if (try x = o.get_string("update_interval").to_int()) {
update_interval_ms = x;
}
config_found = true;
break;
}
if (!config_found) {
io::eprintfn("Keping previous configuration");
2024-10-29 13:32:02 +01:00
}
}
macro float @bezier(float t)
2024-10-22 22:51:50 +02:00
{
const float B = 0.66;
const float D = 0.28;
return 3*B*t+(3*D-6*B)*t*t+(1+3*B-3*D)*t*t*t;
}
// https://docs.kernel.org/admin-guide/abi-stable-files.html#abi-file-stable-sysfs-class-backlight
macro int @read_int_from_file(String path) => ((String)file::load_temp(path)[..^2]).to_int() ?? 0;
macro void @write_int_to_file(String path, int i) => (void)file::save(path, string::tformat("%d\n", i));
macro int @get_luminance() => @read_int_from_file(light_sensor_path);
macro int @get_max_backlight() => @read_int_from_file(backlight_path.tconcat("/max_brightness"));
macro int @get_backlight() => @read_int_from_file(backlight_path.tconcat("/brightness"));
macro void @set_backlight(int v) => @write_int_to_file(backlight_path.tconcat("/brightness"), v);
macro bool @is_screen_on() => @read_int_from_file(backlight_path.tconcat("/bl_power")) == 0;
fn void sigusr1_handler(CInt sig, void* act, void* oldact)
2024-10-22 22:51:50 +02:00
{
(void)sig;
(void)act;
(void)oldact;
update_config();
}
2024-10-22 22:51:50 +02:00
// TODO: add command line arguments, like -h, -v and -d
// TODO: daemonization
// TODO: override config file with arguments
fn void main(String[] args)
{
update_config();
2024-10-22 22:51:50 +02:00
posix::install_signal_handler(libc::SIGUSR1, &sigusr1_handler);
filter.init(@get_luminance());
float luminance_old = filter.update(@get_luminance(), 0.0);
int brightness_set = @get_backlight();
int user_offset = 0;
int backlight_max = @get_max_backlight();
Time start = time::now();
2024-10-22 22:51:50 +02:00
while (true) {
if (!@is_screen_on()) {
thread::sleep_ms(5000);
continue;
2024-10-22 22:51:50 +02:00
}
Time now = time::now();
Time dt = (Time)(now - start);
2024-10-22 22:51:50 +02:00
// Check user offset
user_offset = @get_backlight() - brightness_set;
2024-10-22 22:51:50 +02:00
// Filtered luminance, depends heavily on the type of sensor, for the CF-SV7
// seems to be in lux
float luminance = filter.update(@get_luminance(), (float)dt.to_seconds()) * light_sensor_k;
// Percent of the luminance received compared to what the panel can output
float p = min((luminance/backlight_max_luminance), 1.0f);
2024-10-22 22:51:50 +02:00
// int new_brightness = (int)(@bezier(p)*backlight_max);
// The panel should output slightly more light than the ambient, moreover the
// relationship between a percent increase in the backlight power and the
// perceived luminance is non-linear, I think the inverse square law is correct
int new_brightness = (int)(math::pow(p*1.1f, 0.5f)*backlight_max);
brightness_set = new_brightness;
@set_backlight(brightness_set + user_offset);
// io::printfn("%f, %d, %d", luminance, brightness_set, user_offset);
2024-10-22 22:51:50 +02:00
start = now;
thread::sleep_ms(update_interval_ms);
2024-10-22 22:51:50 +02:00
}
}