From a396e6af3cd16530202f6cbecbd45c7a3f6ac893 Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Thu, 24 Apr 2014 22:34:42 -0500 Subject: hw alarms --- src/CMakeLists.txt | 6 +- src/alarm-queue-simple.cpp | 153 ++++++++++++++++++++++++++++++++++++++ src/alarm-queue.cpp | 81 -------------------- src/date-time.cpp | 5 ++ src/main.cpp | 110 ++++++++++++++++++++------- src/planner-range.cpp | 2 +- src/wakeup-timer-mainloop.cpp | 135 +++++++++++++++++++++++++++++++++ src/wakeup-timer-uha.cpp | 168 ++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 549 insertions(+), 111 deletions(-) create mode 100644 src/alarm-queue-simple.cpp delete mode 100644 src/alarm-queue.cpp create mode 100644 src/wakeup-timer-mainloop.cpp create mode 100644 src/wakeup-timer-uha.cpp (limited to 'src') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 6b65ebc..924e538 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -10,7 +10,7 @@ add_definitions (-DTIMEZONE_FILE="/etc/timezone" add_library (${SERVICE_LIB} STATIC actions.cpp actions-live.cpp - alarm-queue.cpp + alarm-queue-simple.cpp appointment.cpp clock.cpp clock-live.cpp @@ -30,7 +30,9 @@ add_library (${SERVICE_LIB} STATIC timezone-file.cpp timezone-geoclue.cpp timezones-live.cpp - utils.c) + utils.c + wakeup-timer-uha.cpp + wakeup-timer-mainloop.cpp) include_directories (${CMAKE_SOURCE_DIR}) link_directories (${SERVICE_DEPS_LIBRARY_DIRS}) diff --git a/src/alarm-queue-simple.cpp b/src/alarm-queue-simple.cpp new file mode 100644 index 0000000..2fd289c --- /dev/null +++ b/src/alarm-queue-simple.cpp @@ -0,0 +1,153 @@ +/* + * Copyright 2014 Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + * + * Authors: + * Charles Kerr + */ + +#include + +#include + +namespace unity { +namespace indicator { +namespace datetime { + +/*** +**** Public API +***/ + +SimpleAlarmQueue::SimpleAlarmQueue(const std::shared_ptr& clock, + const std::shared_ptr& planner, + const std::shared_ptr& timer): + m_clock(clock), + m_planner(planner), + m_timer(timer), + m_time(clock->localtime()) +{ + m_planner->appointments().changed().connect([this](const std::vector&){ + g_debug("AlarmQueue %p calling requeue() due to appointments changed", this); + requeue(); + }); + + m_clock->minute_changed.connect([=]{ + const auto now = m_clock->localtime(); + constexpr auto skew_threshold_usec = int64_t{90} * G_USEC_PER_SEC; + const bool clock_jumped = std::abs(now - m_time) > skew_threshold_usec; + m_time = now; + if (clock_jumped) { + g_message("AlarmQueue %p calling requeue() due to clock skew", this); + requeue(); + } + }); + + m_timer->timeout().connect([this](){ + g_message("AlarmQueue %p calling requeue() due to timeout", this); + requeue(); + }); + + requeue(); +} + +SimpleAlarmQueue::~SimpleAlarmQueue() +{ +} + +core::Signal& SimpleAlarmQueue::alarm_reached() +{ + return m_alarm_reached; +} + +/*** +**** +***/ + +void SimpleAlarmQueue::requeue() +{ + // kick any current alarms + for (auto current : find_current_alarms()) + { + m_triggered.insert(current.uid); + m_alarm_reached(current); + } + + // idle until the next alarm + Appointment next; + if (find_next_alarm(next)) + { + g_debug ("setting timer to wake up for next appointment '%s' at %s", + next.summary.c_str(), + next.begin.format("%F %T").c_str()); + + m_timer->set_wakeup_time(next.begin); + } +} + +// find the next alarm that will kick now or in the future +bool SimpleAlarmQueue::find_next_alarm(Appointment& setme) const +{ + bool found = false; + Appointment tmp; + const auto now = m_clock->localtime(); + const auto beginning_of_minute = now.add_full (0, 0, 0, 0, 0, -now.seconds()); + + for(const auto& walk : m_planner->appointments().get()) + { + if (m_triggered.count(walk.uid)) // did we already use this one? + continue; + + if (walk.begin < beginning_of_minute) // has this one already passed? + continue; + + if (found && (tmp.begin < walk.begin)) // do we already have a better match? + continue; + + tmp = walk; + found = true; + } + + if (found) + setme = tmp; + + return found; +} + +// find the alarm(s) that should kick right now +std::vector SimpleAlarmQueue::find_current_alarms() const +{ + std::vector appointments; + + const auto now = m_clock->localtime(); + + for(const auto& walk : m_planner->appointments().get()) + { + if (m_triggered.count(walk.uid)) // did we already use this one? + continue; + if (!DateTime::is_same_minute(now, walk.begin)) + continue; + + appointments.push_back(walk); + } + + return appointments; +} + +/*** +**** +***/ + +} // namespace datetime +} // namespace indicator +} // namespace unity diff --git a/src/alarm-queue.cpp b/src/alarm-queue.cpp deleted file mode 100644 index 9e9da7f..0000000 --- a/src/alarm-queue.cpp +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright 2014 Canonical Ltd. - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 3, as published - * by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranties of - * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR - * PURPOSE. See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program. If not, see . - * - * Authors: - * Charles Kerr - */ - -#include - -namespace unity { -namespace indicator { -namespace datetime { - -/*** -**** -***/ - -AlarmQueueImpl::AlarmQueueImpl(const std::shared_ptr& clock, - const std::shared_ptr& upcoming_planner): - m_clock(clock), - m_upcoming_planner(upcoming_planner) -{ - m_clock->date_changed.connect([this](){ - const auto now = m_clock->localtime(); - g_debug("AlarmQueue %p refretching appointments due to date change: %s", this, now.format("%F %T").c_str()); - m_upcoming_planner->date().set(now); - }); - - m_clock->minute_changed.connect([this](){ - g_debug("AlarmQueue %p calling pulse() due to clock minute_changed", this); - pulse(); - }); - - m_upcoming_planner->appointments().changed().connect([this](const std::vector&){ - g_debug("AlarmQueue %p calling pulse() due to appointments changed", this); - pulse(); - }); - - pulse(); -} - -core::Signal& AlarmQueueImpl::alarm_reached() -{ - return m_alarm_reached; -} - -void AlarmQueueImpl::pulse() -{ - const auto now = m_clock->localtime(); - - for(const auto& appointment : m_upcoming_planner->appointments().get()) - { - if (m_triggered.count(appointment.uid)) - continue; - if (!DateTime::is_same_minute(now, appointment.begin)) - continue; - - m_triggered.insert(appointment.uid); - m_alarm_reached(appointment); - } -} - -/*** -**** -***/ - -} // namespace datetime -} // namespace indicator -} // namespace unity diff --git a/src/date-time.cpp b/src/date-time.cpp index a1c1d1b..a139ea9 100644 --- a/src/date-time.cpp +++ b/src/date-time.cpp @@ -174,6 +174,11 @@ bool DateTime::operator==(const DateTime& that) const return g_date_time_compare(get(), that.get()) == 0; } +int64_t DateTime::operator- (const DateTime& that) const +{ + return g_date_time_difference(get(), that.get()); +} + bool DateTime::is_same_day(const DateTime& a, const DateTime& b) { // it's meaningless to compare uninitialized dates diff --git a/src/main.cpp b/src/main.cpp index 238bd02..e35c5da 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -18,7 +18,7 @@ */ #include -#include +#include #include #include #include @@ -31,6 +31,8 @@ #include #include #include +#include +#include #include // bindtextdomain() #include @@ -42,6 +44,79 @@ using namespace unity::indicator::datetime; +namespace +{ + std::shared_ptr create_engine() + { + std::shared_ptr engine; + + // we don't show appointments in the greeter, + // so no need to connect to EDS there... + if (!g_strcmp0("lightdm", g_get_user_name())) + engine.reset(new MockEngine); + else + engine.reset(new EdsEngine); + + return engine; + } + + std::shared_ptr create_wakeup_timer(const std::shared_ptr& clock) + { + std::shared_ptr wakeup_timer; + + if (UhaWakeupTimer::is_supported()) // prefer to use the platform API + wakeup_timer = std::make_shared(clock); + else + wakeup_timer = std::make_shared(clock); + + return wakeup_timer; + } + + std::shared_ptr create_state(const std::shared_ptr& engine, + const std::shared_ptr& tz) + { + // create the live objects + auto live_settings = std::make_shared(); + auto live_timezones = std::make_shared(live_settings, TIMEZONE_FILE); + auto live_clock = std::make_shared(live_timezones); + + // create a full-month planner currently pointing to the current month + const auto now = live_clock->localtime(); + auto range_planner = std::make_shared(engine, tz); + auto calendar_month = std::make_shared(range_planner, now); + + // create an upcoming-events planner currently pointing to the current date + range_planner = std::make_shared(engine, tz); + auto calendar_upcoming = std::make_shared(range_planner, now); + + // create the state + auto state = std::make_shared(); + state->settings = live_settings; + state->clock = live_clock; + state->locations = std::make_shared(live_settings, live_timezones); + state->calendar_month = calendar_month; + state->calendar_upcoming = calendar_upcoming; + return state; + } + + std::shared_ptr create_simple_alarm_queue(const std::shared_ptr& clock, + const std::shared_ptr& engine, + const std::shared_ptr& tz) + { + // create an upcoming-events planner that =always= tracks the clock's date + auto range_planner = std::make_shared(engine, tz); + auto upcoming_planner = std::make_shared(range_planner, clock->localtime()); + clock->date_changed.connect([clock,upcoming_planner](){ + const auto now = clock->localtime(); + g_debug("refretching appointments due to date change: %s", now.format("%F %T").c_str()); + upcoming_planner->date().set(now); + }); + + auto wakeup_timer = create_wakeup_timer(clock); + return std::make_shared(clock, upcoming_planner, wakeup_timer); + } +} + int main(int /*argc*/, char** /*argv*/) { @@ -54,35 +129,16 @@ main(int /*argc*/, char** /*argv*/) bindtextdomain(GETTEXT_PACKAGE, GNOMELOCALEDIR); textdomain(GETTEXT_PACKAGE); - // we don't show appointments in the greeter, - // so no need to connect to EDS there... - std::shared_ptr engine; - if (!g_strcmp0("lightdm", g_get_user_name())) - engine.reset(new MockEngine); - else - engine.reset(new EdsEngine); - - // build the state, actions, and menufactory - std::shared_ptr state(new State); - std::shared_ptr live_settings(new LiveSettings); - std::shared_ptr live_timezones(new LiveTimezones(live_settings, TIMEZONE_FILE)); - std::shared_ptr live_clock(new LiveClock(live_timezones)); - std::shared_ptr file_timezone(new FileTimezone(TIMEZONE_FILE)); - const auto now = live_clock->localtime(); - state->settings = live_settings; - state->clock = live_clock; - state->locations.reset(new SettingsLocations(live_settings, live_timezones)); - auto calendar_month = new MonthPlanner(std::shared_ptr(new SimpleRangePlanner(engine, file_timezone)), now); - state->calendar_month.reset(calendar_month); - state->calendar_upcoming.reset(new UpcomingPlanner(std::shared_ptr(new SimpleRangePlanner(engine, file_timezone)), now)); - std::shared_ptr actions(new LiveActions(state)); + auto engine = create_engine(); + auto timezone = std::make_shared(TIMEZONE_FILE); + auto state = create_state(engine, timezone); + auto actions = std::make_shared(state); MenuFactory factory(actions, state); - // snap decisions - std::shared_ptr upcoming_planner(new UpcomingPlanner(std::shared_ptr(new SimpleRangePlanner(engine, file_timezone)), now)); - AlarmQueueImpl alarm_queue(live_clock, upcoming_planner); + // set up the snap decisions Snap snap; - alarm_queue.alarm_reached().connect([&snap](const Appointment& appt){ + auto alarm_queue = create_simple_alarm_queue(state->clock, engine, timezone); + alarm_queue->alarm_reached().connect([&snap](const Appointment& appt){ auto snap_show = [](const Appointment& a){ const char* url; if(!a.url.empty()) diff --git a/src/planner-range.cpp b/src/planner-range.cpp index 93946e0..41b0f56 100644 --- a/src/planner-range.cpp +++ b/src/planner-range.cpp @@ -34,7 +34,7 @@ SimpleRangePlanner::SimpleRangePlanner(const std::shared_ptr& engine, m_range(std::pair(DateTime::NowLocal(), DateTime::NowLocal())) { engine->changed().connect([this](){ - g_debug("RangePlanner %p rebuilding soon because Engine %p emitted 'changed' signal%p", this, m_engine.get()); + g_debug("RangePlanner %p rebuilding soon because Engine %p emitted 'changed' signal", this, m_engine.get()); rebuild_soon(); }); diff --git a/src/wakeup-timer-mainloop.cpp b/src/wakeup-timer-mainloop.cpp new file mode 100644 index 0000000..53dd684 --- /dev/null +++ b/src/wakeup-timer-mainloop.cpp @@ -0,0 +1,135 @@ +/* + * Copyright 2014 Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + * + * Authors: + * Charles Kerr + */ + +#include + +#include + +namespace unity { +namespace indicator { +namespace datetime { + +/*** +**** +***/ + +class MainloopWakeupTimer::Impl +{ + +public: + + Impl(const std::shared_ptr& clock): + m_clock(clock) + { + } + + ~Impl() + { + cancel_timer(); + } + + void set_wakeup_time(const DateTime& d) + { + m_wakeup_time = d; + + rebuild_timer(); + } + + core::Signal<>& timeout() { return m_timeout; } + +private: + + void rebuild_timer() + { + cancel_timer(); + + g_return_if_fail(m_wakeup_time.is_set()); + + const auto now = m_clock->localtime(); + const auto difference_usec = g_date_time_difference(m_wakeup_time.get(), now.get()); + const guint interval_msec = (guint)difference_usec / 1000u; + g_message("setting wakeup timer to kick at %s, which is in %zu seconds", + m_wakeup_time.format("%F %T").c_str(), + size_t{interval_msec/1000}); + + m_timeout_tag = g_timeout_add_full(G_PRIORITY_HIGH, + interval_msec, + on_timeout, + this, + nullptr); + } + + static gboolean on_timeout(gpointer gself) + { + g_message("%s %s", G_STRLOC, G_STRFUNC); + static_cast(gself)->on_timeout(); + return G_SOURCE_REMOVE; + } + + void on_timeout() + { + cancel_timer(); + m_timeout(); + } + + void cancel_timer() + { + if (m_timeout_tag != 0) + { + g_source_remove(m_timeout_tag); + m_timeout_tag = 0; + } + } + + core::Signal<> m_timeout; + const std::shared_ptr& m_clock; + guint m_timeout_tag = 0; + DateTime m_wakeup_time; +}; + +/*** +**** +***/ + +MainloopWakeupTimer::MainloopWakeupTimer(const std::shared_ptr& clock): + p(new Impl(clock)) +{ +} + +MainloopWakeupTimer::~MainloopWakeupTimer() +{ +} + +void MainloopWakeupTimer::set_wakeup_time(const DateTime& d) +{ + p->set_wakeup_time(d); +} + +core::Signal<>& MainloopWakeupTimer::timeout() +{ + return p->timeout(); +} + +/*** +**** +***/ + +} // namespace datetime +} // namespace indicator +} // namespace unity diff --git a/src/wakeup-timer-uha.cpp b/src/wakeup-timer-uha.cpp new file mode 100644 index 0000000..dda109f --- /dev/null +++ b/src/wakeup-timer-uha.cpp @@ -0,0 +1,168 @@ +/* + * Copyright 2014 Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + * + * Authors: + * Charles Kerr + */ + +#include + +#include + +#include + +#include + +#include // struct timespec +#include +#include + +namespace unity { +namespace indicator { +namespace datetime { + +/*** +**** +***/ + +class UhaWakeupTimer::Impl +{ + +public: + + Impl(const std::shared_ptr& clock): + m_clock(clock), + m_hardware_alarm(u_hardware_alarm_create()) + { + // fire up a worker thread that initially just sleeps + set_wakeup_time_to_the_distant_future(); + m_thread = std::move(std::thread([&](){threadfunc();})); + } + + ~Impl() + { + // tell the worker thread to wake up and exit + m_yielding = true; + set_wakeup_time(m_clock->localtime().add_full(0,0,0,0,0,0.1)); + + // wait for it to happen + if (m_thread.joinable()) + m_thread.join(); + + g_idle_remove_by_data(this); + + u_hardware_alarm_unref(m_hardware_alarm); + } + + void set_wakeup_time(const DateTime& d) + { + g_message("%s %s", G_STRLOC, G_STRFUNC); + std::lock_guard lg(m_mutex); + + g_message("%s setting hardware wakeup time to %s", G_STRFUNC, d.format("%F %T").c_str()); + + const auto diff_usec = d - m_clock->localtime(); + struct timespec ts; + ts.tv_sec = diff_usec / G_USEC_PER_SEC; + ts.tv_nsec = (diff_usec % G_USEC_PER_SEC) * 1000; + g_message("%s setting hardware alarm to kick %zu seconds from now", G_STRFUNC, (size_t)ts.tv_sec); + u_hardware_alarm_set_relative_to_with_behavior(m_hardware_alarm, + U_HARDWARE_ALARM_TIME_REFERENCE_NOW, + U_HARDWARE_ALARM_SLEEP_BEHAVIOR_WAKEUP_DEVICE, + &ts); + } + + core::Signal<>& timeout() { return m_timeout; } + +private: + + void set_wakeup_time_to_the_distant_future() + { + const auto next_year = m_clock->localtime().add_full(1,0,0,0,0,0); + set_wakeup_time(next_year); + } + + static gboolean kick_idle (gpointer gself) + { + static_cast(gself)->timeout(); + + return G_SOURCE_REMOVE; + } + + void threadfunc() + { + while (!m_yielding) + { + // wait for the next hw alarm + UHardwareAlarmWaitResult wait_result; + auto rc = u_hardware_alarm_wait_for_next_alarm(m_hardware_alarm, &wait_result); + g_return_if_fail (rc == U_STATUS_SUCCESS); + + // set a long wakeup interval for the next iteration of the loop. + // if there's another Appointment queued up by the Planner, + // our timeout() listener will call set_wakeup_time() to set the + // real wakeup interval. + set_wakeup_time_to_the_distant_future(); + + // delegate the kick back to the main thread + g_idle_add (kick_idle, this); + } + } + + core::Signal<> m_timeout; + std::recursive_mutex m_mutex; + bool m_yielding = false; + const std::shared_ptr& m_clock; + UHardwareAlarm m_hardware_alarm = nullptr; + std::thread m_thread; +}; + +/*** +**** +***/ + +UhaWakeupTimer::UhaWakeupTimer(const std::shared_ptr& clock): + p(new Impl(clock)) +{ +} + +UhaWakeupTimer::~UhaWakeupTimer() +{ +} + +bool UhaWakeupTimer::is_supported() +{ + auto foo = u_hardware_alarm_create(); + g_message ("%s hardware alarm %p", G_STRFUNC, foo); + return foo != nullptr; +} + +void UhaWakeupTimer::set_wakeup_time(const DateTime& d) +{ + p->set_wakeup_time(d); +} + +core::Signal<>& UhaWakeupTimer::timeout() +{ + return p->timeout(); +} + +/*** +**** +***/ + +} // namespace datetime +} // namespace indicator +} // namespace unity -- cgit v1.2.3