Merge branch 'master' into alarm
[gregoa/zavai.git] / src / clock.vala
1 /*
2  * clock - clock resource for zavai
3  *
4  * Copyright (C) 2009  Enrico Zini <enrico@enricozini.org>
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19  */
20
21 using GLib;
22
23 namespace zavai {
24 namespace clock {
25
26 public enum SourceType
27 {
28         SYSTEM,
29         GPS
30 }
31
32 /*
33 TODO: schedule alarms via at
34
35 Uses the 'z' queue.
36
37 atq -q z  can be used to list the jobs (for example, at startup, or after a job has run)
38 at -c id  can be used to query a job, parsing its contents (which can have
39           comments or variables being set)
40 zavai --notify ...  can be used to notify the job (and start zavai if it's not running)
41
42 Alarm needs to be able to serialize itself to an at invocation and to
43 deserialize itself from the output of at -c
44
45 Alarm needs to deserialize also a job with no special markers whatsoever: a
46 generic at job.
47
48
49
50 refresh_alarms()
51 {
52         oldtime = next_alarm ? next_alarm.time : 0
53         next_alarm = the first alarm from atq
54         if (oldtime != next_alarm.time)
55         {
56                 remove existing triggers
57                   (triggers can be skipped if we don't need to support non-zavai alarms)
58                 schedule a trigger calling refresh_alarms() at next_alarm.time + 30 seconds
59                   (triggers can be skipped if we don't need to support non-zavai alarms)
60         }
61 }
62
63 at clock constructor: refresh_alarms()
64 inotifywait -e close /usr/bin/at -> refresh_alarms()  (shows when someone has just used at)
65   (can be skipped if we don't need to support non-zavai alarms)
66 at alarm triggered through zavai: refresh_alarms()
67
68
69 */
70
71
72 public class Alarm : Object
73 {
74         // Notify of an alarm being triggered
75         //public static signal void trigger(string label);
76
77         // Schedule with at
78         public static void schedule(time_t deadline, string label);
79
80         // Get the label of the job with the given at ID
81         public static string getLabel(int atID);
82 }
83
84 private int alarm_compare(void* a, void* b)
85 {
86         return (int)(((Alarm*)a)->deadline - ((Alarm*)b)->deadline);
87 }
88
89 [DBus (name = "org.freesmartphone.Notification")]
90 public class AlarmNotification : Object {
91         public void Alarm () {
92                 clock.check_alarms();
93         }
94 }
95
96 public class Clock: zavai.Service
97 {
98         protected time_t last_gps_time;
99         protected time_t last_gps_time_system_time;
100         protected time_t last_system_time;
101         protected dynamic DBus.Object gps_time;
102         protected uint system_time_timeout;
103         protected time_t last_minute;
104         protected time_t chosen_time;
105         protected SourceType chosen_type;
106         protected AlarmNotification listener;
107
108         protected dynamic DBus.Object otimed_alarm;
109         protected dynamic DBus.Object rtc;
110         protected SList<Alarm> alarms;
111
112         // Ticks once a minute
113         public signal void minute_changed(long time, SourceType source);
114         public signal void schedule_changed();
115
116         public Clock()
117         {
118                 Object(name: "clock");
119                 alarms = null;
120                 listener = new AlarmNotification();
121                 last_minute = 0;
122                 last_gps_time = 0;
123                 last_gps_time_system_time = 0;
124                 last_system_time = time_t();
125                 chosen_time = last_system_time;
126
127                 gps_time = zavai.registry.sbus.get_object(
128                                 "org.freesmartphone.ogpsd",
129                                 "/org/freedesktop/Gypsy",
130                                 "org.freedesktop.Gypsy.Time");
131
132                 // FSO alarm system
133                 otimed_alarm = zavai.registry.sbus.get_object(
134                                 "org.freesmartphone.otimed",
135                                 "/org/freesmartphone/Time/Alarm",
136                                 "org.freesmartphone.Time.Alarm");
137
138                 rtc = zavai.registry.sbus.get_object(
139                                 "org.freesmartphone.odeviced",
140                                 "/org/freesmartphone/Device/RTC/0",
141                                 "org.freesmartphone.Device.RealtimeClock");
142
143                 zavai.registry.sbus.register_object("/", listener);
144
145         }
146
147         public Alarm? next_alarm()
148         {
149                 if (alarms == null)
150                         return null;
151                 return alarms.data;
152         }
153
154         public void schedule(Alarm a)
155         {
156                 alarms.insert_sorted(a, alarm_compare);
157                 otimed_reschedule();
158         }
159
160         private void otimed_reschedule()
161         {
162                 if (alarms != null)
163                 {
164                         zavai.log.info("Scheduling next alarm: " + alarms.data.label + " at " + Time.local(alarms.data.deadline).to_string());
165                         zavai.log.info("Scheduling at abs " + "%d".printf((int)alarms.data.deadline));
166
167                         try {
168                                 otimed_alarm.ClearAlarm(zavai.registry.bus_name);
169                         } catch (Error e) {
170                                 zavai.log.error("Cannot clear alarms: " + e.message);
171                         }
172                         try {
173                                 otimed_alarm.SetAlarm(zavai.registry.bus_name, (int)alarms.data.deadline);
174                         } catch (Error e) {
175                                 zavai.log.error("Cannot reschedule alarms: " + e.message);
176                         }
177
178                         int t = rtc.GetCurrentTime();
179                         stderr.printf("Current time: %d, RTC time: %d\n", (int)time_t(), t);
180                         t = rtc.GetWakeupTime();
181                         stderr.printf("Scheduled alarm: %d, RTC wakeup time: %d\n", (int)alarms.data.deadline, t);
182                 } else
183                         zavai.log.info("No alarms left to reschedule");
184                 schedule_changed();
185         }
186
187         public void check_alarms()
188         {
189                 last_system_time = time_t();
190                 update_time();
191                 while (alarms != null && alarms.data.deadline <= chosen_time)
192                 {
193                         Alarm a = alarms.data;
194                         alarms.remove(a);
195                         zavai.log.info("Triggering " + a.label);
196                         a.trigger(a);
197                 }
198
199                 otimed_reschedule();
200         }
201
202         private void on_gps_time(dynamic DBus.Object pos, int t)
203         {
204                 if (t == 0)
205                 {
206                         last_gps_time_system_time = 0;
207                         update_time();
208                 } else {
209                         last_gps_time = (time_t)t;
210                         last_gps_time_system_time = time_t();
211                         update_time();
212                 }
213         }
214
215         private bool on_system_time()
216         {
217                 last_system_time = time_t();
218                 update_time();
219                 return true;
220         }
221
222         private void update_time()
223         {
224                 if (last_gps_time_system_time + 10 > last_system_time)
225                 {
226                         chosen_time = last_gps_time;
227                         chosen_type = SourceType.GPS;
228                 }
229                 else
230                 {
231                         chosen_time = last_system_time;
232                         chosen_type = SourceType.SYSTEM;
233                 }
234                 if (chosen_time / 60 != last_minute)
235                 {
236                         last_minute = chosen_time / 60;
237                         minute_changed(chosen_time, chosen_type);
238                 }
239         }
240
241         /// Request GPS resource
242         public override void start()
243         {
244                 if (started) return;
245
246                 system_time_timeout = Timeout.add(5000, on_system_time);
247                 gps_time.TimeChanged += on_gps_time;
248                 last_system_time = time_t();
249                 update_time();
250
251                 base.start();
252         }
253
254         public override void stop()
255         {
256                 if (!started) return;
257
258                 Source.remove(system_time_timeout);
259                 gps_time.TimeChanged -= on_gps_time;
260
261                 base.stop();
262         }
263 }
264
265 public Clock clock = null;
266
267 public void init()
268 {
269         clock = new Clock();
270
271         zavai.registry.register_service(clock);
272 }
273
274 }
275 }