-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add MQTT booth volume demo application
Signed-off-by: Kate Goldenring <[email protected]>
- Loading branch information
1 parent
8616db3
commit 4e3e90b
Showing
39 changed files
with
6,605 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
.spin/ | ||
**/target/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
SPIN_VARIABLE_MQTT_BROKER_URI ?= "mqtt://localhost:1883" | ||
SPIN_VARIABLE_MQTT_TOPIC ?= "booth/+" | ||
REGISTRY ?= ghcr.io/kate-goldenring/spin-apps/mqtt-booth-volume | ||
TAG ?= v0.2.0 | ||
VOLUME ?= 350 | ||
BOOTH ?= 20 | ||
BROKER_HOSTNAME ?= 127.0.0.1 | ||
|
||
all: emqx spin-build-up | ||
|
||
emqx: | ||
docker run -d --name emqx -p 1883:1883 emqx/emqx | ||
|
||
spin-build-up: | ||
SPIN_VARIABLE_MQTT_BROKER_URI=${SPIN_VARIABLE_MQTT_BROKER_URI} SPIN_VARIABLE_MQTT_TOPIC=${SPIN_VARIABLE_MQTT_TOPIC} SPIN_VARIABLE_SQLITE_USERNAME="admin" SPIN_VARIABLE_SQLITE_PASSWORD="password" spin build --up --sqlite @mqtt-message-persister/migration.up.sql | ||
|
||
spin-up: | ||
SPIN_VARIABLE_MQTT_BROKER_URI=${SPIN_VARIABLE_MQTT_BROKER_URI} SPIN_VARIABLE_MQTT_TOPIC=${SPIN_VARIABLE_MQTT_TOPIC} SPIN_VARIABLE_SQLITE_USERNAME="admin" SPIN_VARIABLE_SQLITE_PASSWORD="password" spin up --sqlite @mqtt-message-persister/migration.up.sql | ||
|
||
pub: | ||
mqttx pub -t 'booth/${BOOTH}' --hostname ${BROKER_HOSTNAME} --port 1883 --message '{"volume": ${VOLUME}}' | ||
|
||
clean: | ||
docker rm -f emqx | ||
|
||
app-apply: | ||
spin kube scaffold --from ${REGISTRY}:${TAG} --variable mqtt_broker_uri=${SPIN_VARIABLE_MQTT_BROKER_URI} --variable mqtt_topic="booth/+" --variable sqlite_username="admin" --variable sqlite_password=password --replicas 1 | kubectl apply -f - | ||
|
||
emqx-apply: | ||
kubectl apply -f spinkube/broker-configuration/emqx-pod.yaml | ||
|
||
mock-device-apply: | ||
kubectl apply -f spinkube/sound-device.yaml | ||
|
||
k8s-clean: | ||
kubectl delete -f spinkube/broker.yaml | ||
kubectl delete -f spinkube/sound-device.yaml | ||
spin kube scaffold --from ${REGISTRY}:${TAG} | kubectl delete -f - |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,108 @@ | ||
# spin-mqtt-booth-volume-app | ||
Sample SpinKube application that uses MQTT and HTTP triggers. Ingests noise data from sensors, persists in SQLite DB, and displays over graphical frontend | ||
# Spin MQTT Booth Volume App | ||
|
||
Companies go to conferences to increase the visibility of their products. A big driver for that is getting conference attendees to engage with the company booth. One way engagement is often tracked is through scanning all booth visitors' badges. This is not only a manual process but also fails to indicate the level of engagement of a visitor. Nor does it track the times of the day when visitors are engaged in the booth which could be useful to understand for better booth staffing. | ||
|
||
This Spin app aims to simplify measuring booth engagement at a conference by using the volume of sound around the booth as a proxy for booth engagement at any given point. | ||
|
||
A prerequisite of using this app in real life would be having a sound sensor that publishes sound over MQTT. See the [example sound sensor section](##Example-Sound-Sensor) to reference a device option. | ||
|
||
To run the MQTT broker and Spin app, simply run `make all`. | ||
|
||
## Running an MQTT Broker | ||
|
||
Eclipse provides a free publically available [MQTT broker](https://test.mosquitto.org/) that can be used for demos. Use `test.mosquitto.org` as the broker address with port 1883. Connect and subscribe to a topic on the broker as follows: | ||
|
||
```sh | ||
mqttx sub -h test.mosquitto.org -t "/booth/20" -p 1883 | ||
``` | ||
|
||
Alternatively, run your own broker with [EMQX](https://github.com/emqx/emqx), which is an open source MQTT broker that can easily be [run as a Docker container](https://mqttx.app/docs/get-started). Run it locally on port 1883 in the background as follows: | ||
|
||
```sh | ||
docker run -d --name emqx -p 1883:1883 emqx/emqx | ||
``` | ||
|
||
Connect and subscribe to a topic on the locally running EMQX broker: | ||
```sh | ||
mqttx sub -h '127.0.0.1' -t "/booth/20" -p 1883 | ||
``` | ||
|
||
## Running the spin app to listen to a specific topic | ||
|
||
Now, that our broker is running, we can start our Spin app, setting the broker URI to be our locally running broker. We will also listen for all messages posted to a topic that matches `booth/+`. The `+` sign is a single-level wildcard that will match any string in place of the wildcard, i.e. `booth/20` but not `booth/20/b`. | ||
|
||
```sh | ||
SPIN_VARIABLE_MQTT_BROKER_URI="mqtt://localhost:1883" SPIN_VARIABLE_MQTT_TOPIC="booth/+" spin build --up --sqlite @mqtt-message-persister/migration.up.sql | ||
``` | ||
|
||
## Publishing Messages from a Fake Device | ||
|
||
First, download [MQTTX CLI](https://github.com/emqx/MQTTX/tree/main/cli) which facilitates connecting to a broker and then publishing or subscribing to topics. | ||
|
||
Now, publish to the topic for booth 20. Set the broker host address to that of your broker, we are using Mosquitto's public broker here: | ||
|
||
```sh | ||
mqttx pub -h test.mosquitto.org -t 'booth/20' -p 1883 -m '{"volume": 350}' | ||
``` | ||
|
||
## Example Sound Sensor | ||
|
||
To use a real device with this Spin app, the device must meet the following criteria: | ||
|
||
- Has a sound sensor | ||
- Can publish messages over MQTT (requires WiFi access) | ||
|
||
The [`sound-sensor`](./sound-sensor/mqttsound) folder contains an Anduino program that can be loaded on a device with a sound sensor. It has been tested with a a device comprised of the following components, which all can be purchased from the Arduino store: | ||
|
||
- An [Arduino UNO R4 WiFi Board](https://store-usa.arduino.cc/products/uno-r4-wifi?variant=42871580917967) | ||
- A [Grove Sound Sensor](https://store-usa.arduino.cc/products/grove-sound-sensor?variant=39277290488015) (plugged into A2) | ||
- A [Grove Base Shield V2.0](https://store-usa.arduino.cc/products/grove-base-shield-v2-0-for-arduino?variant=39557870682319) for Arduino | ||
|
||
## Running on SpinKube with Local SQLite Database | ||
|
||
The Spin runtime supports persisting data to SQLite databases. A local one can be used by default. | ||
It cannot be pre-configured in SpinKube since it will be created in the container filesystem when | ||
the `SpinApp` Pod is started. Instead, you can configure the database after the app has started | ||
using the SQLite explorer component. Be sure to uncomment the SQLite explorer component from the | ||
[`spin.toml`](./spin.toml) and push your app to a registry first. | ||
|
||
```sh | ||
export REGISTRY=ghcr.io/username/mqtt-booth-volume | ||
export TAG=v0.1.0 | ||
spin registry push $REGISTRY:$TAG | ||
make emqx-apply | ||
SPIN_VARIABLE_MQTT_BROKER_URI="mqtt://emqx.default.svc.cluster.local:1883" make app-apply | ||
# port-forward the app service | ||
kubectl port-forward svc/mqtt-booth-volume 3000:80 | ||
``` | ||
|
||
> Note: the local SQLite database should only be used for testing purposes and only on `SpinApp` | ||
> deployments with 1 replica. For more complex scenarios, see the [next | ||
> section](#persisting-to-a-turso-database-with-runtime-config) on how to use runtime configuration | ||
> with an external database. | ||
Navigate to localhost:3000/internal/sqlite. Log in with credentials "admin" / "password" and perform | ||
the initial database migration by entering the contents of | ||
[`migration.up.sql`](./mqtt-message-persister/migration.up.sql). Now that the database is | ||
initialized, you can deploy the fake device: | ||
|
||
```sh | ||
make mock-device-apply | ||
``` | ||
|
||
## Persisting to a Turso Database with Runtime Config | ||
|
||
[Download the Turso CLI](https://github.com/tursodatabase/turso-cli). Then, login to your account, create a database, and generate a token for it. | ||
|
||
```sh | ||
turso auth login | ||
turso db create mqtt-booth-volume | ||
turso db shell mqtt-booth-volume < mqtt-message-persister/migration.up.sql | ||
turso db tokens create mqtt-booth-volume | ||
``` | ||
|
||
Update the [runtime-config.toml](./spinkube/runtime-config.toml) file to contain your database URL and token. Now, you can instruct the `spin` and `spin kube` CLI's to use the runtime configuration using the `--runtime-config-file` flag. | ||
|
||
## Running EMQX Inside Your Cluster | ||
|
||
Use the [`install-broker.sh`](./spinkube/broker-configuration/install-broker.sh) script to install an EMQX broker in your cluster. Provide the `-external` flag to deploy [EMQX cluster](https://docs.emqx.com/en/emqx-operator/latest/deployment/on-azure-aks.html#apps.emqx.io/v2beta1) and configure it to be available outside of the cluster. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
node_modules | ||
dist | ||
target | ||
.spin/ | ||
dist.js |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
package local:combined-wit; | ||
|
||
world combined { | ||
import wasi:io/poll@0.2.0; | ||
import wasi:clocks/monotonic-clock@0.2.0; | ||
import wasi:io/error@0.2.0; | ||
import wasi:io/streams@0.2.0; | ||
import wasi:http/types@0.2.0; | ||
import wasi:http/outgoing-handler@0.2.0; | ||
import fermyon:spin/llm@2.0.0; | ||
import fermyon:spin/redis@2.0.0; | ||
import fermyon:spin/rdbms-types@2.0.0; | ||
import fermyon:spin/postgres@2.0.0; | ||
import fermyon:spin/mysql@2.0.0; | ||
import fermyon:spin/mqtt@2.0.0; | ||
import fermyon:spin/sqlite@2.0.0; | ||
import fermyon:spin/key-value@2.0.0; | ||
import fermyon:spin/variables@2.0.0; | ||
|
||
export wasi:http/incoming-handler@0.2.0; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
package wasi:cli@0.2.0; | ||
|
||
interface environment { | ||
get-environment: func() -> list<tuple<string, string>>; | ||
|
||
get-arguments: func() -> list<string>; | ||
|
||
initial-cwd: func() -> option<string>; | ||
} | ||
|
||
interface exit { | ||
exit: func(status: result); | ||
} | ||
|
||
interface run { | ||
run: func() -> result; | ||
} | ||
|
||
interface stdin { | ||
use wasi:io/streams@0.2.0.{input-stream}; | ||
|
||
get-stdin: func() -> input-stream; | ||
} | ||
|
||
interface stdout { | ||
use wasi:io/streams@0.2.0.{output-stream}; | ||
|
||
get-stdout: func() -> output-stream; | ||
} | ||
|
||
interface stderr { | ||
use wasi:io/streams@0.2.0.{output-stream}; | ||
|
||
get-stderr: func() -> output-stream; | ||
} | ||
|
||
interface terminal-input { | ||
resource terminal-input; | ||
} | ||
|
||
interface terminal-output { | ||
resource terminal-output; | ||
} | ||
|
||
interface terminal-stdin { | ||
use terminal-input.{terminal-input}; | ||
|
||
get-terminal-stdin: func() -> option<terminal-input>; | ||
} | ||
|
||
interface terminal-stdout { | ||
use terminal-output.{terminal-output}; | ||
|
||
get-terminal-stdout: func() -> option<terminal-output>; | ||
} | ||
|
||
interface terminal-stderr { | ||
use terminal-output.{terminal-output}; | ||
|
||
get-terminal-stderr: func() -> option<terminal-output>; | ||
} | ||
|
||
world imports { | ||
import environment; | ||
import exit; | ||
import wasi:io/error@0.2.0; | ||
import wasi:io/poll@0.2.0; | ||
import wasi:io/streams@0.2.0; | ||
import stdin; | ||
import stdout; | ||
import stderr; | ||
import terminal-input; | ||
import terminal-output; | ||
import terminal-stdin; | ||
import terminal-stdout; | ||
import terminal-stderr; | ||
import wasi:clocks/monotonic-clock@0.2.0; | ||
import wasi:clocks/wall-clock@0.2.0; | ||
import wasi:filesystem/types@0.2.0; | ||
import wasi:filesystem/preopens@0.2.0; | ||
import wasi:sockets/network@0.2.0; | ||
import wasi:sockets/instance-network@0.2.0; | ||
import wasi:sockets/udp@0.2.0; | ||
import wasi:sockets/udp-create-socket@0.2.0; | ||
import wasi:sockets/tcp@0.2.0; | ||
import wasi:sockets/tcp-create-socket@0.2.0; | ||
import wasi:sockets/ip-name-lookup@0.2.0; | ||
import wasi:random/random@0.2.0; | ||
import wasi:random/insecure@0.2.0; | ||
import wasi:random/insecure-seed@0.2.0; | ||
} | ||
world command { | ||
import environment; | ||
import exit; | ||
import wasi:io/error@0.2.0; | ||
import wasi:io/poll@0.2.0; | ||
import wasi:io/streams@0.2.0; | ||
import stdin; | ||
import stdout; | ||
import stderr; | ||
import terminal-input; | ||
import terminal-output; | ||
import terminal-stdin; | ||
import terminal-stdout; | ||
import terminal-stderr; | ||
import wasi:clocks/monotonic-clock@0.2.0; | ||
import wasi:clocks/wall-clock@0.2.0; | ||
import wasi:filesystem/types@0.2.0; | ||
import wasi:filesystem/preopens@0.2.0; | ||
import wasi:sockets/network@0.2.0; | ||
import wasi:sockets/instance-network@0.2.0; | ||
import wasi:sockets/udp@0.2.0; | ||
import wasi:sockets/udp-create-socket@0.2.0; | ||
import wasi:sockets/tcp@0.2.0; | ||
import wasi:sockets/tcp-create-socket@0.2.0; | ||
import wasi:sockets/ip-name-lookup@0.2.0; | ||
import wasi:random/random@0.2.0; | ||
import wasi:random/insecure@0.2.0; | ||
import wasi:random/insecure-seed@0.2.0; | ||
|
||
export run; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
package wasi:clocks@0.2.0; | ||
|
||
interface monotonic-clock { | ||
use wasi:io/poll@0.2.0.{pollable}; | ||
|
||
type instant = u64; | ||
|
||
type duration = u64; | ||
|
||
now: func() -> instant; | ||
|
||
resolution: func() -> duration; | ||
|
||
subscribe-instant: func(when: instant) -> pollable; | ||
|
||
subscribe-duration: func(when: duration) -> pollable; | ||
} | ||
|
||
interface wall-clock { | ||
record datetime { | ||
seconds: u64, | ||
nanoseconds: u32, | ||
} | ||
|
||
now: func() -> datetime; | ||
|
||
resolution: func() -> datetime; | ||
} | ||
|
||
world imports { | ||
import wasi:io/poll@0.2.0; | ||
import monotonic-clock; | ||
import wall-clock; | ||
} |
Oops, something went wrong.