/*
* clock - clock resource for zavai
*
- * Copyright (C) 2009 Enrico Zini <enrico@enricozini.org>
+ * Copyright (C) 2009--2010 Enrico Zini <enrico@enricozini.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
GPS
}
+/*
+TODO: schedule alarms via at
+
+Uses the 'z' queue.
+
+atq -q z can be used to list the jobs (for example, at startup, or after a job has run)
+at -c id can be used to query a job, parsing its contents (which can have
+ comments or variables being set)
+zavai --notify ... can be used to notify the job (and start zavai if it's not running)
+
+Alarm needs to be able to serialize itself to an at invocation and to
+deserialize itself from the output of at -c
+
+Alarm needs to deserialize also a job with no special markers whatsoever: a
+generic at job.
+
+
+
+refresh_alarms()
+{
+ oldtime = next_alarm ? next_alarm.time : 0
+ next_alarm = the first alarm from atq
+ if (oldtime != next_alarm.time)
+ {
+ remove existing triggers
+ (triggers can be skipped if we don't need to support non-zavai alarms)
+ schedule a trigger calling refresh_alarms() at next_alarm.time + 30 seconds
+ (triggers can be skipped if we don't need to support non-zavai alarms)
+ }
+}
+
+at clock constructor: refresh_alarms()
+inotifywait -e close /usr/bin/at -> refresh_alarms() (shows when someone has just used at)
+ (can be skipped if we don't need to support non-zavai alarms)
+at alarm triggered through zavai: refresh_alarms()
+
+
+*/
+
+
+public class Alarm : Object
+{
+ public at.Event ev;
+ public string label;
+
+ // Schedule with at
+ public static void schedule(string timespec, string label) throws Error
+ {
+ string argv[5];
+ argv[0] = "/usr/bin/at";
+ argv[1] = "-q";
+ argv[2] = "z";
+ argv[3] = timespec;
+ argv[4] = null;
+
+ Pid pid;
+ int stdinfd;
+
+ if (!Process.spawn_async_with_pipes("/", argv, null, SpawnFlags.STDERR_TO_DEV_NULL, null, out pid, out stdinfd, null, null))
+ return;
+
+ {
+ FileStream fs = FileStream.fdopen(stdinfd, "w");
+ string display = GLib.Environment.get_variable("DISPLAY");
+ if (display != null)
+ fs.printf("DISPLAY=\"%s\"; export DISPLAY\n", display);
+ fs.printf("# Zavai variables start here\n");
+ fs.printf("ZAVAI_LABEL=\"%s\"\n", label.escape(""));
+ fs.printf("# Zavai commands starts here\n");
+ fs.printf("%s notify \"$ZAVAI_LABEL\"", zavai.config.argv0);
+ }
+
+ Process.close_pid(pid);
+ }
+
+ // Get the label of the job with the given at ID
+ public static string? getLabel(int atID)
+ {
+ string label = null;
+ at.jobContents(atID, fd => {
+ FileStream fs = FileStream.fdopen(fd, "r");
+ while (true)
+ {
+ string? line = fs.read_line();
+ if (line == null) break;
+ if (line.has_prefix("ZAVAI_LABEL=\""))
+ {
+ size_t size = line.size();
+ if (size < 15) continue;
+ label = line.substring(13, (long)(size - 14));
+ label = label.compress();
+ break;
+ }
+ }
+ return true;
+ });
+ return label;
+ }
+}
+
+[DBus (name = "org.enricozini.zavai.Alarm")]
+public class ZavaiClock : Object {
+ public void Notify (string label) {
+ clock.notify_alarm(label);
+ }
+}
+
+public class AlarmTriggerInfo
+{
+ public uint id;
+ public string label;
+ public bool acked;
+ public bool canceled;
+
+ public AlarmTriggerInfo(string label)
+ {
+ id = 0;
+ this.label = label;
+ acked = false;
+ canceled = false;
+ }
+}
+
+public class AlarmTriggerQueue : zavai.Service
+{
+ protected List<AlarmTriggerInfo> queue;
+
+ public signal void triggered(AlarmTriggerInfo info);
+ public signal void acked(AlarmTriggerInfo info);
+ public signal void canceled(AlarmTriggerInfo info);
+
+ public AlarmTriggerQueue()
+ {
+ queue = new List<AlarmTriggerInfo>();
+ }
+
+ public uint enqueue_trigger(AlarmTriggerInfo info)
+ {
+ // Reuse IDs from the associated logger object
+ info.id = zavai.log.log.start("alarm", "Alarm " + info.label);
+ queue.append(info);
+ if (queue.data.id == info.id)
+ triggered(queue.data);
+ return info.id;
+ }
+
+ protected void done_with_first()
+ {
+ var first = queue.data;
+ queue.remove_link(queue);
+ if (queue != null)
+ triggered(queue.data);
+ }
+
+ public void ack(AlarmTriggerInfo info)
+ {
+ if (queue == null || info.id != queue.data.id) return;
+ if (!info.acked && !info.canceled)
+ {
+ info.acked = true;
+ acked(info);
+ zavai.log.log.add(info.id, "alarm acknowledged");
+ zavai.log.log.end(info.id);
+ }
+ done_with_first();
+ }
+
+ public void cancel(AlarmTriggerInfo info)
+ {
+ if (queue == null || info.id != queue.data.id) return;
+ if (!info.acked && !info.canceled)
+ {
+ info.canceled = true;
+ canceled(info);
+ zavai.log.log.add(info.id, "alarm canceled");
+ zavai.log.log.end(info.id);
+ }
+ done_with_first();
+ }
+}
+
public class Clock: zavai.Service
{
protected time_t last_gps_time;
protected time_t last_gps_time_system_time;
protected time_t last_system_time;
- protected dynamic DBus.Object gps_time;
protected uint system_time_timeout;
protected time_t last_minute;
+ protected time_t chosen_time;
+ protected SourceType chosen_type;
+ protected ZavaiClock dbusClock;
+
+ protected dynamic DBus.Object otimed_alarm;
+ protected dynamic DBus.Object rtc;
+ protected SList<Alarm> alarms;
// Ticks once a minute
public signal void minute_changed(long time, SourceType source);
+ public signal void schedule_changed(Alarm? next);
- public Clock()
- {
- name = "clock";
-
+ public Clock()
+ {
+ Object(name: "clock");
+ alarms = null;
+ dbusClock = new ZavaiClock();
last_minute = 0;
last_gps_time = 0;
last_gps_time_system_time = 0;
last_system_time = time_t();
-
- gps_time = zavai.registry.sbus.get_object(
- "org.freesmartphone.ogpsd",
- "/org/freedesktop/Gypsy",
- "org.freedesktop.Gypsy.Time");
- }
-
- private void on_gps_time(dynamic DBus.Object pos, int t)
- {
+ chosen_time = last_system_time;
+
+ // FSO alarm system
+ otimed_alarm = zavai.registry.sbus.get_object(
+ "org.freesmartphone.otimed",
+ "/org/freesmartphone/Time/Alarm",
+ "org.freesmartphone.Time.Alarm");
+
+ rtc = zavai.registry.sbus.get_object(
+ "org.freesmartphone.odeviced",
+ "/org/freesmartphone/Device/RTC/0",
+ "org.freesmartphone.Device.RealtimeClock");
+
+ zavai.registry.sbus.register_object("/org/enricozini/Zavai/Clock", dbusClock);
+ }
+
+ public void notify_alarm(string label)
+ {
+ stderr.printf("Notifying %s\n", label);
+ AlarmTriggerInfo info = new AlarmTriggerInfo(label);
+ alarm_trigger_queue.enqueue_trigger(info);
+ schedule_changed(next_alarm());
+ }
+
+ public Alarm? next_alarm()
+ {
+ at.Event ev;
+ ev = at.earliestID("z");
+ if (ev.deadline == 0)
+ return null;
+ string label = Alarm.getLabel(ev.id);
+ Alarm res = new Alarm();
+ res.ev = ev;
+ res.label = label;
+ return res;
+ }
+
+ public void schedule(string timespec, string label) throws Error
+ {
+ Alarm.schedule(timespec, label);
+ schedule_changed(next_alarm());
+ }
+
+ private void on_gps_time(uint t)
+ {
if (t == 0)
{
last_gps_time_system_time = 0;
last_gps_time_system_time = time_t();
update_time();
}
- }
+ }
private bool on_system_time()
{
private void update_time()
{
- time_t chosen;
- SourceType type;
if (last_gps_time_system_time + 10 > last_system_time)
{
- chosen = last_gps_time;
- type = SourceType.GPS;
+ chosen_time = last_gps_time;
+ chosen_type = SourceType.GPS;
}
else
{
- chosen = last_system_time;
- type = SourceType.SYSTEM;
+ chosen_time = last_system_time;
+ chosen_type = SourceType.SYSTEM;
}
- if (chosen / 60 != last_minute)
+ if (chosen_time / 60 != last_minute)
{
- last_minute = chosen / 60;
- minute_changed(chosen, type);
+ last_minute = chosen_time / 60;
+ minute_changed(chosen_time, chosen_type);
}
}
- /// Request GPS resource
- public override void start()
- {
- if (started) return;
+ /// Request GPS resource
+ public override void start()
+ {
+ if (started) return;
system_time_timeout = Timeout.add(5000, on_system_time);
- gps_time.TimeChanged += on_gps_time;
+ zavai.gps.gps.time_changed += on_gps_time;
last_system_time = time_t();
update_time();
- base.start();
- }
+ base.start();
+ }
- public override void stop()
- {
- if (!started) return;
+ public override void stop()
+ {
+ if (!started) return;
Source.remove(system_time_timeout);
- gps_time.TimeChanged -= on_gps_time;
+ zavai.gps.gps.time_changed -= on_gps_time;
- base.stop();
- }
+ base.stop();
+ }
}
public Clock clock = null;
+public AlarmTriggerQueue alarm_trigger_queue = null;
public void init()
{
clock = new Clock();
-
- zavai.registry.register_service(clock);
+ alarm_trigger_queue = new AlarmTriggerQueue();
}
}