Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions .github/workflows/arduino.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
name: Arduino

on:
pull_request:
paths:
- "Makefile"
- "arduino/Makefile"
- "src/*"
- "include/*.h"
- .github/workflows/arduino.yaml
push:
tags:
- "v*"

permissions:
contents: read

jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- name: checkout repo
uses: actions/checkout@v6

- name: Install build dependencies
run: sudo apt update -y && sudo apt install -y build-essential zip

- name: Build Arduino library
run: make arduino
working-directory: arduino

- name: Lint # must be done after build to generate all the files
uses: arduino/arduino-lint-action@v3.0.0

Check warning

Code scanning / CodeQL

Unpinned tag for a non-immutable Action in workflow or composite action Medium

Unpinned 3rd party Action 'Arduino' step
Uses Step
uses 'arduino/arduino-lint-action' with ref 'v3.0.0', not a pinned commit hash
with:
path: arduino/Spot
compliance: specification
project-type: library

- uses: actions/upload-artifact@v7
with:
name: Spot
path: arduino/Spot.zip
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@


LIB := libspot
VERSION := 3.0.0
VERSION := 3.0.1
# revision should be incremented when releasing an updated package
# of the same upstream version, and it should reset to 1 when
# bumping the version.
Expand Down
48 changes: 48 additions & 0 deletions arduino/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
ROOT_DIR = $(shell realpath $(CURDIR)/..)
# arduino lib
ARDUINO_DIR = $(CURDIR)
ARDUINO_LIB = Spot
ARDUINO_LIB_DIR = $(ARDUINO_DIR)/$(ARDUINO_LIB)
ARDUINO_DIST_DIR = $(ARDUINO_DIR)

LIBSPOT_DIST_DIR = $(ROOT_DIR)/dist

SORTED_HEADERS = $(shell cc -MM $(ROOT_DIR)/include/*.h|sed 's,[a-z0-9]*[.]o[:][ ],,g'|sed ':a;/\\$$/{N;s/\\\n */ /;ba}'|awk '{for(i=2;i<=NF;i++) print $$i, $$1}'|tsort|tr '\n' ' ')
SORTED_SRCS = $(shell echo "$(SORTED_HEADERS)"|sed 's,[.]h,.c,g'|sed 's,include[/],src/,g')

VERSION = $(shell make -s -C $(ROOT_DIR) version)

# ========================================================================== #
# Arduino
# ========================================================================== #

arduino: $(ARDUINO_DIST_DIR)/$(ARDUINO_LIB).zip

install: $(HOME)/Arduino/libraries/$(ARDUINO_LIB)

$(LIBSPOT_DIST_DIR)/spot.h:
@make -C $(ROOT_DIR) api

$(ARDUINO_LIB_DIR)/src/$(ARDUINO_LIB).h: $(LIBSPOT_DIST_DIR)/spot.h
@mkdir -p $(@D)
@cp -u $< $@

$(ARDUINO_LIB_DIR)/src/$(ARDUINO_LIB).cpp: $(SORTED_SRCS)
@mkdir -p $(@D)
echo '#include "$(ARDUINO_LIB).h"' > $@
cat $^ | sed 's/^#include ".*h"//g' | sed 's,^[/][/].*,,g' | awk '/^[/][*][*]/{f=1} !f;/^[ ][*][/]/{f=0}' >> $@

