Filter and transform

You can let the OOCSI server do some computing work for you and this works on ALL platforms that OOCSI supports: by subscribing from a client to a channel with additional filter and/or transform expressions, you can control which messages will be received on the client (“filter”) and/or you can compute new values based on the message contents (“transform”).

Filtering messages

Let’s start with a filter example. Suppose an environmental sensor publishes events with temperature and humidity to a channel climate. We only want our client to wake up when temperature > 25:

// Java / Processing
oocsi.subscribe("climate[filter(temperature > 25)]", "handleClimate");
# Python
oocsi.subscribe("climate[filter(temperature > 25)]", handle_climate)
// JavaScript (oocsi-web.js)
OOCSI.subscribe("climate[filter(temperature > 25)]", (msg) => {
  console.log("High temperature alert:", msg.data.temperature);
});
// ESP32 (Embedded C++ / Arduino)
oocsi.subscribe("climate[filter(temperature > 25)]", handleClimate);

You can also use boolean logic to create compound conditions:

// Java / Processing: Filter for hot AND humid conditions
oocsi.subscribe("climate[filter(temperature > 25 && humidity > 70)]", "handleClimate");

Transforming messages

With transform, you can compute new values directly on the server before the message reaches your client. The result can be stored in a new key or replace an existing key.

For instance, converting a temperature from Celsius to Fahrenheit:

// Java / Processing: Add a new fahrenheit field: (temp * 1.8) + 32
oocsi.subscribe("climate[transform(fahrenheit, (temperature * 1.8) + 32)]", "handleClimate");
# Python
oocsi.subscribe("climate[transform(fahrenheit, (temperature * 1.8) + 32)]", handle_climate)
// JavaScript (oocsi-web.js)
OOCSI.subscribe("climate[transform(fahrenheit, (temperature * 1.8) + 32)]", (msg) => {
  console.log("Fahrenheit:", msg.data.fahrenheit);
});
// ESP32 (Embedded C++ / Arduino)
oocsi.subscribe("climate[transform(fahrenheit, (temperature * 1.8) + 32)]", handleClimate);

Combining filter and transform

You can chain multiple filter and transform expressions together in a single subscription by separating them with semicolons ;:

// Java / Processing: Filter for temperature > 25 AND compute fahrenheit
oocsi.subscribe("climate[filter(temperature > 25); transform(fahrenheit, (temperature * 1.8) + 32)]", "handleClimate");
# Python
oocsi.subscribe("climate[filter(temperature > 25); transform(fahrenheit, (temperature * 1.8) + 32)]", handle_climate)
// JavaScript (oocsi-web.js)
OOCSI.subscribe("climate[filter(temperature > 25); transform(fahrenheit, (temperature * 1.8) + 32)]", (msg) => {
  console.log("Filtered & Transformed:", msg.data);
});
// ESP32 (Embedded C++ / Arduino)
oocsi.subscribe("climate[filter(temperature > 25); transform(fahrenheit, (temperature * 1.8) + 32)]", handleClimate);

Expressions

Supported Operators

Mathematical Operators
OperatorDescription
+Additive operator / Unary plus
-Subtraction operator / Unary minus
*Multiplication operator, can be omitted in front of an open bracket
/Division operator
%Remainder operator (Modulo)
^Power operator
Boolean Operators*
OperatorDescription
=Equals
==Equals
!=Not equals
<>Not equals
<Less than
<=Less than or equal to
>Greater than
>=Greater than or equal to
&&Boolean and
||Boolean or

*Boolean operators result always in a BigDecimal value of 1 or 0 (zero). Any non-zero value is treated as a true value. Boolean not is implemented by a function.

Supported Functions

