SMACC2
Loading...
Searching...
No Matches
cl_gcalcli::CpCalendarPoller Class Reference

Component that polls Google Calendar and parses event data. More...

#include <cp_calendar_poller.hpp>

Inheritance diagram for cl_gcalcli::CpCalendarPoller:
Inheritance graph
Collaboration diagram for cl_gcalcli::CpCalendarPoller:
Collaboration graph

Public Member Functions

 CpCalendarPoller ()
 
virtual ~CpCalendarPoller ()=default
 
void onInitialize () override
 
bool refreshAgenda ()
 Force an immediate agenda refresh.
 
std::vector< CalendarEventgetEvents () const
 Get cached list of calendar events.
 
std::vector< CalendarEventfindEvents (const std::string &pattern, bool use_regex=false) const
 Get events matching a title pattern.
 
std::vector< CalendarEventgetEventsInWindow (std::chrono::system_clock::time_point start, std::chrono::system_clock::time_point end) const
 Get events happening within a time window.
 
std::vector< CalendarEventgetActiveEvents () const
 Get currently active events.
 
std::chrono::system_clock::time_point getLastPollTime () const
 Get the time of the last successful poll.
 
template<typename T >
smacc2::SmaccSignalConnection onAgendaUpdated (void(T::*callback)(const std::vector< CalendarEvent > &), T *object)
 
template<typename TOrthogonal , typename TSourceObject >
void onStateOrthogonalAllocation ()
 Template method for type-safe event posting setup.
 
- Public Member Functions inherited from smacc2::ISmaccComponent
 ISmaccComponent ()
 
virtual ~ISmaccComponent ()
 
virtual std::string getName () const
 
- Public Member Functions inherited from smacc2::ISmaccUpdatable
 ISmaccUpdatable ()
 
 ISmaccUpdatable (rclcpp::Duration duration)
 
void executeUpdate (rclcpp::Node::SharedPtr node)
 
void setUpdatePeriod (rclcpp::Duration duration)
 

Public Attributes

smacc2::SmaccSignal< void(const std::vector< CalendarEvent > &)> onAgendaUpdated_
 
std::function< void(const std::vector< CalendarEvent > &)> postAgendaUpdatedEvent_
 

Protected Member Functions

void update () override
 Periodic update for polling (called by SignalDetector)
 
- Protected Member Functions inherited from smacc2::ISmaccComponent
template<typename TOrthogonal , typename TClient >
void onComponentInitialization ()
 
template<typename EventType >
void postEvent (const EventType &ev)
 
template<typename EventType >
void postEvent ()
 
template<typename TOrthogonal , typename TSourceObject >
void onStateOrthogonalAllocation ()
 
template<typename TComponent >
void requiresComponent (TComponent *&requiredComponentStorage, ComponentRequirement requirementType=ComponentRequirement::SOFT)
 
template<typename TComponent >
void requiresComponent (std::string name, TComponent *&requiredComponentStorage, ComponentRequirement requirementType=ComponentRequirement::SOFT)
 
template<typename TClient >
void requiresClient (TClient *&requiredClientStorage)
 
template<typename SmaccComponentType , typename TOrthogonal , typename TClient , typename... TArgs>
SmaccComponentType * createSiblingComponent (TArgs... targs)
 
template<typename SmaccComponentType , typename TOrthogonal , typename TClient , typename... TArgs>
SmaccComponentType * createSiblingNamedComponent (std::string name, TArgs... targs)
 
rclcpp::Node::SharedPtr getNode ()
 
rclcpp::Logger getLogger () const
 
ISmaccStateMachinegetStateMachine ()
 
- Protected Member Functions inherited from smacc2::ISmaccUpdatable

Private Member Functions

std::vector< CalendarEventparseTsvOutput (const std::string &output)
 Parse TSV output into CalendarEvent structures.
 
std::optional< CalendarEventparseTsvLine (const std::string &line)
 Parse a single TSV line into a CalendarEvent.
 
std::optional< std::chrono::system_clock::time_point > parseDateTime (const std::string &date_str, const std::string &time_str)
 Parse date and time strings into time_point.
 
std::string generateEventId (const CalendarEvent &event)
 Generate a unique ID for an event.
 

