1use std::str::FromStr;
2use std::sync::{Arc, Mutex};
3use std::time::Duration;
4use std::vec;
5
6use agent_stream_kit::{
7 ASKit, Agent, AgentContext, AgentData, AgentError, AgentOutput, AgentSpec, AgentStatus,
8 AgentValue, AsAgent, askit_agent, async_trait,
9};
10use chrono::{DateTime, Local, Utc};
11use cron::Schedule;
12use log;
13use regex::Regex;
14use tokio::task::JoinHandle;
15
16const CATEGORY: &str = "Std/Time";
17
18const PIN_TIME: &str = "time";
19const PIN_VALUE: &str = "value";
20const PIN_UNIT: &str = "unit";
21
22const CONFIG_DELAY: &str = "delay";
23const CONFIG_MAX_NUM_DATA: &str = "max_num_data";
24const CONFIG_INTERVAL: &str = "interval";
25const CONFIG_SCHEDULE: &str = "schedule";
26const CONFIG_TIME: &str = "time";
27
28const DELAY_MS_DEFAULT: i64 = 1000; const MAX_NUM_DATA_DEFAULT: i64 = 10;
30const INTERVAL_DEFAULT: &str = "10s";
31const TIME_DEFAULT: &str = "1s";
32
33#[askit_agent(
35 title = "Delay",
36 description = "Delays output by a specified time",
37 category = CATEGORY,
38 inputs = [PIN_VALUE],
39 outputs = [PIN_VALUE],
40 integer_config(name = CONFIG_DELAY, default = DELAY_MS_DEFAULT, title = "delay (ms)"),
41 integer_config(name = CONFIG_MAX_NUM_DATA, default = MAX_NUM_DATA_DEFAULT, title = "max num data")
42)]
43struct DelayAgent {
44 data: AgentData,
45 num_waiting_data: Arc<Mutex<i64>>,
46}
47
48#[async_trait]
49impl AsAgent for DelayAgent {
50 fn new(askit: ASKit, id: String, spec: AgentSpec) -> Result<Self, AgentError> {
51 Ok(Self {
52 data: AgentData::new(askit, id, spec),
53 num_waiting_data: Arc::new(Mutex::new(0)),
54 })
55 }
56
57 async fn process(
58 &mut self,
59 ctx: AgentContext,
60 pin: String,
61 value: AgentValue,
62 ) -> Result<(), AgentError> {
63 let config = self.configs()?;
64 let delay_ms = config.get_integer_or(CONFIG_DELAY, DELAY_MS_DEFAULT);
65 let max_num_data = config.get_integer_or(CONFIG_MAX_NUM_DATA, MAX_NUM_DATA_DEFAULT);
66
67 {
69 let num_waiting_data = self.num_waiting_data.clone();
70 let mut num_waiting_data = num_waiting_data.lock().unwrap();
71 if *num_waiting_data >= max_num_data {
72 return Ok(());
73 }
74 *num_waiting_data += 1;
75 }
76
77 tokio::time::sleep(Duration::from_millis(delay_ms as u64)).await;
78
79 self.output(ctx.clone(), pin, value.clone()).await?;
80
81 let mut num_waiting_data = self.num_waiting_data.lock().unwrap();
82 *num_waiting_data -= 1;
83
84 Ok(())
85 }
86}
87
88#[askit_agent(
90 title = "Interval Timer",
91 description = "Outputs a unit signal at specified intervals",
92 category = CATEGORY,
93 outputs = [PIN_UNIT],
94 string_config(name = CONFIG_INTERVAL, default = INTERVAL_DEFAULT, description = "(ex. 10s, 5m, 100ms, 1h, 1d)")
95)]
96struct IntervalTimerAgent {
97 data: AgentData,
98 timer_handle: Arc<Mutex<Option<JoinHandle<()>>>>,
99 interval_ms: u64,
100}
101
102impl IntervalTimerAgent {
103 fn start_timer(&mut self) -> Result<(), AgentError> {
104 let timer_handle = self.timer_handle.clone();
105 let interval_ms = self.interval_ms;
106
107 let askit = self.askit().clone();
108 let agent_id = self.id().to_string();
109 let handle = self.runtime().spawn(async move {
110 loop {
111 tokio::time::sleep(tokio::time::Duration::from_millis(interval_ms)).await;
113
114 if let Ok(handle) = timer_handle.lock() {
116 if handle.is_none() {
117 break;
118 }
119 }
120
121 if let Err(e) = askit.try_send_agent_out(
123 agent_id.clone(),
124 AgentContext::new(),
125 PIN_UNIT.to_string(),
126 AgentValue::unit(),
127 ) {
128 log::error!("Failed to send interval timer output: {}", e);
129 }
130 }
131 });
132
133 if let Ok(mut timer_handle) = self.timer_handle.lock() {
135 *timer_handle = Some(handle);
136 }
137
138 Ok(())
139 }
140
141 fn stop_timer(&mut self) -> Result<(), AgentError> {
142 if let Ok(mut timer_handle) = self.timer_handle.lock() {
144 if let Some(handle) = timer_handle.take() {
145 handle.abort();
146 }
147 }
148 Ok(())
149 }
150}
151
152#[async_trait]
153impl AsAgent for IntervalTimerAgent {
154 fn new(askit: ASKit, id: String, spec: AgentSpec) -> Result<Self, AgentError> {
155 let interval = spec
156 .configs
157 .as_ref()
158 .ok_or(AgentError::NoConfig)?
159 .get_string_or(CONFIG_INTERVAL, INTERVAL_DEFAULT);
160 let interval_ms = parse_duration_to_ms(&interval)?;
161
162 Ok(Self {
163 data: AgentData::new(askit, id, spec),
164 timer_handle: Default::default(),
165 interval_ms,
166 })
167 }
168
169 async fn start(&mut self) -> Result<(), AgentError> {
170 self.start_timer()
171 }
172
173 async fn stop(&mut self) -> Result<(), AgentError> {
174 self.stop_timer()
175 }
176
177 fn configs_changed(&mut self) -> Result<(), AgentError> {
178 let interval = self.configs()?.get_string(CONFIG_INTERVAL)?;
180 let new_interval = parse_duration_to_ms(&interval)?;
181 if new_interval != self.interval_ms {
182 self.interval_ms = new_interval;
183 if *self.status() == AgentStatus::Start {
184 self.stop_timer()?;
186 self.start_timer()?;
187 }
188 }
189 Ok(())
190 }
191}
192
193#[askit_agent(
195 title = "On Start",
196 category = CATEGORY,
197 outputs = [PIN_UNIT],
198 integer_config(name = CONFIG_DELAY, default = DELAY_MS_DEFAULT, title = "delay (ms)")
199)]
200struct OnStartAgent {
201 data: AgentData,
202}
203
204#[async_trait]
205impl AsAgent for OnStartAgent {
206 fn new(askit: ASKit, id: String, spec: AgentSpec) -> Result<Self, AgentError> {
207 Ok(Self {
208 data: AgentData::new(askit, id, spec),
209 })
210 }
211
212 async fn start(&mut self) -> Result<(), AgentError> {
213 let config = self.configs()?;
214 let delay_ms = config.get_integer_or(CONFIG_DELAY, DELAY_MS_DEFAULT);
215
216 let askit = self.askit().clone();
217 let agent_id = self.id().to_string();
218
219 self.runtime().spawn(async move {
220 tokio::time::sleep(Duration::from_millis(delay_ms as u64)).await;
221
222 if let Err(e) = askit.try_send_agent_out(
223 agent_id,
224 AgentContext::new(),
225 PIN_UNIT.to_string(),
226 AgentValue::unit(),
227 ) {
228 log::error!("Failed to send delayed output: {}", e);
229 }
230 });
231
232 Ok(())
233 }
234}
235
236#[askit_agent(
238 title = "Schedule Timer",
239 category = CATEGORY,
240 outputs = [PIN_TIME],
241 string_config(name = CONFIG_SCHEDULE, default = "0 0 * * * *", description = "sec min hour day month week year")
242)]
243struct ScheduleTimerAgent {
244 data: AgentData,
245 cron_schedule: Option<Schedule>,
246 timer_handle: Arc<Mutex<Option<JoinHandle<()>>>>,
247}
248
249impl ScheduleTimerAgent {
250 fn start_timer(&mut self) -> Result<(), AgentError> {
251 let Some(schedule) = &self.cron_schedule else {
252 return Err(AgentError::InvalidConfig("No schedule defined".into()));
253 };
254
255 let askit = self.askit().clone();
256 let agent_id = self.id().to_string();
257 let timer_handle = self.timer_handle.clone();
258 let schedule = schedule.clone();
259
260 let handle = self.runtime().spawn(async move {
261 loop {
262 let now: DateTime<Utc> = Utc::now();
264 let next = match schedule.upcoming(Utc).next() {
265 Some(next_time) => next_time,
266 None => {
267 log::error!("No upcoming schedule times found");
268 break;
269 }
270 };
271
272 let duration = match (next - now).to_std() {
274 Ok(duration) => duration,
275 Err(e) => {
276 log::error!("Failed to calculate duration until next schedule: {}", e);
277 tokio::time::sleep(Duration::from_secs(60)).await;
279 continue;
280 }
281 };
282
283 let next_local = next.with_timezone(&Local);
284 log::debug!(
285 "Scheduling timer for '{}' to fire at {} (in {:?})",
286 agent_id,
287 next_local.format("%Y-%m-%d %H:%M:%S %z"),
288 duration
289 );
290
291 tokio::time::sleep(duration).await;
293
294 if let Ok(handle) = timer_handle.lock() {
296 if handle.is_none() {
297 break;
298 }
299 }
300
301 let current_local_time = Local::now().timestamp();
303
304 if let Err(e) = askit.try_send_agent_out(
306 agent_id.clone(),
307 AgentContext::new(),
308 PIN_TIME.to_string(),
309 AgentValue::integer(current_local_time),
310 ) {
311 log::error!("Failed to send schedule timer output: {}", e);
312 }
313 }
314 });
315
316 if let Ok(mut timer_handle) = self.timer_handle.lock() {
318 *timer_handle = Some(handle);
319 }
320
321 Ok(())
322 }
323
324 fn stop_timer(&mut self) -> Result<(), AgentError> {
325 if let Ok(mut timer_handle) = self.timer_handle.lock() {
327 if let Some(handle) = timer_handle.take() {
328 handle.abort();
329 }
330 }
331 Ok(())
332 }
333
334 fn parse_schedule(&mut self, schedule_str: &str) -> Result<(), AgentError> {
335 if schedule_str.trim().is_empty() {
336 self.cron_schedule = None;
337 return Ok(());
338 }
339
340 let schedule = Schedule::from_str(schedule_str).map_err(|e| {
341 AgentError::InvalidConfig(format!("Invalid cron schedule '{}': {}", schedule_str, e))
342 })?;
343 self.cron_schedule = Some(schedule);
344 Ok(())
345 }
346}
347
348#[async_trait]
349impl AsAgent for ScheduleTimerAgent {
350 fn new(askit: ASKit, id: String, spec: AgentSpec) -> Result<Self, AgentError> {
351 let schedule_str = spec
352 .configs
353 .as_ref()
354 .map(|cfg| cfg.get_string(CONFIG_SCHEDULE))
355 .transpose()?;
356
357 let mut agent = Self {
358 data: AgentData::new(askit, id, spec),
359 cron_schedule: None,
360 timer_handle: Default::default(),
361 };
362
363 if let Some(schedule_str) = schedule_str {
364 if !schedule_str.is_empty() {
365 agent.parse_schedule(&schedule_str)?;
366 }
367 }
368
369 Ok(agent)
370 }
371
372 async fn start(&mut self) -> Result<(), AgentError> {
373 if self.cron_schedule.is_some() {
374 self.start_timer()?;
375 }
376 Ok(())
377 }
378
379 async fn stop(&mut self) -> Result<(), AgentError> {
380 self.stop_timer()
381 }
382
383 fn configs_changed(&mut self) -> Result<(), AgentError> {
384 let schedule_str = self.configs()?.get_string(CONFIG_SCHEDULE)?;
386 self.parse_schedule(&schedule_str)?;
387
388 if *self.status() == AgentStatus::Start {
389 self.stop_timer()?;
391 if self.cron_schedule.is_some() {
392 self.start_timer()?;
393 }
394 }
395 Ok(())
396 }
397}
398
399#[askit_agent(
401 title = "Throttle Time",
402 category = CATEGORY,
403 inputs = [PIN_VALUE],
404 outputs = [PIN_VALUE],
405 string_config(name = CONFIG_TIME, default = TIME_DEFAULT, description = "(ex. 10s, 5m, 100ms, 1h, 1d)"),
406 integer_config(name = CONFIG_MAX_NUM_DATA, title = "max num data", description = "0: no data, -1: all data")
407)]
408struct ThrottleTimeAgent {
409 data: AgentData,
410 timer_handle: Arc<Mutex<Option<JoinHandle<()>>>>,
411 time_ms: u64,
412 max_num_data: i64,
413 waiting_data: Arc<Mutex<Vec<(AgentContext, String, AgentValue)>>>,
414}
415
416impl ThrottleTimeAgent {
417 fn start_timer(&mut self) -> Result<(), AgentError> {
418 let timer_handle = self.timer_handle.clone();
419 let time_ms = self.time_ms;
420
421 let waiting_data = self.waiting_data.clone();
422 let askit = self.askit().clone();
423 let agent_id = self.id().to_string();
424
425 let handle = self.runtime().spawn(async move {
426 loop {
427 tokio::time::sleep(tokio::time::Duration::from_millis(time_ms)).await;
429
430 let mut handle = timer_handle.lock().unwrap();
432 if handle.is_none() {
433 break;
434 }
435
436 let mut wd = waiting_data.lock().unwrap();
438 if wd.len() > 0 {
439 let (ctx, pin, data) = wd.remove(0);
441 askit
442 .try_send_agent_out(agent_id.clone(), ctx, pin, data)
443 .unwrap_or_else(|e| {
444 log::error!("Failed to send delayed output: {}", e);
445 });
446 }
447
448 if wd.len() == 0 {
450 handle.take();
451 break;
452 }
453 }
454 });
455
456 if let Ok(mut timer_handle) = self.timer_handle.lock() {
458 *timer_handle = Some(handle);
459 }
460
461 Ok(())
462 }
463
464 fn stop_timer(&mut self) -> Result<(), AgentError> {
465 if let Ok(mut timer_handle) = self.timer_handle.lock() {
467 if let Some(handle) = timer_handle.take() {
468 handle.abort();
469 }
470 }
471 Ok(())
472 }
473}
474
475#[async_trait]
476impl AsAgent for ThrottleTimeAgent {
477 fn new(askit: ASKit, id: String, spec: AgentSpec) -> Result<Self, AgentError> {
478 let time = spec
479 .configs
480 .as_ref()
481 .ok_or(AgentError::NoConfig)?
482 .get_string_or(CONFIG_TIME, TIME_DEFAULT);
483 let time_ms = parse_duration_to_ms(&time)?;
484
485 let max_num_data = spec
486 .configs
487 .as_ref()
488 .ok_or(AgentError::NoConfig)?
489 .get_integer_or(CONFIG_MAX_NUM_DATA, 0);
490
491 Ok(Self {
492 data: AgentData::new(askit, id, spec),
493 timer_handle: Default::default(),
494 time_ms,
495 max_num_data,
496 waiting_data: Arc::new(Mutex::new(vec![])),
497 })
498 }
499
500 async fn stop(&mut self) -> Result<(), AgentError> {
501 self.stop_timer()
502 }
503
504 fn configs_changed(&mut self) -> Result<(), AgentError> {
505 let time = self.configs()?.get_string(CONFIG_TIME)?;
507 let new_time = parse_duration_to_ms(&time)?;
508 if new_time != self.time_ms {
509 self.time_ms = new_time;
510 }
511
512 let max_num_data = self.configs()?.get_integer(CONFIG_MAX_NUM_DATA)?;
514 if self.max_num_data != max_num_data {
515 let mut wd = self.waiting_data.lock().unwrap();
516 let wd_len = wd.len();
517 if max_num_data >= 0 && wd_len > (max_num_data as usize) {
518 wd.drain(0..(wd_len - (max_num_data as usize)));
520 }
521 self.max_num_data = max_num_data;
522 }
523 Ok(())
524 }
525
526 async fn process(
527 &mut self,
528 ctx: AgentContext,
529 pin: String,
530 value: AgentValue,
531 ) -> Result<(), AgentError> {
532 if self.timer_handle.lock().unwrap().is_some() {
533 let mut wd = self.waiting_data.lock().unwrap();
535
536 if self.max_num_data == 0 {
538 return Ok(());
539 }
540
541 wd.push((ctx, pin, value));
542 if self.max_num_data > 0 && wd.len() > self.max_num_data as usize {
543 wd.remove(0);
545 }
546
547 return Ok(());
548 }
549
550 self.start_timer()?;
552
553 self.output(ctx, pin, value).await?;
555
556 Ok(())
557 }
558}
559
560fn parse_duration_to_ms(duration_str: &str) -> Result<u64, AgentError> {
562 const MIN_DURATION: u64 = 10;
563
564 let re = Regex::new(r"^(\d+)(?:([a-zA-Z]+))?$").expect("Failed to compile regex");
566
567 if let Some(captures) = re.captures(duration_str.trim()) {
568 let value: u64 = captures.get(1).unwrap().as_str().parse().map_err(|e| {
569 AgentError::InvalidConfig(format!(
570 "Invalid number in duration '{}': {}",
571 duration_str, e
572 ))
573 })?;
574
575 let unit = captures
577 .get(2)
578 .map_or("s".to_string(), |m| m.as_str().to_lowercase());
579
580 let milliseconds = match unit.as_str() {
582 "ms" => value, "s" => value * 1000, "m" => value * 60 * 1000, "h" => value * 3600 * 1000, "d" => value * 86400 * 1000, _ => {
588 return Err(AgentError::InvalidConfig(format!(
589 "Unknown time unit: {}",
590 unit
591 )));
592 }
593 };
594
595 Ok(std::cmp::max(milliseconds, MIN_DURATION))
597 } else {
598 let value: u64 = duration_str.parse().map_err(|e| {
601 AgentError::InvalidConfig(format!("Invalid duration format '{}': {}", duration_str, e))
602 })?;
603 Ok(std::cmp::max(value * 1000, MIN_DURATION)) }
605}