~kennylevinsen/wlsunset

edf3595e0b82ba6fee53d9b0f48792a73b4038c7 — Kenny Levinsen 14 days ago 4167131
WIP works??
4 files changed, 226 insertions(+), 108 deletions(-)

M color_math.c
M color_math.h
M main.c
M testcalc.c
M color_math.c => color_math.c +44 -47
@@ 2,80 2,77 @@
#include <math.h>
#include <errno.h>
#include <time.h>
#include <stdio.h>
#include "color_math.h"

#define SOLAR_HORIZON		90.833
#define SOLAR_START_TWILIGHT	6.0
#define SOLAR_END_TWILIGHT	-3.0
#define RAD2DEG(rad) ((rad) * 180.0 / M_PI)
#define DEG2RAD(deg) ((deg) * M_PI / 180.0)

static int is_leap(int year) {
	return (year % 4 == 0 && year % 100 != 0) || year % 400 == 0;
}
static double SOLAR_START_TWILIGHT = DEG2RAD(90.833 + 6.0);
static double SOLAR_END_TWILIGHT   = DEG2RAD(90.833 - 3.0);

static int days_in_year(int year) {
	return is_leap(year) ? 366 : 365;
}

static double radians(double degrees) {
	return degrees * M_PI / 180.0;
}

static double degrees(double radians) {
	return radians * 180.0 / M_PI;
	int leap = (year % 4 == 0 && year % 100 != 0) || year % 400 == 0;
	return leap ? 366 : 365;
}