Private Attributes

CpGcalcliConnectionconnection_
 
std::vector< CalendarEventcached_events_
 
std::chrono::system_clock::time_point last_poll_time_
 
std::chrono::steady_clock::time_point last_poll_attempt_
 
bool initialized_
 
std::mutex events_mutex_
 

Additional Inherited Members

- Protected Attributes inherited from smacc2::ISmaccComponent
ISmaccStateMachinestateMachine_
 
ISmaccClientowner_
 

Detailed Description

Component that polls Google Calendar and parses event data.

This component periodically fetches the agenda from Google Calendar using gcalcli's TSV output format and parses it into CalendarEvent structures.

TSV Format: Start_Date | Start_Time | End_Date | End_Time | Title | Location | Description | Calendar

Definition at line 43 of file cp_calendar_poller.hpp.

Constructor & Destructor Documentation

◆ CpCalendarPoller()

cl_gcalcli::CpCalendarPoller::CpCalendarPoller ( )

Definition at line 24 of file cp_calendar_poller.cpp.

◆ ~CpCalendarPoller()

virtual cl_gcalcli::CpCalendarPoller::~CpCalendarPoller ( )
virtualdefault

Member Function Documentation

◆ findEvents()

std::vector< CalendarEvent > cl_gcalcli::CpCalendarPoller::findEvents ( const std::string & pattern,
bool use_regex = false ) const

Get events matching a title pattern.

Parameters
patternPattern to match (regex or exact based on use_regex)
use_regexIf true, use regex matching

Definition at line 109 of file cp_calendar_poller.cpp.

111{
112 std::lock_guard<std::mutex> lock(events_mutex_);
113
114 std::vector<CalendarEvent> matches;
115
116 if (use_regex)
117 {
118 try
119 {
120 boost::regex regex(pattern, boost::regex::icase);
121 for (const auto & event : cached_events_)
122 {
123 if (boost::regex_search(event.title, regex))
124 {
125 matches.push_back(event);
126 }
127 }
128 }
129 catch (const boost::regex_error & e)
130 {
131 RCLCPP_ERROR(getLogger(), "[CpCalendarPoller] Invalid regex pattern: %s", e.what());
132 }
133 }
134 else
135 {
136 for (const auto & event : cached_events_)
137 {
138 if (event.title.find(pattern) != std::string::npos)
139 {
140 matches.push_back(event);
141 }
142 }
143 }
144
145 return matches;
146}
std::vector< CalendarEvent > cached_events_
rclcpp::Logger getLogger() const

References cached_events_, events_mutex_, and smacc2::ISmaccComponent::getLogger().

Here is the call graph for this function:

◆ generateEventId()

std::string cl_gcalcli::CpCalendarPoller::generateEventId ( const CalendarEvent & event)
private

Generate a unique ID for an event.

Definition at line 384 of file cp_calendar_poller.cpp.

385{
386 // Generate a unique ID based on title and start time
387 auto start_t = std::chrono::system_clock::to_time_t(event.start_time);
388 std::stringstream ss;
389 ss << event.title << "_" << start_t << "_" << event.calendar_name;
390 return ss.str();
391}

References cl_gcalcli::CalendarEvent::start_time.

Referenced by parseTsvLine().

Here is the caller graph for this function:

◆ getActiveEvents()

std::vector< CalendarEvent > cl_gcalcli::CpCalendarPoller::getActiveEvents ( ) const

Get currently active events.

Definition at line 166 of file cp_calendar_poller.cpp.

167{
168 std::lock_guard<std::mutex> lock(events_mutex_);
169
170 std::vector<CalendarEvent> active;
171 for (const auto & event : cached_events_)
172 {
173 if (event.isActiveNow())
174 {
175 active.push_back(event);
176 }
177 }
178
179 return active;
180}

References cached_events_, and events_mutex_.

◆ getEvents()

std::vector< CalendarEvent > cl_gcalcli::CpCalendarPoller::getEvents ( ) const

Get cached list of calendar events.

Definition at line 103 of file cp_calendar_poller.cpp.

104{
105 std::lock_guard<std::mutex> lock(events_mutex_);
106 return cached_events_;
107}

