Files
2026-02-04 17:46:30 -06:00

12 KiB

Attune Timer Sensor

A standalone sensor daemon for the Attune automation platform that monitors timer-based triggers and emits events. This sensor manages multiple concurrent timer schedules based on active rules.

Overview

The timer sensor is a lightweight, event-driven process that:

  • Listens for rule lifecycle events via RabbitMQ
  • Manages per-rule timer tasks dynamically
  • Emits events to the Attune API when timers fire
  • Supports interval-based, cron-based, and datetime-based timers
  • Authenticates using service account tokens

Architecture

┌─────────────────────────────────────────────────────────────┐
│ Timer Sensor Process                                         │
│                                                              │
│  ┌────────────────┐    ┌──────────────────┐                │
│  │ Rule Lifecycle │───▶│  Timer Manager   │                │
│  │   Listener     │    │                  │                │
│  │  (RabbitMQ)    │    │ ┌──────────────┐ │                │
│  └────────────────┘    │ │ Rule 1 Timer │ │                │
│                        │ ├──────────────┤ │                │
│                        │ │ Rule 2 Timer │ │───┐            │
│                        │ ├──────────────┤ │   │            │
│                        │ │ Rule 3 Timer │ │   │            │
│                        │ └──────────────┘ │   │            │
│                        └──────────────────┘   │            │
│                                               │            │
│  ┌────────────────┐                           │            │
│  │  API Client    │◀──────────────────────────┘            │
│  │ (Create Events)│                                        │
│  └────────────────┘                                        │
└─────────────────────────────────────────────────────────────┘
         │                                  ▲
         │ Events                           │ Rule Lifecycle
         ▼                                  │ Messages
┌─────────────────┐              ┌─────────────────┐
│  Attune API     │              │   RabbitMQ      │
└─────────────────┘              └─────────────────┘

Features

  • Per-Rule Timers: Each rule gets its own independent timer task
  • Dynamic Management: Timers start/stop automatically based on rule lifecycle
  • Multiple Timer Types:
    • Interval: Fire every N seconds/minutes/hours/days
    • Cron: Fire based on cron expression (planned)
    • DateTime: Fire at a specific date/time
  • Resilient: Retries event creation with exponential backoff
  • Secure: Token-based authentication with trigger type restrictions
  • Observable: Structured JSON logging for monitoring

Installation

From Source

cargo build --release --package core-timer-sensor
sudo cp target/release/attune-core-timer-sensor /usr/local/bin/

Using Cargo Install

cargo install --path crates/core-timer-sensor

Configuration

Environment Variables

The sensor requires the following environment variables:

Variable Required Description Example
ATTUNE_API_URL Yes Base URL of the Attune API http://localhost:8080
ATTUNE_API_TOKEN Yes Service account token eyJhbGci...
ATTUNE_SENSOR_REF Yes Sensor reference (must be core.timer) core.timer
ATTUNE_MQ_URL Yes RabbitMQ connection URL amqp://localhost:5672
ATTUNE_MQ_EXCHANGE No RabbitMQ exchange name attune (default)
ATTUNE_LOG_LEVEL No Logging verbosity info (default)

Example: Environment Variables

export ATTUNE_API_URL="http://localhost:8080"
export ATTUNE_API_TOKEN="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
export ATTUNE_SENSOR_REF="core.timer"
export ATTUNE_MQ_URL="amqp://localhost:5672"
export ATTUNE_LOG_LEVEL="info"

attune-core-timer-sensor

Example: stdin Configuration

echo '{
  "api_url": "http://localhost:8080",
  "api_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "sensor_ref": "core.timer",
  "mq_url": "amqp://localhost:5672",
  "mq_exchange": "attune",
  "log_level": "info"
}' | attune-core-timer-sensor --stdin-config

Service Account Setup

Before running the sensor, you need to create a service account with the appropriate permissions:

# Create service account (requires admin token)
curl -X POST http://localhost:8080/service-accounts \
  -H "Authorization: Bearer ${ADMIN_TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "sensor:core.timer",
    "scope": "sensor",
    "description": "Timer sensor for interval-based triggers",
    "ttl_hours": 72,
    "metadata": {
      "trigger_types": ["core.timer"]
    }
  }'

# Response will include the token (save this - it's only shown once!)
{
  "identity_id": 123,
  "name": "sensor:core.timer",
  "scope": "sensor",
  "token": "eyJhbGci...",  # Use this as ATTUNE_API_TOKEN
  "expires_at": "2025-01-30T12:34:56Z"  # 72 hours from now
}

Important:

  • The token is only displayed once. Store it securely!
  • Sensor tokens expire after 24-72 hours and must be rotated
  • Plan to rotate the token before expiration (set up monitoring/alerts)

Timer Configuration

Rules using the core.timer trigger must provide configuration in trigger_params:

Interval Timer

Fires every N units of time:

{
  "type": "interval",
  "interval": 30,
  "unit": "seconds"  // "seconds", "minutes", "hours", "days"
}

Examples:

  • Every 5 seconds: {"type": "interval", "interval": 5, "unit": "seconds"}
  • Every 10 minutes: {"type": "interval", "interval": 10, "unit": "minutes"}
  • Every 1 hour: {"type": "interval", "interval": 1, "unit": "hours"}
  • Every 1 day: {"type": "interval", "interval": 1, "unit": "days"}