Function*Description
NOT(expression)Boolean negation, 1 (means true) if the expression is not zero
IF(condition,value_if_true,value_if_false)Returns one value if the condition evaluates to true or the other if it evaluates to false
RANDOM()Produces a random number between 0 and 1
MIN(e1,e2, ...)Returns the smallest of the given expressions
MAX(e1,e2, ...)Returns the biggest of the given expressions
ABS(expression)Returns the absolute (non-negative) value of the expression
ROUND(expression,precision)Rounds a value to a certain number of digits, uses the current rounding mode
FLOOR(expression)Rounds the value down to the nearest integer
CEILING(expression)Rounds the value up to the nearest integer
LOG(expression)Returns the natural logarithm (base e) of an expression
LOG10(expression)Returns the common logarithm (base 10) of an expression
SQRT(expression)Returns the square root of an expression
SIN(expression)Returns the trigonometric sine of an angle (in degrees)
COS(expression)Returns the trigonometric cosine of an angle (in degrees)
TAN(expression)Returns the trigonometric tangens of an angle (in degrees)
COT(expression)Returns the trigonometric cotangens of an angle (in degrees)
ASIN(expression)Returns the angle of asin (in degrees)
ACOS(expression)Returns the angle of acos (in degrees)
ATAN(expression)Returns the angle of atan (in degrees)
ACOT(expression)Returns the angle of acot (in degrees)
ATAN2(y,x)Returns the angle of atan2 (in degrees)
SINH(expression)Returns the hyperbolic sine of a value
COSH(expression)Returns the hyperbolic cosine of a value
TANH(expression)Returns the hyperbolic tangens of a value
COTH(expression)Returns the hyperbolic cotangens of a value
SEC(expression)Returns the secant (in degrees)
CSC(expression)Returns the cosecant (in degrees)
SECH(expression)Returns the hyperbolic secant (in degrees)
CSCH(expression)Returns the hyperbolic cosecant (in degrees)
ASINH(expression)Returns the angle of hyperbolic sine (in degrees)
ACOSH(expression)Returns the angle of hyperbolic cosine (in degrees)
ATANH(expression)Returns the angle of hyperbolic tangens of a value
RAD(expression)Converts an angle measured in degrees to an approximately equivalent angle measured in radians
DEG(expression)Converts an angle measured in radians to an approximately equivalent angle measured in degrees
FACT(expression)Note: Factorial is disabled on the OOCSI server for security.

*Functions names are case insensitive.

Supported Constants

ConstantDescription
eThe value of e, exact to 70 digits
PIThe value of PI, exact to 100 digits
TRUEThe value one
FALSEThe value zero
NULLThe null value

Rolling Window Functions

When working with sensor streams or interactive input, you frequently need to calculate rolling averages, sums, or peak values over time without maintaining client-side buffers. The OOCSI server provides five built-in sliding window aggregation functions:

FunctionDescription
mean(variable, n)Calculates the arithmetic mean (average) of variable over the last n events.
sum(variable, n)Calculates the running sum of variable over the last n events.
stdev(variable, n)Calculates the standard deviation of variable over the last n events.
emin(variable, n)Calculates the minimum value of variable over the last n events.
emax(variable, n)Calculates the maximum value of variable over the last n events.

Important notes on window functions:

  • Window size cap: The window length n is automatically capped at 50 events (n <= 50).
  • emin and emax: To calculate running minimum and maximum over a window of n events, use emin and emax. This distinguishes them from the standard mathematical comparison functions MIN(a, b) and MAX(a, b).

Examples in all supported languages

Smooth out a noisy light sensor by computing a 10-sample rolling average directly on the server:

// Java / Processing
oocsi.subscribe("sensors/light[transform(smooth_lux, mean(lux, 10))]", "handleLight");
# Python
oocsi.subscribe("sensors/light[transform(smooth_lux, mean(lux, 10))]", handle_light)
// JavaScript (oocsi-web.js)
OOCSI.subscribe("sensors/light[transform(smooth_lux, mean(lux, 10))]", (msg) => {
  console.log("Smoothed Lux:", msg.data.smooth_lux);
});
// ESP32 (Embedded C++ / Arduino)
oocsi.subscribe("sensors/light[transform(smooth_lux, mean(lux, 10))]", handleLight);

