Skip to main content

What is a Signal?

A Signal is the fundamental data structure in AveniECA that represents the state of a digital twin at a specific point in time. Every piece of information flowing through the system—whether streamed via Kafka or sent through the REST API—is represented as a Signal.

Signal Structure

The Signal dataclass is defined in avenieca/data.py:24-29:
from dataclasses import dataclass
from typing import Optional, List

@dataclass
class Signal(Base):
    state: List[float]
    valence: Optional[float] = None
    score: Optional[int] = None
    emb_inp: Optional[int] = None

Field Descriptions

state
List[float]
required
The state vector representing the current condition of the digital twin. This is a list of floating-point numbers that encode the twin’s state in multi-dimensional space.Examples:
  • Temperature sensor: [22.5]
  • RGB color: [0.8, 0.3, 0.2]
  • Multi-sensor: [temp, humidity, pressure] = [22.5, 65.0, 1013.25]
valence
float
default:"None"
An evaluative or emotional assessment of the state, typically ranging from negative (undesirable) to positive (desirable). This cybernetic feedback helps the system learn which states are preferable.Examples:
  • Comfortable temperature: valence=10.0
  • Overheating: valence=-90.0
  • Neutral state: valence=0.0
score
int
default:"None"
A numeric ranking or importance value for this state. Often used to indicate the significance or priority of a particular state observation.Examples:
  • Critical alert: score=100
  • Normal reading: score=1
  • Low priority: score=0
emb_inp
int
default:"None"
Reference to an embedding input ID that provides semantic or contextual understanding of the state. This links the state vector to human-readable descriptions or natural language inputs.Use case: When state=[25.0] represents a temperature, emb_inp might reference “Temperature reading from living room sensor at 2pm”

Creating Signals

Basic Signal

The simplest signal contains just a state vector:
from avenieca.data import Signal

# Single-dimensional state
temperature_signal = Signal(state=[22.5])

# Multi-dimensional state
rgb_signal = Signal(state=[0.8, 0.3, 0.2])

Signal with Valence

Add evaluative feedback to help the system learn preferences:
# Positive valence for comfortable temperature
comfortable = Signal(
    state=[22.5],
    valence=10.0
)

# Negative valence for uncomfortable temperature
too_hot = Signal(
    state=[35.0],
    valence=-90.0
)

Complete Signal

Include all fields for maximum context:
full_signal = Signal(
    state=[0.2, 0.3, 0.8],
    valence=9.0,
    score=4,
    emb_inp=1
)

State Vectors Explained

What is a State Vector?

A state vector is a list of numbers that represents the condition of your digital twin in multi-dimensional space. Think of it as coordinates that uniquely identify where your twin is in its “state space.”

Dimensionality

The number of elements in the state vector determines its dimensionality:
  • 1D: Single value like temperature [22.5]
  • 2D: Two values like coordinates [latitude, longitude]
  • 3D: Three values like RGB color [red, green, blue]
  • N-D: Any number of values representing complex state

Design Considerations

Keep state vectors consistent: All signals for a given module_id should have the same dimensionality and order of components.
# Good: Consistent structure
signal1 = Signal(state=[temp, humidity])  # [22.5, 65.0]
signal2 = Signal(state=[temp, humidity])  # [23.0, 63.0]

# Bad: Inconsistent structure
signal1 = Signal(state=[temp, humidity])     # [22.5, 65.0]
signal2 = Signal(state=[humidity, temp])     # [63.0, 23.0] - wrong order!
signal3 = Signal(state=[temp, humidity, pressure])  # [23.0, 63.0, 1013.25] - wrong dimension!

Valence: The Cybernetic Feedback

Purpose of Valence

Valence provides evaluative feedback that helps the system understand which states are desirable. This is a key component of the cybernetic aspect of the ECA model—the system learns from feedback loops.

Valence Ranges

While valence can be any float value, common patterns include:
  • Positive valence (+1 to +100): Desirable states
  • Zero valence (0): Neutral states
  • Negative valence (-1 to -100): Undesirable states

Real-World Examples

# HVAC system learning optimal temperatures