$(ARDUINO_DIR)/$(ARDUINO_LIB).zip: $(ARDUINO_LIB_DIR)/src/$(ARDUINO_LIB).h $(ARDUINO_LIB_DIR)/src/$(ARDUINO_LIB).cpp $(ARDUINO_LIB_DIR)/library.properties
@mkdir -p $(@D)
sed -i $(ARDUINO_LIB_DIR)/library.properties -e 's/^version=.*$$/version=$(VERSION)/g'
cd $(ARDUINO_DIR) && zip -r $(ARDUINO_LIB).zip $(ARDUINO_LIB)/*

$(HOME)/Arduino/libraries/$(ARDUINO_LIB): $(ARDUINO_LIB_DIR)/src/$(ARDUINO_LIB).h $(ARDUINO_LIB_DIR)/src/$(ARDUINO_LIB).cpp $(ARDUINO_LIB_DIR)/library.properties
@mkdir -p $(@D)
cp -ru $(ARDUINO_LIB_DIR) $@

clean:
rm -rf $(ARDUINO_LIB_DIR)/src
rm -f $(ARDUINO_DIR)/$(ARDUINO_LIB).zip


95 changes: 95 additions & 0 deletions arduino/Spot/examples/Basic/Basic.ino
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// Basic.ino

#include "math.h"
#include "Spot.h"

#define MAX_EXCESS 200
#define TRAIN_SIZE 20000
#define TEST_SIZE 500000

struct Spot spot;
double q = 1e-4; // anomaly probability
int low = 0; // observe upper tail
int discard_anomalies = 0; // reject anomalies from the model
double level = 0.99; // tail quantile
double buffer[MAX_EXCESS];

double train[TRAIN_SIZE];

int status = 0;
unsigned long testStartMs = 0;

// N(0, 1) - Box-Muller transform
double gaussian_random() {
double u = 1.0 - ((double)rand() / (double)RAND_MAX);
double v = (double)rand() / (double)RAND_MAX;
return sqrt(-2.0 * log(u)) * cos(2.0 * M_PI * v);
}

void setup() {
Serial.begin(115200);

// provide platform specidic math function
set_math_functions(log, exp, pow);

// init the structure
status = spot_init(&spot, q, low, discard_anomalies, level, buffer,
MAX_EXCESS, );
if (spot_init(&spot, q, low, discard_anomalies, level, buffer,
MAX_EXCESS) < 0) {
Serial.printf("spot_init failed, code: %d\n", -status);
while (true) {
delay(1000);
}
}
Serial.println("spot_init succeeded");

// initial data
for (unsigned long i = 0; i < TRAIN_SIZE; i++) {
train[i] = gaussian_random();
}

// fit
status = spot_fit(&spot, train, TRAIN_SIZE);
if (status < 0) {
Serial.printf("spot_fit failed, code: %d\n", -status);
while (true) {
delay(1000);
}
}
Serial.println("spot_fit succeeded");

// start
testStartMs = millis();
}

unsigned int n = 0;
int normal = 0;
int excess = 0;
int anomaly = 0;

void loop() {
n++;
if (n == TEST_SIZE) {
unsigned long elapsedMs = millis() - testStartMs;

Serial.printf("Elapsed time: %d ms (%d values)\n", elapsedMs,
TEST_SIZE);
Serial.printf("Throughput: %f values/s\n",
(double)TEST_SIZE / ((double)elapsedMs / 1000.0));
Serial.printf("NORMAL:%d EXCESS:%d ANOMALY:%d\n", normal, excess,
anomaly);
} else {
switch (spot_step(&spot, gaussian_random())) {
case ANOMALY:
anomaly++;
break;
case EXCESS:
excess++;
break;
case NORMAL:
normal++;
break;
}
}
}
9 changes: 9 additions & 0 deletions arduino/Spot/library.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
name=Spot
version=3.0.0
author=Alban Siffer <31479857+asiffer@users.noreply.github.com>
maintainer=Alban Siffer <31479857+asiffer@users.noreply.github.com>
sentence=Born to flag outliers (from arduino)
paragraph=Spot algorithm that detects anomalies in streaming data
category=Data Processing
url=https://github.com/asiffer/libspot/
architectures=*
53 changes: 53 additions & 0 deletions docs/foreign/arduino.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
---
title: Arduino
summary: AI on dev board
---

Remember, **libspot** is a modest `C` library that could run on modest systems.
Since version `3.0.1`, we ship a dedicated [Arduino library](https://docs.arduino.cc/libraries/), called **Spot**.
You can then directly import library code into your next IoT project.

!!! info ""
**libspot** source code is licensed under `LGPLv3`. As the code is likely to be directly compiled into your final program,
you should probably share it in a way. The simplest way to comply is to just include a copy of this library's source (unmodified or with your changes clearly marked) alongside your project.

## Install

Just grab the archive from the latest release, and extract it to the default libraries directory.

//// tab | Linux

```bash
curl -L -o /tmp/Spot.zip "https://github.com/asiffer/libspot/releases/latest/download/Spot.zip"
unzip -o /tmp/Spot.zip -d ~/Arduino/libraries/
```

////

//// tab | Windows

```powershell
Invoke-WebRequest -Uri "https://github.com/asiffer/libspot/releases/latest/download/Spot.zip" -OutFile "$env:TEMP\Spot.zip"; Expand-Archive -Path "$env:TEMP\Spot.zip" -DestinationPath "$env:USERPROFILE\Documents\Arduino\libraries" -Force
```

////

//// tab | MacOS

```bash
curl -L -o /tmp/Spot.zip "https://github.com/asiffer/libspot/releases/latest/download/Spot.zip"
unzip -o /tmp/Spot.zip -d ~/Documents/Arduino/libraries/
```

////


## Example

The following example roughly follows the **libspot** benchmark.
On `ESP32-C3` board, you can expect about **1500 values/s**.

<!-- prettier-ignore -->
```arduino
--8<-- "arduino/Spot/examples/Basic/Basic.ino"
```
4 changes: 2 additions & 2 deletions docs/foreign/javascript.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
title: Javascript
icon: material/language-javascript
summary: libspot in the browser
---

**libspot** has also been ported to the browser (and more generally to the `js` ecosystem) through [webassembly +simple-icons:webassembly+](https://developer.mozilla.org/en-US/docs/WebAssembly/Concepts) thanks to `clang` & `wasm-ld` (see [this example](https://log.schemescape.com/posts/webassembly/trivial-example.html)).
Expand Down Expand Up @@ -44,7 +44,7 @@ You then get a typescript library that wraps the webassembly code.
/// codexec

:::typescript
import spot from "https://esm.sh/libspot@2.0.0-beta.5"
import spot from "https://esm.sh/libspot@latest"

console.log(spot);

Expand Down
2 changes: 1 addition & 1 deletion docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,5 +37,5 @@ While the first implementation of **libspot** was in `C++`, this new version has
The development of **libspot** tries to follow some principles:

- +lucide:computer+ **UNIX philosophy**: do one thing and do it well. It implies that the library should not embed code that provides too specific features, extra utilities or additional abstraction layers. In return, the project must also meet high quality: code, API, documentation and testing are the main undertakings.
- +lucide:feather+ **Lightness**: the library should have a low footprint. Actually the SPOT algorithm is against the flow: far from all the deep models that more and more overwhelm our daily life. Obviously it is also far less powerful :grin: but its use-cases are definitely different. In a word, SPOT is a cheap algorithm that must be able to run on cheap systems.
- +lucide:feather+ **Lightness**: the library should have a low footprint. Actually the SPOT algorithm is against the flow: far from all the deep models that more and more overwhelm our daily life. Obviously it is also far less powerful +twemoji:grinning-face-with-smiling-eyes+ but its use-cases are definitely different. In a word, SPOT is a cheap algorithm that must be able to run on cheap systems.
- +lucide:handbag+ **Portability/Extensibility**: in relation with the lightness goal, the library should be easily integrable to any ecosystem. This is why **libspot** is developed in `C99` without depending on the standard library. In addition, I would like to provide some bindings that will help developers using the algorithm.
2 changes: 2 additions & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ nav:
- foreign/javascript.md
- foreign/golang.md
- foreign/rust.md
- foreign/arduino.md
- foreign/performances.md
- Guides:
- "Drifting data": guides/drifting.md
Expand Down Expand Up @@ -57,3 +58,4 @@ markdown_extensions:
- shadcn.extensions.iconify
- shadcn.extensions.codexec
- shadcn.extensions.echarts.alpha

2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "libspot-docs"
version = "3.0.0"
version = "3.0.1"
description = "Born to flag outliers"
authors = [
{ name = "Alban Siffer", email = "31479857+asiffer@users.noreply.github.com" },
Expand Down
2 changes: 1 addition & 1 deletion src/xmath.c
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ static double nostdlib_exp(double x) {
return _NAN;
}
if (x < 0) {
return 1.0 / xexp(-x);
return 1.0 / nostdlib_exp(-x);
}
if (x > LOG2) {
unsigned int k = x / LOG2;
Expand Down
Loading
Loading