static int sign(double x) {
	return signbit(x) == 0;
static double year_rad(int year, int day) {
	return 2 * M_PI / (double)days_in_year(year) * day;
}

void calc_sun(struct tm *tm, double longitude, double latitude, struct sun *sun) {
static double equation_of_time(double year_rad) {
	// https://www.esrl.noaa.gov/gmd/grad/solcalc/solareqns.PDF
	double year_rad = 2 * M_PI / days_in_year(tm->tm_year) * (tm->tm_yday - 1);
	double eqtime = 229.18 * (0.000075 +
	return 229.18 * (0.000075 +
		0.001868 * cos(year_rad) -
		0.032077 * sin(year_rad) -
		0.014615 * cos(2*year_rad) -
		0.040849 * sin(2*year_rad));
	double decl = 0.006918 -
}

static double sun_declination(double year_rad) {
	// https://www.esrl.noaa.gov/gmd/grad/solcalc/solareqns.PDF
	return 0.006918 -
		0.399912 * cos(year_rad) +
		0.070257 * sin(year_rad) -
		0.006758 * cos(2*year_rad) +
		0.000907 * sin(2*year_rad) -
		0.002697 * cos(3*year_rad) +
		0.00148 * sin(3*year_rad);
}

	double latitude_rad = radians(latitude);
static double sun_hour_angle(double latitude_rad, double declination_rad, double target_sun_rad) {
	// https://www.esrl.noaa.gov/gmd/grad/solcalc/solareqns.PDF
	return acos(cos(target_sun_rad) /
		cos(latitude_rad) * cos(declination_rad) -
		tan(latitude_rad) * tan(declination_rad));
}

	double latdecl_cos = cos(latitude_rad) * cos(decl);
	double latdecl_tan = tan(latitude_rad) * tan(decl);
static time_t hour_angle_to_time(double longitude, double eqtime, double hour_angle) {
	// https://www.esrl.noaa.gov/gmd/grad/solcalc/solareqns.PDF
	return isnan(hour_angle) ? -1 : (720 - 4 * (longitude + hour_angle) - eqtime) * 60;
}

	double twilight_cos = cos(radians(SOLAR_HORIZON + SOLAR_START_TWILIGHT)) / latdecl_cos - latdecl_tan;
	double daylight_cos = cos(radians(SOLAR_HORIZON + SOLAR_END_TWILIGHT)) / latdecl_cos - latdecl_tan;
static enum sun_condition condition(double latitude_rad, double sun_declination) {
	int sign_lat = signbit(latitude_rad) == 0;
	int sign_decl = signbit(sun_declination) == 0;
	return sign_lat == sign_decl ? MIDNIGHT_SUN : POLAR_NIGHT;
}

	double twilight_ha = degrees(acos(twilight_cos));
	double daylight_ha = degrees(acos(daylight_cos));
enum sun_condition calc_sun(struct tm *tm, double longitude_deg, double latitude, struct sun *sun) {
	double latitude_rad = DEG2RAD(latitude);

	fprintf(stderr, "twilight: cos %lf, ha %lf, daylight: cos %lf, ha %lf\n",
			twilight_cos, twilight_ha, daylight_cos, daylight_ha);
	double ya = year_rad(tm->tm_year + 1900, tm->tm_yday);
	double decl = sun_declination(ya);
	double eqtime = equation_of_time(ya);

	if (!isnan(twilight_ha)) {
			sun->dawn = (720 - 4 * (longitude + fabs(twilight_ha)) - eqtime) * 60;
		sun->dusk = (720 - 4 * (longitude - fabs(twilight_ha)) - eqtime) * 60;
	} else {
		sun->dawn = -1;
		sun->dusk = -1;
		sun->condition = sign(latitude) != sign(decl) ? MIDNIGHT_SUN : POLAR_NIGHT;
	}
	double ha_twilight = RAD2DEG(sun_hour_angle(latitude_rad, decl, SOLAR_START_TWILIGHT));
	double ha_daylight = RAD2DEG(sun_hour_angle(latitude_rad, decl, SOLAR_END_TWILIGHT));

	if (!isnan(daylight_ha)) {
		sun->sunrise = (720 - 4 * (longitude + fabs(daylight_ha)) - eqtime) * 60;
		sun->sunset = (720 - 4 * (longitude - fabs(daylight_ha)) - eqtime) * 60;
	} else {
		sun->sunrise = -1;
		sun->sunset = -1;
		sun->condition = sign(latitude) != sign(decl) ? MIDNIGHT_SUN : POLAR_NIGHT;
	}
	sun->dawn = hour_angle_to_time(longitude_deg, eqtime, fabs(ha_twilight));
	sun->dusk = hour_angle_to_time(longitude_deg, eqtime, -fabs(ha_twilight));
	sun->sunrise = hour_angle_to_time(longitude_deg, eqtime, fabs(ha_daylight));
	sun->sunset = hour_angle_to_time(longitude_deg, eqtime, -fabs(ha_daylight));
	
	return isnan(ha_twilight) || isnan(ha_daylight) ? condition(latitude_rad, decl) : NORMAL;
}

static int illuminant_d(int temp, double *x, double *y) {

M color_math.h => color_math.h +2 -2
@@ 7,6 7,7 @@ enum sun_condition {
	NORMAL,
	MIDNIGHT_SUN,
	POLAR_NIGHT,
	SUN_CONDITION_LAST
};

struct sun {


@@ 14,10 15,9 @@ struct sun {
	time_t sunrise;
	time_t sunset;
	time_t dusk;
	enum sun_condition condition;
};

void calc_sun(struct tm *tm, double longitude, double latitude, struct sun *sun);
enum sun_condition calc_sun(struct tm *tm, double longitude, double latitude, struct sun *sun);
double clamp(double value);
void calc_whitepoint(int temp, double *rw, double *gw, double *bw);


M main.c => main.c +157 -36
@@ 38,7 38,7 @@ static time_t get_time_sec(time_t *tloc) {
	(void)tloc;
	struct timespec realtime;
	clock_gettime(CLOCK_REALTIME, &realtime);
	time_t now = start + ((realtime.tv_sec - offset) * 1000 + realtime.tv_nsec / 1000000);
	time_t now = start + ((realtime.tv_sec - offset) * 100000 + realtime.tv_nsec / 10000);
	struct tm tm;
	localtime_r(&now, &tm);
	fprintf(stderr, "time in termina: %02d:%02d:%02d, %d/%d/%d\n", tm.tm_hour, tm.tm_min, tm.tm_sec,


@@ 48,8 48,8 @@ static time_t get_time_sec(time_t *tloc) {
static int set_timer(timer_t timer, time_t deadline) {
	time_t diff = deadline - offset;
	struct itimerspec timerspec = {{0}, {0}};
	timerspec.it_value.tv_sec = offset + diff / 1000;
	timerspec.it_value.tv_nsec = (diff % 1000) * 1000000;
	timerspec.it_value.tv_sec = offset + diff / 100000;
	timerspec.it_value.tv_nsec = (diff % 100000) * 10000;
	return timer_settime(timer, TIMER_ABSTIME, &timerspec, NULL);
}
#else


@@ 67,6 67,14 @@ static int set_timer(timer_t timer, time_t deadline) {
}
#endif

static time_t round_day(time_t now) {
	return now - (now % 86400);
}

static time_t tomorrow(time_t now) {
	return round_day(now) + 86400;
}

struct config {
	int high_temp;
	int low_temp;


@@ 75,14 83,26 @@ struct config {
	double latitude;
};

enum state {
	STATE_INITIAL,
	STATE_NORMAL,
	STATE_TRANSITION,
	STATE_STATIC,
};

struct context {
	struct config config;
	struct sun sun;

	enum state state;
	enum sun_condition condition;

	time_t dawn_step_time;
	time_t dusk_step_time;
	time_t calc_day;

	time_t anim_start;

	bool new_output;
	struct wl_list outputs;
	timer_t timer;


@@ 311,72 331,170 @@ static void print_trajectory(struct context *ctx, time_t day) {
		fprintf(stderr, " dusk N/A,");
	}

	fprintf(stderr, " condition: %s\n", sun_condition_str[ctx->sun.condition]);
	fprintf(stderr, " condition: %s\n", sun_condition_str[ctx->condition]);
}

static int anim_kelvin_step = 25;

static void recalc_stops(struct context *ctx, time_t now) {
	time_t day = now - (now % 86400);
	if (day == ctx->calc_day) {
	time_t day = round_day(now);
	time_t last_day = ctx->calc_day;
	if (day == last_day) {
		return;
	}
	ctx->calc_day = day;

	struct sun sun;
	struct tm tm = { 0 };
	gmtime_r(&day, &tm);
	calc_sun(&tm, ctx->config.longitude, ctx->config.latitude, &ctx->sun);
	enum sun_condition cond = calc_sun(&tm, ctx->config.longitude, ctx->config.latitude, &sun);

	if (ctx->condition != cond) {
		enum state state = STATE_TRANSITION;
		switch (cond) {
		case NORMAL:
			state = STATE_NORMAL;
			if (ctx->condition == MIDNIGHT_SUN) {
				// Yesterday had no sunset, so remove our sunrise.
				sun.dawn = -1;
				sun.sunrise = -1;
			}
			break;
		case MIDNIGHT_SUN:
		case POLAR_NIGHT:
			if (ctx->state == STATE_INITIAL) {
				state = STATE_STATIC;
				break;
			} else if (ctx->state == STATE_STATIC) {
				fprintf(stderr, "warning: direct %s to %s transition\n",
						sun_condition_str[ctx->condition], sun_condition_str[cond]);
				state = STATE_STATIC;
				break;
			}

			// Borrow values from yesterday for animation to our new static state.
			sun.dawn = sun.dawn != -1 ? sun.dawn :
				ctx->sun.dawn - last_day;
			sun.sunrise = sun.sunrise != -1 ? sun.sunrise :
				ctx->sun.sunrise - last_day;
			sun.sunset = sun.sunset != -1 ? sun.sunset :
				ctx->sun.sunset - last_day;
			sun.dusk = sun.dusk != -1 ? sun.dusk :
				ctx->sun.dusk - last_day;

	if (ctx->config.duration != -1) {
		ctx->sun.dawn = ctx->sun.sunrise - ctx->config.duration;
		ctx->sun.dusk = ctx->sun.sunset + ctx->config.duration;
			break;
		default:
			abort();
		}
		ctx->state = state;
		ctx->condition = cond;
	}
	ctx->sun.dawn += day;
	ctx->sun.sunrise += day;
	ctx->sun.sunset += day;
	ctx->sun.dusk += day;

	ctx->sun.dawn = sun.dawn + day;
	ctx->sun.sunrise = sun.sunrise + day;
	ctx->sun.sunset = sun.sunset + day;
	ctx->sun.dusk = sun.dusk + day;

	int temp_diff = ctx->config.high_temp - ctx->config.low_temp;
	if (ctx->sun.condition == NORMAL) {
		ctx->dawn_step_time = (ctx->sun.sunrise - ctx->sun.dawn) * anim_kelvin_step / temp_diff;
		ctx->dusk_step_time = (ctx->sun.dusk - ctx->sun.sunset) * anim_kelvin_step / temp_diff;
	}
	ctx->dawn_step_time = (ctx->sun.sunrise - ctx->sun.dawn) * anim_kelvin_step / temp_diff;
	ctx->dusk_step_time = (ctx->sun.dusk - ctx->sun.sunset) * anim_kelvin_step / temp_diff;

	print_trajectory(ctx, day);
	return;
}

static int interpolate_temperature(time_t now, time_t start, time_t stop, int temp_start, int temp_stop) {
	if (start == stop) {
		return stop;
	}
	double time_pos = clamp((double)(now - start) / (double)(stop - start));
	int temp_pos = (double)(temp_stop - temp_start) * time_pos;
	return temp_start + temp_pos;
}

static int get_temperature(const struct context *ctx, int last, time_t now) {
static int get_temperature_normal(const struct context *ctx, time_t now) {
	if (now < ctx->sun.dawn) {
		return ctx->sun.condition == MIDNIGHT_SUN ? ctx->config.high_temp : ctx->config.low_temp;
	} else if (now < ctx->sun.sunrise && (last == -1 || last < ctx->config.high_temp)) {
		return ctx->config.low_temp;
	} else if (now < ctx->sun.sunrise) {
		return interpolate_temperature(now, ctx->sun.dawn, ctx->sun.sunrise, ctx->config.low_temp, ctx->config.high_temp);
	} else if (now < ctx->sun.sunset) {
		return ctx->sun.condition == POLAR_NIGHT ? ctx->config.low_temp : ctx->config.high_temp;
	} else if (now < ctx->sun.dusk && (last == -1 || last > ctx->config.low_temp)) {
		return ctx->config.high_temp;
	} else if (now < ctx->sun.dusk) {
		return interpolate_temperature(now, ctx->sun.sunset, ctx->sun.dusk, ctx->config.high_temp, ctx->config.low_temp);
	} else {
		return ctx->sun.condition == MIDNIGHT_SUN ? ctx->config.high_temp : ctx->config.low_temp;
		return ctx->config.low_temp;
	}
}

static void update_timer(struct context *ctx, timer_t timer, time_t now) {
	time_t deadline;
static int get_temperature_transition(const struct context *ctx, time_t now) {
	switch (ctx->condition) {
	case MIDNIGHT_SUN:
		if (now < ctx->sun.sunrise) {
			return get_temperature_normal(ctx, now);
		}
		return ctx->config.high_temp;
	case POLAR_NIGHT:
		return ctx->config.low_temp;
	default:
		abort();
	}
}

static int get_temperature(const struct context *ctx, time_t now) {
	switch (ctx->state) {
	case STATE_NORMAL:
		return get_temperature_normal(ctx, now);
	case STATE_TRANSITION:
		return get_temperature_transition(ctx, now);
	case STATE_STATIC:
		return ctx->condition == MIDNIGHT_SUN ? ctx->config.high_temp : ctx->config.low_temp;
	default:
		abort();
	}
}

static time_t get_deadline_normal(struct context *ctx, time_t now) {
	if (now < ctx->sun.dawn) {
		deadline = ctx->sun.dawn;
	} else if (now < ctx->sun.sunrise && ctx->sun.condition == NORMAL) {
		deadline = now + ctx->dawn_step_time;
		return ctx->sun.dawn;
	} else if (now < ctx->sun.sunrise) {
		return now + ctx->dawn_step_time;
	} else if (now < ctx->sun.sunset) {
		deadline = ctx->sun.sunset;
	} else if (now < ctx->sun.dusk && ctx->sun.condition == NORMAL) {
		deadline = now + ctx->dusk_step_time;
		return ctx->sun.sunset;
	} else if (now < ctx->sun.dusk) {
		return now + ctx->dusk_step_time;
	} else {
		deadline = (now / 86400 + 1) * 86400;
		return tomorrow(now);
	}
}

static time_t get_deadline_transition(struct context *ctx, time_t now) {
	switch (ctx->condition) {
	case MIDNIGHT_SUN:
		if (now < ctx->sun.sunrise) {
			return get_deadline_normal(ctx, now);
		}
		// fallthrough
	case POLAR_NIGHT:
		return tomorrow(now);
	default:
		abort();
	}
}

static void update_timer(struct context *ctx, timer_t timer, time_t now) {
	time_t deadline;
	switch (ctx->state) {
	case STATE_NORMAL:
		deadline = get_deadline_normal(ctx, now);
		break;
	case STATE_TRANSITION:
		deadline = get_deadline_transition(ctx, now);
		break;
	case STATE_STATIC:
		deadline = tomorrow(now);
		break;
	default:
		abort();
	}

	assert(deadline > now);


@@ 452,6 570,8 @@ int main(int argc, char *argv[]) {
	// Initialize defaults
	struct context ctx = {
		.sun = { 0 },
		.condition = SUN_CONDITION_LAST,
		.state = STATE_INITIAL,
		.config = {
			.high_temp = 6500,
			.low_temp = 4000,


@@ 524,10 644,11 @@ int main(int argc, char *argv[]) {
	recalc_stops(&ctx, now);
	update_timer(&ctx, ctx.timer, now);

	int temp = get_temperature(&ctx, 0, now);
	int temp = get_temperature(&ctx, now);
	int old_temp = temp;

	if (dryrun) {
		set_temperature(&ctx.outputs, temp, gamma);
		while (wait(NULL)) {
			if (timer_fired) {
				timer_fired = false;


@@ 536,7 657,7 @@ int main(int argc, char *argv[]) {
				recalc_stops(&ctx, now);
				update_timer(&ctx, ctx.timer, now);

				if ((temp = get_temperature(&ctx, old_temp, now)) != old_temp) {
				if ((temp = get_temperature(&ctx, now)) != old_temp) {
					old_temp = temp;
					set_temperature(&ctx.outputs, temp, gamma);
				}


@@ 576,7 697,7 @@ int main(int argc, char *argv[]) {
			recalc_stops(&ctx, now);
			update_timer(&ctx, ctx.timer, now);

			if ((temp = get_temperature(&ctx, old_temp, now)) != old_temp) {
			if ((temp = get_temperature(&ctx, now)) != old_temp) {
				old_temp = temp;
				ctx.new_output = false;
				set_temperature(&ctx.outputs, temp, gamma);

M testcalc.c => testcalc.c +23 -23
@@ 14,39 14,39 @@ static const char *sun_condition_str[] = {
	NULL,
};

static void print_trajectory(struct sun *sun, time_t day) {
	fprintf(stderr, "calculated sun trajectory:");
static void print_trajectory(struct sun *sun, enum sun_condition cond) {
	fprintf(stderr, "calculated sun trajectory: \n");

	struct tm tm;
	if (sun->dawn >= day) {
	if (sun->dawn != -1) {
		localtime_r(&sun->dawn, &tm);
		fprintf(stderr, " dawn %0d:%02d,", tm.tm_hour, tm.tm_min);
		fprintf(stderr, " - dawn       %02d:%02d\n", tm.tm_hour, tm.tm_min);
	} else {
		fprintf(stderr, " dawn N/A,");
		fprintf(stderr, " - dawn       N/A\n");
	}

	if (sun->sunrise >= day) {
	if (sun->sunrise != -1) {
		localtime_r(&sun->sunrise, &tm);
		fprintf(stderr, " sunrise %0d:%02d,", tm.tm_hour, tm.tm_min);
		fprintf(stderr, " - sunrise    %02d:%02d\n", tm.tm_hour, tm.tm_min);
	} else {
		fprintf(stderr, " sunrise N/A,");
		fprintf(stderr, " - sunrise    N/A\n");
	}

	if (sun->sunset >= day) {
	if (sun->sunset != -1) {
		localtime_r(&sun->sunset, &tm);
		fprintf(stderr, " sunset %0d:%02d,", tm.tm_hour, tm.tm_min);
		fprintf(stderr, " - sunset     %02d:%02d\n", tm.tm_hour, tm.tm_min);
	} else {
		fprintf(stderr, " sunset N/A,");
		fprintf(stderr, " - sunset     N/A\n");
	}

	if (sun->dusk >= day) {
	if (sun->dusk != -1) {
		localtime_r(&sun->dusk, &tm);
		fprintf(stderr, " dusk %0d:%02d,", tm.tm_hour, tm.tm_min);
		fprintf(stderr, " - dusk       %02d:%02d\n", tm.tm_hour, tm.tm_min);
	} else {
		fprintf(stderr, " dusk N/A,");
		fprintf(stderr, " - dusk       N/A\n");
	}

	fprintf(stderr, " condition: %s\n", sun_condition_str[sun->condition]);
	fprintf(stderr, " - condition  %s\n", sun_condition_str[cond]);
}

int main(int argc, char *argv[]) {


@@ 72,19 72,19 @@ int main(int argc, char *argv[]) {

	struct tm tm;
	localtime_r(&now, &tm);
	fprintf(stderr, "time in termina: %02d:%02d:%02d, %d/%d/%d\n", tm.tm_hour, tm.tm_min, tm.tm_sec,
			tm.tm_mday, tm.tm_mon+1, tm.tm_year + 1900);
	fprintf(stderr, "time in termina: %02d:%02d:%02d, %d/%d/%d, %ld\n", tm.tm_hour, tm.tm_min, tm.tm_sec,
			tm.tm_mday, tm.tm_mon+1, tm.tm_year + 1900, now);

	struct sun sun = { 0 };
	time_t day = now - (now % 86400);
	gmtime_r(&day, &tm);
	calc_sun(&tm, longitude, latitude, &sun);
	enum sun_condition cond = calc_sun(&tm, longitude, latitude, &sun);
	
	sun.dawn += day;
	sun.sunrise += day;
	sun.sunset += day;
	sun.dusk += day;
	sun.dawn = sun.dawn == -1 ? -1 : sun.dawn + day;
	sun.sunrise = sun.sunrise == -1 ? -1 : sun.sunrise + day;
	sun.sunset = sun.sunset == -1 ? -1 : sun.sunset + day;
	sun.dusk = sun.dusk == -1 ? -1 : sun.dusk + day;

	print_trajectory(&sun, day);
	print_trajectory(&sun, cond);
	return EXIT_SUCCESS;
}