Filter and Transform Intricacies

flowchart TD
    IN["Incoming Message on Channel"] --> TEST_FILTERS{"Evaluate filter(...)<br/>Expressions"}
    
    TEST_FILTERS -->|Any expression FALSE or key missing| DROP["Discard Message<br/>(No delivery, save bandwidth)"]
    
    TEST_FILTERS -->|All expressions TRUE| TRANS{"Any transform(...)<br/>Expressions?"}
    
    TRANS -->|No| DELIVER["Deliver Original Message<br/>to Client"]
    TRANS -->|Yes| RUN_TRANS["Execute Transforms in Sequence<br/>(Missing keys default to 0)"]
    
    RUN_TRANS --> WIN{"Rolling Window Function?<br/>(mean, sum, stdev, emin, emax)"}
    WIN -->|Yes| EVAL_WIN["Update Circular Buffer (cap 50)<br/>Compute Window Value"]
    WIN -->|No| APPLY_TRANS["Update/Add Payload Keys"]
    EVAL_WIN --> APPLY_TRANS
    
    APPLY_TRANS --> DELIVER_TRANS["Deliver Transformed Message<br/>to Client"]

When designing advanced interactions with server-side filters and transforms, keep the following execution details in mind:

1. Order of Execution

When you combine multiple filters and transformations separated by ;:

  1. Filters run first: The server tests all filter(...) expressions. If any filter evaluates to false, the message is immediately dropped. Transformations will not be computed, and no message is delivered to your client.
  2. Transforms run in sequence: If all filters pass, each transform(key, expression) is evaluated in the order specified. Each transformation adds or updates the specified key in the transformed message payload.

2. Handling Missing Keys

Messages on a channel do not always contain the same set of keys. The server handles missing keys differently depending on whether they occur in a filter or a transform:

  • In filter(...): If an expression references a key that is missing from the incoming message, the filter evaluation aborts safely and the message is discarded.
  • In transform(...): If an expression references a missing key, the server does not discard the message; instead, the missing key safely defaults to 0.

3. Evaluation Safety and Error Handling

If an expression produces a runtime error (such as division by zero or invalid type conversion), the server catches the exception safely and drops the message. Your client and the server remain stable and unaffected.

4. Combining with Password-Protected Channels

You can apply filters and transforms to private, password-protected channels by specifying the password before the bracketed expressions:

// Java / Processing
oocsi.subscribe("secret_lab:myPass123[filter(temp > 30); transform(temp_f, (temp * 1.8) + 32)]", "handleAlert");
# Python
oocsi.subscribe("secret_lab:myPass123[filter(temp > 30); transform(temp_f, (temp * 1.8) + 32)]", handle_alert)
// JavaScript (oocsi-web.js)
OOCSI.subscribe("secret_lab:myPass123[filter(temp > 30); transform(temp_f, (temp * 1.8) + 32)]", (msg) => {
  console.log("High temp alert:", msg.data);
});
// ESP32 (Embedded C++ / Arduino)
oocsi.subscribe("secret_lab:myPass123[filter(temp > 30); transform(temp_f, (temp * 1.8) + 32)]", handleAlert);

5. Security Restrictions and Syntax Constraints

  • Length limit: The function string inside [...] can be at most 512 characters.
  • Balanced brackets: Brackets and parentheses must be properly closed and balanced.
  • Blocked functions: To protect server resources and prevent denial-of-service, potentially dangerous or unbounded functions in the underlying expression engine (such as FACT, STR_FORMAT, STR_MATCHES, DT_DATE_NEW, and DT_DURATION_NEW) are blocked.

Credits

All expressions are evaluated with the EvalEx library, with custom extensions and security hardening for OOCSI.


Copyright © 2013-2026 Mathias Funk.

This site uses Just the Docs, a documentation theme for Jekyll.