def create_temperature_signal(celsius):
    # Define comfort zone
    if 20 <= celsius <= 24:
        valence = 10.0  # Comfortable
    elif 18 <= celsius < 20 or 24 < celsius <= 26:
        valence = 0.0   # Acceptable
    else:
        valence = -90.0  # Uncomfortable
    
    return Signal(
        state=[celsius],
        valence=valence
    )

Score: Ranking State Importance

Purpose of Score

The score field allows you to rank or prioritize different state observations. Higher scores typically indicate more important or significant states.

Common Use Cases

  1. Time-based priority: More recent observations get higher scores
  2. Event significance: Critical events get higher scores than routine ones
  3. Confidence levels: More certain measurements get higher scores
  4. Business priority: States affecting business KPIs get higher scores

Example: Alert System

class AlertLevel:
    INFO = 1
    WARNING = 10
    CRITICAL = 100

# Normal operation
normal = Signal(
    state=[temperature],
    valence=10.0,
    score=AlertLevel.INFO
)

# Warning condition
warning = Signal(
    state=[temperature],
    valence=-20.0,
    score=AlertLevel.WARNING
)

# Critical condition
critical = Signal(
    state=[temperature],
    valence=-90.0,
    score=AlertLevel.CRITICAL
)

Embedding Inputs

The emb_inp field creates a link between numeric state vectors and semantic, human-readable information through the embedding system.

How It Works

  1. Create an embedding input with natural language description:
    embedding = EmbeddingInputInsert(
        module_id="air_conditioner",
        input="Temperature reading from bedroom AC at 2pm",
        hash=avenieca.encode("my_secret", "the inputs")
    )
    res, status = eca.embedding.create(data=embedding)
    # Returns: {"id": 1, ...}
    
  2. Reference it in your signal:
    signal = Signal(
        state=[25.0],
        valence=10.0,
        emb_inp=1  # Links to the embedding above
    )
    
  3. Retrieve semantic information when querying:
    # Get ESS with pretty formatting that includes embedding text
    res, status = eca.ess.get_one_pretty(module_id="air_conditioner", db_id=1)
    

Streaming Signals

Continuous Streaming

Use the Stream producer for continuous state updates:
from avenieca.producers import Stream
from avenieca.config.broker import Broker
import os

def sensor_handler():
    # Read from sensor
    value = read_sensor()
    return Signal(
        state=[value],
        valence=calculate_valence(value)
    )

config = Broker(
    url=os.environ["KAFKA_URL"],
    sub_topic="left_wheel",
    group="sensors",
    pub_topic=""
)

stream = Stream(config=config, sync_rate=1.0)  # Every 1 second
stream.publish(sensor_handler)

Event-Based Signals

Use the Event producer for one-time or triggered signals:
from avenieca.producers import Event

# Create signal
signal = Signal(
    state=[0.2, 0.3, 0.8],
    valence=9.0,
    score=4
)

# Publish once
event = Event(config=config)
event.publish(signal)

Signal Utilities

AveniECA provides utility functions for working with signals from Kafka:
from avenieca.utils.signal import get_state_as_list, get_state_as_array
import numpy as np

# Kafka consumer handler
def process_signal(data):
    # Data arrives as: {"state": "[0.2, 0.3, 0.8]", "valence": 10}
    
    # Convert to Python list
    get_state_as_list(data)
    # Now: data["state"] == [0.2, 0.3, 0.8]
    
    # Or convert to NumPy array
    get_state_as_array(data)
    # Now: data["state"] == np.array([0.2, 0.3, 0.8])

Best Practices

All signals for a given module_id should have the same state vector dimensionality and component order. This ensures the system can properly learn patterns and make predictions.
Valence should reflect genuine preferences or assessments. Don’t use random values—the system learns from this feedback to understand which states are desirable.
Consider normalizing state values to similar ranges (e.g., 0-1 or -1 to 1) to prevent any single component from dominating the state vector.
# Good: Normalized values
signal = Signal(state=[0.75, 0.23, 0.91])  # All in 0-1 range

# Problematic: Mixed scales
signal = Signal(state=[0.5, 1000, 0.3])  # Middle value dominates
For complex systems, use emb_inp to link state vectors to human-readable descriptions. This makes debugging easier and enables semantic queries.

Next Steps

Digital Twins

Learn how signals flow to digital twins

Streaming Guide

Stream signals via Kafka

API Reference

Complete Signal API documentation

Build docs developers (and LLMs) love