References cached_events_, and events_mutex_.

◆ getEventsInWindow()

std::vector< CalendarEvent > cl_gcalcli::CpCalendarPoller::getEventsInWindow ( std::chrono::system_clock::time_point start,
std::chrono::system_clock::time_point end ) const

Get events happening within a time window.

Definition at line 148 of file cp_calendar_poller.cpp.

150{
151 std::lock_guard<std::mutex> lock(events_mutex_);
152
153 std::vector<CalendarEvent> matches;
154 for (const auto & event : cached_events_)
155 {
156 // Event overlaps with window if it starts before window end and ends after window start
157 if (event.start_time < end && event.end_time > start)
158 {
159 matches.push_back(event);
160 }
161 }
162
163 return matches;
164}

References cached_events_, and events_mutex_.

◆ getLastPollTime()

std::chrono::system_clock::time_point cl_gcalcli::CpCalendarPoller::getLastPollTime ( ) const

Get the time of the last successful poll.

Definition at line 182 of file cp_calendar_poller.cpp.

183{
184 std::lock_guard<std::mutex> lock(events_mutex_);
185 return last_poll_time_;
186}
std::chrono::system_clock::time_point last_poll_time_

References events_mutex_, and last_poll_time_.

◆ onAgendaUpdated()

template<typename T >
smacc2::SmaccSignalConnection cl_gcalcli::CpCalendarPoller::onAgendaUpdated ( void(T::* callback )(const std::vector< CalendarEvent > &),
T * object )
inline

Definition at line 90 of file cp_calendar_poller.hpp.

92 {
93 return this->getStateMachine()->createSignalConnection(onAgendaUpdated_, callback, object);
94 }
smacc2::SmaccSignal< void(const std::vector< CalendarEvent > &)> onAgendaUpdated_
ISmaccStateMachine * getStateMachine()
smacc2::SmaccSignalConnection createSignalConnection(TSmaccSignal &signal, TMemberFunctionPrototype callback, TSmaccObjectType *object)

References smacc2::ISmaccStateMachine::createSignalConnection(), and smacc2::ISmaccComponent::getStateMachine().

Referenced by cl_gcalcli::CpCalendarEventListener::onInitialize().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ onInitialize()

void cl_gcalcli::CpCalendarPoller::onInitialize ( )
overridevirtual

Reimplemented from smacc2::ISmaccComponent.

Definition at line 26 of file cp_calendar_poller.cpp.

27{
28 if (!initialized_)
29 {
31
32 if (connection_ == nullptr)
33 {
34 RCLCPP_ERROR(getLogger(), "[CpCalendarPoller] CpGcalcliConnection component not found!");
35 return;
36 }
37
38 last_poll_attempt_ = std::chrono::steady_clock::now();
39 initialized_ = true;
40
41 RCLCPP_INFO(getLogger(), "[CpCalendarPoller] Initialized");
42 }
43}
std::chrono::steady_clock::time_point last_poll_attempt_
void requiresComponent(TComponent *&requiredComponentStorage, ComponentRequirement requirementType=ComponentRequirement::SOFT)

References connection_, smacc2::ISmaccComponent::getLogger(), initialized_, last_poll_attempt_, and smacc2::ISmaccComponent::requiresComponent().

Here is the call graph for this function:

◆ onStateOrthogonalAllocation()

template<typename TOrthogonal , typename TSourceObject >
void cl_gcalcli::CpCalendarPoller::onStateOrthogonalAllocation ( )
inline

Template method for type-safe event posting setup.

Definition at line 103 of file cp_calendar_poller.hpp.

104 {
105 postAgendaUpdatedEvent_ = [this](const std::vector<CalendarEvent> & events)
106 {
107 auto ev = new EvAgendaUpdated<CpCalendarPoller, TOrthogonal>();
108 ev->events = events;
109 this->postEvent(ev);
110 };
111 }
std::function< void(const std::vector< CalendarEvent > &)> postAgendaUpdatedEvent_

References postAgendaUpdatedEvent_, and smacc2::ISmaccComponent::postEvent().

Here is the call graph for this function:

◆ parseDateTime()

