edge lab LIVE! starts Tuesday in Munich. Grab a workshop seat and take home the board you build on. Code FRIEND-HACKSTER saves 20%. () Learn more!
ayalErandi Itzai Estrada Martínez
Created March 3, 2026

Stocksense

A speculative interface enabling somatic dialogue between human and stock market intelligence.

16
Stocksense

Things used in this project

Hardware components

Raspberry Pi Pico W
Raspberry Pi Pico W
×1
General Purpose Transistor NPN
General Purpose Transistor NPN
Is an 2N222
×9
Resistor 220 ohm
Resistor 220 ohm
×9
Solar Cockroach Vibrating Disc Motor
Brown Dog Gadgets Solar Cockroach Vibrating Disc Motor
Haptic motor
×9
Jumper wires (generic)
Jumper wires (generic)
×1

Story

Read more

Schematics

Simple diagram

For this project we decided to make a grid of 3x3, therefore we had to connect 9 haptic motors, but there can be more or less according to the project

Code

Stock Market Haptic Wearable + MQTT

Python
Load this onto the raspberry pi pico 2 to get the information that node-red is sending.
"""
Simple test - All 6 motors buzz together when MQTT message arrives
"""

import network
import time
import json
from machine import Pin, PWM
from umqtt.simple import MQTTClient

# ============ CONFIGURATION ============

WIFI_SSID = "Iaac-Wifi"
WIFI_PASSWORD = "EnterIaac22@" 

MQTT_BROKER = "broker.hivemq.com"
MQTT_PORT = 1883
MQTT_TOPIC = b"multisense/stock/data"
MQTT_CLIENT_ID = b"multisense_pico"

# All 6 motor pins
MOTOR_PINS = [13, 14, 15, 16, 17, 18]

# ============ SETUP ============

print("MULTISENSE - Simple 6-Motor Test")
print("="*40)

# Initialize all motors
print("Initializing motors...")
motors = []
for i, pin in enumerate(MOTOR_PINS):
    motor = PWM(Pin(pin))
    motor.freq(1000)
    motors.append(motor)
    print(f"  Motor {i+1} on GPIO {pin}")

# Connect WiFi
print(f"\nConnecting to WiFi: {WIFI_SSID}...")
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.connect(WIFI_SSID, WIFI_PASSWORD)

timeout = 20
while not wlan.isconnected() and timeout > 0:
    print(".", end="")
    time.sleep(0.5)
    timeout -= 0.5

if not wlan.isconnected():
    print("\n✗ WiFi failed!")
    raise SystemExit

print(f"\n✓ WiFi Connected! {wlan.ifconfig()[0]}")

# ============ MQTT CALLBACK ============

def mqtt_callback(topic, msg):
    """When stock data arrives, buzz all motors"""
    try:
        data = json.loads(msg.decode())
        
        intensity = data.get('intensity', 0)
        direction = data.get('direction', 'neutral')
        price = data.get('price', 0)
        change = data.get('change', 0)
        
        arrow = "📈" if direction == "up" else "📉"
        print(f"\n{arrow} ${price} ({change}%) → {intensity}%")
        
        # Calculate PWM duty
        duty = int((intensity / 100) * 50000)  # Cap at 50000
        
        # Turn ALL motors ON
        print(f"  ALL motors buzzing at {duty}")
        for motor in motors:
            motor.duty_u16(duty)
        
        # Keep buzzing for 3 seconds
        time.sleep(3)
        
        # Turn ALL motors OFF
        print("  Motors off")
        for motor in motors:
            motor.duty_u16(0)
            
    except Exception as e:
        print(f"Error: {e}")

# Connect MQTT
print(f"\nConnecting to MQTT: {MQTT_BROKER}...")
client = MQTTClient(MQTT_CLIENT_ID, MQTT_BROKER, port=MQTT_PORT)
client.set_callback(mqtt_callback)

try:
    client.connect()
    print("✓ MQTT Connected!")
except Exception as e:
    print(f"✗ MQTT failed: {e}")
    raise SystemExit

client.subscribe(MQTT_TOPIC)
print(f"✓ Subscribed to: {MQTT_TOPIC.decode()}")

print("\n" + "="*40)
print("READY - All motors will buzz when")
print("stock data arrives from Node-RED")
print("="*40 + "\n")

# ============ MAIN LOOP ============

try:
    while True:
        client.check_msg()
        time.sleep(0.1)
        
except KeyboardInterrupt:
    print("\n⏹ Stopping...")
    for motor in motors:
        motor.duty_u16(0)
    client.disconnect()
    print("✓ Stopped")

Node-Red Stock (AAPL) Flow

JSON
Import this code into Node-Red to create the node flow:

**In Node-RED:**
- Click **Menu** (☰ top right)
- Click **Import**
- Click **Clipboard**
- Paste the JSON
- Click **Import**

Afterwards...

Add Your Finnhub API Key

1. **Double-click the "Get AAPL Price" node** (the light blue one)