DateTime Timer

Fires at a specific date/time (one-time):

{
  "type": "date_time",
  "fire_at": "2025-01-27T15:00:00Z"
}

Cron Timer (Planned)

Fires based on cron expression:

{
  "type": "cron",
  "expression": "0 0 * * *"  // Daily at midnight
}

Note: Cron timers are not yet implemented.

Running the Sensor

Development

# Terminal 1: Start dependencies
docker-compose up -d postgres rabbitmq

# Terminal 2: Start API
cd crates/api
cargo run

# Terminal 3: Start sensor
export ATTUNE_API_URL="http://localhost:8080"
export ATTUNE_API_TOKEN="your_sensor_token_here"
export ATTUNE_SENSOR_REF="core.timer"
export ATTUNE_MQ_URL="amqp://localhost:5672"

cargo run --package core-timer-sensor

Production (systemd)

Create a systemd service file at /etc/systemd/system/attune-core-timer-sensor.service:

[Unit]
Description=Attune Timer Sensor
After=network.target rabbitmq-server.service

[Service]
Type=simple
User=attune
WorkingDirectory=/opt/attune
ExecStart=/usr/local/bin/attune-core-timer-sensor
Restart=always
RestartSec=10

# Environment variables
Environment="ATTUNE_API_URL=https://attune.example.com"
Environment="ATTUNE_SENSOR_REF=core.timer"
Environment="ATTUNE_MQ_URL=amqps://rabbitmq.example.com:5671"
Environment="ATTUNE_LOG_LEVEL=info"

# Load token from file
EnvironmentFile=/etc/attune/sensor-timer.env

# Security
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true

[Install]
WantedBy=multi-user.target

Create /etc/attune/sensor-timer.env:

ATTUNE_API_TOKEN=eyJhbGci...

Enable and start:

sudo systemctl daemon-reload
sudo systemctl enable attune-core-timer-sensor
sudo systemctl start attune-core-timer-sensor
sudo systemctl status attune-core-timer-sensor

Token Rotation:

Sensor tokens expire after 24-72 hours. To rotate:

# 1. Create new service account token (via API)
# 2. Update /etc/attune/sensor-timer.env with new token
sudo nano /etc/attune/sensor-timer.env

# 3. Restart sensor
sudo systemctl restart attune-core-timer-sensor

Set up a cron job or monitoring alert to remind you to rotate tokens every 72 hours.

View logs:

sudo journalctl -u attune-core-timer-sensor -f

Monitoring

Logs

The sensor outputs structured JSON logs:

{
  "timestamp": "2025-01-27T12:34:56Z",
  "level": "info",
  "message": "Timer fired for rule 123, created event 456",
  "rule_id": 123,
  "event_id": 456
}

Health Checks

The sensor verifies API connectivity on startup. Monitor the logs for:

  • "API connectivity verified" - Sensor connected successfully
  • "Timer started for rule" - Timer activated for a rule
  • "Timer fired for rule" - Event created by timer
  • "Failed to create event" - Event creation error (check token/permissions)

Troubleshooting

"Invalid sensor_ref: expected 'core.timer'"

The ATTUNE_SENSOR_REF must be exactly core.timer. This sensor only handles timer triggers.

"Failed to connect to Attune API"

  • Verify ATTUNE_API_URL is correct and reachable
  • Check that the API service is running
  • Ensure no firewall blocking the connection

"Insufficient permissions to create event for trigger type 'core.timer'"

The service account token doesn't have permission to create timer events. Ensure the token's metadata includes "trigger_types": ["core.timer"].

"Failed to connect to RabbitMQ"

  • Verify ATTUNE_MQ_URL is correct
  • Check that RabbitMQ is running
  • Ensure credentials are correct in the URL

"Token expired"

The service account token has exceeded its TTL (24-72 hours). This is expected behavior.

Solution:

  1. Create a new service account token via API
  2. Update ATTUNE_API_TOKEN environment variable
  3. Restart the sensor

Prevention:

  • Set up monitoring to alert 6 hours before token expiration
  • Plan regular token rotation (every 72 hours maximum)

Timer not firing

  1. Check that the rule is enabled
  2. Verify the rule's trigger_type is core.timer
  3. Check the sensor logs for "Timer started for rule"
  4. Ensure trigger_params is valid JSON matching the timer config format

Development

Running Tests

cargo test --package core-timer-sensor

Building

# Debug build
cargo build --package core-timer-sensor

# Release build
cargo build --release --package core-timer-sensor

Code Structure

crates/core-timer-sensor/
├── src/
│   ├── main.rs           # Entry point, initialization
│   ├── config.rs         # Configuration loading (env/stdin)
│   ├── api_client.rs     # Attune API communication
│   ├── timer_manager.rs  # Per-rule timer task management
│   ├── rule_listener.rs  # RabbitMQ message consumer
│   └── types.rs          # Shared types and enums
├── Cargo.toml
└── README.md

Contributing

When adding new timer types:

  1. Add variant to TimerConfig enum in types.rs
  2. Implement spawn logic in timer_manager.rs
  3. Add tests for the new timer type
  4. Update this README with examples

License

MIT License - see LICENSE file for details.

See Also