std::optional< std::chrono::system_clock::time_point > cl_gcalcli::CpCalendarPoller::parseDateTime ( const std::string & date_str,
const std::string & time_str )
private

Parse date and time strings into time_point.

Parameters
date_strDate string (MM/DD/YYYY or similar)
time_strTime string (HH:MM or similar)

Definition at line 296 of file cp_calendar_poller.cpp.

298{
299 std::tm tm = {};
300
301 // Parse date - gcalcli TSV uses YYYY-MM-DD format
302 std::istringstream date_stream(date_str);
303 char sep1, sep2;
304 int year, month, day;
305
306 if (date_stream >> year >> sep1 >> month >> sep2 >> day && sep1 == '-' && sep2 == '-')
307 {
308 // YYYY-MM-DD format (gcalcli TSV)
309 tm.tm_year = year - 1900;
310 tm.tm_mon = month - 1;
311 tm.tm_mday = day;
312 }
313 else
314 {
315 // Try MM/DD/YYYY format as fallback
316 date_stream.clear();
317 date_stream.str(date_str);
318 if (date_stream >> month >> sep1 >> day >> sep2 >> year && sep1 == '/' && sep2 == '/')
319 {
320 tm.tm_mon = month - 1;
321 tm.tm_mday = day;
322 tm.tm_year = year - 1900;
323 }
324 else
325 {
326 RCLCPP_DEBUG(getLogger(), "[CpCalendarPoller] Failed to parse date: %s", date_str.c_str());
327 return std::nullopt;
328 }
329 }
330
331 // Parse time - gcalcli TSV uses 24-hour HH:MM format
332 if (!time_str.empty())
333 {
334 std::istringstream time_stream(time_str);
335 char colon;
336 int hour, minute;
337
338 if (time_stream >> hour >> colon >> minute && colon == ':')
339 {
340 tm.tm_hour = hour;
341 tm.tm_min = minute;
342
343 // Check for am/pm suffix (for non-TSV formats)
344 std::string rest;
345 std::getline(time_stream, rest);
346 if (!rest.empty())
347 {
348 // Remove leading spaces
349 size_t start = rest.find_first_not_of(" ");
350 if (start != std::string::npos)
351 {
352 rest = rest.substr(start);
353 }
354 // Check for am/pm
355 if (rest == "pm" || rest == "PM" || rest == "p" || rest == "P")
356 {
357 if (hour != 12) tm.tm_hour += 12;
358 }
359 else if (rest == "am" || rest == "AM" || rest == "a" || rest == "A")
360 {
361 if (hour == 12) tm.tm_hour = 0;
362 }
363 }
364 }
365 else
366 {
367 RCLCPP_DEBUG(getLogger(), "[CpCalendarPoller] Failed to parse time: %s", time_str.c_str());
368 return std::nullopt;
369 }
370 }
371
372 tm.tm_sec = 0;
373 tm.tm_isdst = -1; // Let mktime determine DST
374
375 std::time_t time = std::mktime(&tm);
376 if (time == -1)
377 {
378 return std::nullopt;
379 }
380
381 return std::chrono::system_clock::from_time_t(time);
382}

References smacc2::ISmaccComponent::getLogger().

Referenced by parseTsvLine().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ parseTsvLine()

std::optional< CalendarEvent > cl_gcalcli::CpCalendarPoller::parseTsvLine ( const std::string & line)
private

Parse a single TSV line into a CalendarEvent.

Definition at line 235 of file cp_calendar_poller.cpp.