2. In the URL field, you'll see:
```
https://finnhub.io/api/v1/quote?symbol=AAPL&token=YOUR_API_KEY_HERE

Replace YOUR_API_KEY_HERE with your actual Finnhub API key

Click Done
[
    {
        "id": "inject1",
        "type": "inject",
        "z": "flow1",
        "name": "Trigger Stock Fetch",
        "props": [{"p": "payload"}],
        "repeat": "30",
        "crontab": "",
        "once": true,
        "onceDelay": 0.1,
        "topic": "",
        "payload": "",
        "payloadType": "date",
        "x": 150,
        "y": 100,
        "wires": [["http1"]]
    },
    {
        "id": "http1",
        "type": "http request",
        "z": "flow1",
        "name": "Get AAPL Price",
        "method": "GET",
        "ret": "obj",
        "paytoqs": "ignore",
        "url": "https://finnhub.io/api/v1/quote?symbol=AAPL&token=YOUR_API_KEY_HERE",
        "tls": "",
        "persist": false,
        "proxy": "",
        "authType": "",
        "x": 370,
        "y": 100,
        "wires": [["function1"]]
    },
    {
        "id": "function1",
        "type": "function",
        "z": "flow1",
        "name": "Stock to Haptic",
        "func": "// Get stock data from Finnhub\nconst stock = msg.payload;\n\n// Extract values\nconst currentPrice = stock.c;      // current price\nconst change = stock.d;            // dollar change\nconst percentChange = stock.dp;    // percent change\nconst dayHigh = stock.h;\nconst dayLow = stock.l;\n\n// Calculate volatility\nconst dayRange = dayHigh - dayLow;\nconst volatility = (dayRange / currentPrice) * 100;\n\n// Map to haptic parameters\nconst intensity = Math.min(100, Math.max(10, Math.abs(percentChange) * 30));\nconst direction = percentChange >= 0 ? 'up' : 'down';\n\n// Create MQTT payload\nmsg.payload = {\n    intensity: Math.round(intensity),\n    direction: direction,\n    price: currentPrice,\n    change: percentChange,\n    volatility: Math.round(volatility * 10) / 10\n};\n\n// Log for debugging\nnode.warn(`💰 $${currentPrice} | ${percentChange >= 0 ? '📈' : '📉'} ${percentChange}% | ⚡ ${intensity}%`);\n\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 590,
        "y": 100,
        "wires": [["mqtt1", "debug1"]]
    },
    {
        "id": "mqtt1",
        "type": "mqtt out",
        "z": "flow1",
        "name": "Publish to Wearable",
        "topic": "multisense/stock/data",
        "qos": "0",
        "retain": "false",
        "respTopic": "",
        "contentType": "",
        "userProps": "",
        "correl": "",
        "expiry": "",
        "broker": "broker1",
        "x": 830,
        "y": 100,
        "wires": []
    },
    {
        "id": "debug1",
        "type": "debug",
        "z": "flow1",
        "name": "View Haptic Data",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "payload",
        "targetType": "msg",
        "statusVal": "",
        "statusType": "auto",
        "x": 830,
        "y": 160,
        "wires": []
    },
    {
        "id": "broker1",
        "type": "mqtt-broker",
        "name": "HiveMQ Public",
        "broker": "broker.hivemq.com",
        "port": "1883",
        "clientid": "",
        "autoConnect": true,
        "usetls": false,
        "protocolVersion": "4",
        "keepalive": "60",
        "cleansession": true,
        "birthTopic": "",
        "birthQos": "0",
        "birthPayload": "",
        "birthMsg": {},
        "closeTopic": "",
        "closeQos": "0",
        "closePayload": "",
        "closeMsg": {},
        "willTopic": "",
        "willQos": "0",
        "willPayload": "",
        "willMsg": {},
        "userProps": "",
        "sessionExpiry": ""
    },
    {
        "id": "flow1",
        "type": "tab",
        "label": "Multisense Stock Data",
        "disabled": false,
        "info": ""
    }
]

Import MQTT onto Raspberry Pi

Python
In Thonny, open a new file
Type the test code


Potential error: not connected to the internet. To fix this go to code named "connect to wifi"
from umqtt.simple import MQTTClient
print("MQTT library imported successfully!")
```

3. Click **Run** (F5)
4. Should see in Shell (bottom panel):
```
   MQTT library imported successfully!

Connect to Wifi

Python
use this to connect to wifi and get your ip address
import network
import time

# Replace with YOUR WiFi credentials
SSID = "YOUR_WIFI_NAME"
PASSWORD = "YOUR_WIFI_PASSWORD"

wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.connect(SSID, PASSWORD)

print("Connecting to WiFi...")
while not wlan.isconnected():
    print(".", end="")
    time.sleep(0.5)

print("\n✓ WiFi Connected!")
print("IP Address:", wlan.ifconfig()[0])
```

2. **Replace `YOUR_WIFI_NAME` and `YOUR_WIFI_PASSWORD`** with your actual WiFi credentials

3. Click **Run** (F5)

4. Should see:
```
Connecting to WiFi...
.....
✓ WiFi Connected!
IP Address: 192.168.1.XXX

Github Repo

This is the github repository for the website. In the references folder you can find the Node-RED and Haptic scripts

Credits

ayal
3 projects • 1 follower
hi :)
Erandi Itzai Estrada Martínez
3 projects • 1 follower
Thanks to Julia.

Comments