236{
237 // TSV columns: Start_Date | Start_Time | End_Date | End_Time | Title | Location | Description | Calendar
238 std::vector<std::string> fields;
239 std::istringstream stream(line);
240 std::string field;
241
242 while (std::getline(stream, field, '\t'))
243 {
244 fields.push_back(field);
245 }
246
247 // We need at least 5 fields (date/time + title)
248 if (fields.size() < 5)
249 {
250 RCLCPP_DEBUG(
251 getLogger(), "[CpCalendarPoller] Skipping line with insufficient fields: %s", line.c_str());
252 return std::nullopt;
253 }
254
255 CalendarEvent event;
256
257 // Parse start time
258 auto start = parseDateTime(fields[0], fields[1]);
259 if (!start.has_value())
260 {
261 RCLCPP_DEBUG(
262 getLogger(), "[CpCalendarPoller] Failed to parse start time: %s %s", fields[0].c_str(),
263 fields[1].c_str());
264 return std::nullopt;
265 }
266 event.start_time = start.value();
267
268 // Parse end time
269 auto end = parseDateTime(fields[2], fields[3]);
270 if (!end.has_value())
271 {
272 RCLCPP_DEBUG(
273 getLogger(), "[CpCalendarPoller] Failed to parse end time: %s %s", fields[2].c_str(),
274 fields[3].c_str());
275 return std::nullopt;
276 }
277 event.end_time = end.value();
278
279 // Title (required)
280 event.title = fields[4];
281
282 // Optional fields
283 if (fields.size() > 5) event.location = fields[5];
284 if (fields.size() > 6) event.description = fields[6];
285 if (fields.size() > 7) event.calendar_name = fields[7];
286
287 // Check for all-day event (typically has no time or 00:00)
288 event.is_all_day = (fields[1].empty() || fields[1] == "00:00" || fields[1] == "12:00am");
289
290 // Generate ID
291 event.id = generateEventId(event);
292
293 return event;
294}
std::optional< std::chrono::system_clock::time_point > parseDateTime(const std::string &date_str, const std::string &time_str)
Parse date and time strings into time_point.
std::string generateEventId(const CalendarEvent &event)
Generate a unique ID for an event.

References generateEventId(), smacc2::ISmaccComponent::getLogger(), and parseDateTime().

Referenced by parseTsvOutput().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ parseTsvOutput()

std::vector< CalendarEvent > cl_gcalcli::CpCalendarPoller::parseTsvOutput ( const std::string & output)
private

Parse TSV output into CalendarEvent structures.

Definition at line 211 of file cp_calendar_poller.cpp.

212{
213 std::vector<CalendarEvent> events;
214 std::istringstream stream(output);
215 std::string line;
216
217 while (std::getline(stream, line))
218 {
219 // Skip empty lines
220 if (line.empty() || line.find_first_not_of(" \t\r\n") == std::string::npos)
221 {
222 continue;
223 }
224
225 auto event = parseTsvLine(line);
226 if (event.has_value())
227 {
228 events.push_back(event.value());
229 }
230 }
231
232 return events;
233}
std::optional< CalendarEvent > parseTsvLine(const std::string &line)
Parse a single TSV line into a CalendarEvent.

References parseTsvLine().

Referenced by refreshAgenda().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ refreshAgenda()

bool cl_gcalcli::CpCalendarPoller::refreshAgenda ( )

Force an immediate agenda refresh.

Returns
true if refresh was successful

Definition at line 45 of file cp_calendar_poller.cpp.

46{
48 {
49 RCLCPP_WARN(getLogger(), "[CpCalendarPoller] Cannot refresh - not connected");
50 return false;
51 }
52
53 const auto & config = connection_->getConfig();
54
55 // Build the agenda command with TSV output
56 std::stringstream args;
57 args << "agenda --tsv --nocolor";
58 args << " --nodeclined";
59
60 // Add date range
61 auto now = std::chrono::system_clock::now();
62 auto end = now + std::chrono::hours(24 * config.agenda_days);
63
64 auto now_t = std::chrono::system_clock::to_time_t(now);
65 auto end_t = std::chrono::system_clock::to_time_t(end);
66
67 std::tm now_tm = *std::localtime(&now_t);
68 std::tm end_tm = *std::localtime(&end_t);
69
70 args << " \"" << std::put_time(&now_tm, "%m/%d/%Y") << "\"";
71 args << " \"" << std::put_time(&end_tm, "%m/%d/%Y") << "\"";
72
73 auto result = connection_->executeGcalcli(args.str(), 30000);
74
75 if (result.exit_code != 0 || result.timed_out)
76 {
77 RCLCPP_WARN(
78 getLogger(), "[CpCalendarPoller] Agenda fetch failed: %s", result.stdout_output.c_str());
79 return false;
80 }
81
82 // Parse the TSV output
83 auto events = parseTsvOutput(result.stdout_output);
84
85 {
86 std::lock_guard<std::mutex> lock(events_mutex_);
87 cached_events_ = events;
88 last_poll_time_ = std::chrono::system_clock::now();
89 }
90
91 RCLCPP_DEBUG(getLogger(), "[CpCalendarPoller] Refreshed agenda: %zu events", events.size());
92
93 // Emit signal and post event
94 onAgendaUpdated_(events);
96 {
98 }
99
100 return true;
101}
std::vector< CalendarEvent > parseTsvOutput(const std::string &output)
Parse TSV output into CalendarEvent structures.
const GcalcliConfig & getConfig() const
Get the gcalcli configuration.
bool isConnected() const
Check if connected to Google Calendar.
smacc2::client_core_components::SubprocessResult executeGcalcli(const std::string &args, int timeout_ms=30000)
Execute a gcalcli command.

References cached_events_, connection_, events_mutex_, cl_gcalcli::CpGcalcliConnection::executeGcalcli(), cl_gcalcli::CpGcalcliConnection::getConfig(), smacc2::ISmaccComponent::getLogger(), cl_gcalcli::CpGcalcliConnection::isConnected(), last_poll_time_, onAgendaUpdated_, parseTsvOutput(), and postAgendaUpdatedEvent_.

Referenced by update().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ update()

void cl_gcalcli::CpCalendarPoller::update ( )
overrideprotectedvirtual

Periodic update for polling (called by SignalDetector)

Implements smacc2::ISmaccUpdatable.

Definition at line 188 of file cp_calendar_poller.cpp.

189{
190 if (!initialized_ || !connection_)
191 {
192 return;
193 }
194
195 if (!connection_->isConnected())
196 {
197 return;
198 }
199
200 const auto & config = connection_->getConfig();
201 auto now = std::chrono::steady_clock::now();
202 auto elapsed = std::chrono::duration_cast<std::chrono::seconds>(now - last_poll_attempt_);
203
204 if (elapsed >= config.poll_interval)
205 {
207 last_poll_attempt_ = now;
208 }
209}
bool refreshAgenda()
Force an immediate agenda refresh.

References connection_, cl_gcalcli::CpGcalcliConnection::getConfig(), initialized_, cl_gcalcli::CpGcalcliConnection::isConnected(), last_poll_attempt_, and refreshAgenda().

Here is the call graph for this function:

Member Data Documentation

◆ cached_events_

std::vector<CalendarEvent> cl_gcalcli::CpCalendarPoller::cached_events_
private

◆ connection_

CpGcalcliConnection* cl_gcalcli::CpCalendarPoller::connection_
private

Definition at line 143 of file cp_calendar_poller.hpp.

Referenced by onInitialize(), refreshAgenda(), and update().

◆ events_mutex_

std::mutex cl_gcalcli::CpCalendarPoller::events_mutex_
mutableprivate

◆ initialized_

bool cl_gcalcli::CpCalendarPoller::initialized_
private

Definition at line 147 of file cp_calendar_poller.hpp.

Referenced by onInitialize(), and update().

◆ last_poll_attempt_

std::chrono::steady_clock::time_point cl_gcalcli::CpCalendarPoller::last_poll_attempt_
private

Definition at line 146 of file cp_calendar_poller.hpp.

Referenced by onInitialize(), and update().

◆ last_poll_time_

std::chrono::system_clock::time_point cl_gcalcli::CpCalendarPoller::last_poll_time_
private

Definition at line 145 of file cp_calendar_poller.hpp.

Referenced by getLastPollTime(), and refreshAgenda().

◆ onAgendaUpdated_

smacc2::SmaccSignal<void(const std::vector<CalendarEvent> &)> cl_gcalcli::CpCalendarPoller::onAgendaUpdated_

Definition at line 86 of file cp_calendar_poller.hpp.

Referenced by refreshAgenda().

◆ postAgendaUpdatedEvent_

std::function<void(const std::vector<CalendarEvent> &)> cl_gcalcli::CpCalendarPoller::postAgendaUpdatedEvent_

Definition at line 97 of file cp_calendar_poller.hpp.

Referenced by onStateOrthogonalAllocation(), and refreshAgenda().


The documentation for this class was generated from the following files: