IoT for Beginners

Connecting the virtual world with the physical world

Show & Tell

https://github.com/Zefiro

Think about the possibilities

Phyiscal

LED Strip

Virtual

Bit Pulse Signal

Raspberry PI

Node JS Server

Device

Front-end

Physical Side

What do we need?

ws2811/ws2812

Title Text

https://sigrok.org/wiki/VKTECH_saleae_clone

Raspberry PI 3
Arduino

Shopping List

5m Ledstrip ~30 €
Logic Analyzer ~10 €
Raspberry Pi ~35 €
Soldering material ~5 €
LED Power supply ~5 €

  85 €

Where to start?

https://webofthings.org

https://element14.com

Data

Virtual Side

Client

Server

LED Strip

HTTP

Websocket

GPIO

The Server

https://github.com/jgarff/rpi_ws281x

function set(pixels = {r: 0, g: 0, b: 0}, rgborder = 'rgb') {
    this.currentPixels = pixels
    const order = rgborder.split('')
    const pixels = pixels
        .map(pixel => util.rgb2Int(pixel[order[0]], pixel[order[1]], pixel[order[2]]))
    const pixelInts = new Uint32Array(pixels)
    ws281x.render(pixelInts)
}
function render(canvas) {
    let deltaT = new Date() - this._startTime
    let slowDeltaT = deltaT / 200
    let transition = slowDeltaT % 100
    let transitionJump = slowDeltaT - transition
    if (transitionJump != self.anim.lastTransitionJump) {
        self.anim.lastTransitionJump = transitionJump
        for (var i = 0; i < this.numLeds; i++) {
            this.pixels[1][i].counter = this.pixels[0][i].counter
        }
        this.pixels[0] = this.pixels[1]
        this.pixels[1] = createAllPixels(self.layout.fxLength)
    }
    for (var i = 0; i < self.layout.fxLength; i++) {
        // TODO this still relies on calling-frequency instead of Date()
        this.pixels[0][i].counter +=
            (Math.random() + 0.5) 
            * util.map(this.pixels[0][i].speed, this.pixels[1][i].speed, transition)
        if (this.pixels[0][i].counter > 200) this.pixels[0][i].counter = 0
        var variants =
            this.pixels[0][i].counter > 100 
                ? 200 - this.pixels[0][i].counter 
                : this.pixels[0][i].counter
        var maxTemp = util.map(this.pixels[0][i].maxTemp, this.pixels[1][i].maxTemp, transition)
        var minTemp = util.map(this.pixels[0][i].minTemp, this.pixels[1][i].minTemp, transition)
        var temp = util.map(maxTemp, minTemp, variants)
        if (this.type == 'linear') {
            temp = (255 * (i + self.layout.fxStart)) / (self.layout.fxSize - 1)
        }
        temp = Math.max(0, Math.min(Math.floor(temp), 255))
        let targetIdx = this.layout.canvasStart + (this.layout.reverse 
            ? self.layout.fxLength - i - 1 
            : i)
        canvas[targetIdx] = temperatureColorMap[temp]
    }
    return canvas
}

We used to hardcode effects

We can level up

Frame

Step

Scene

How would the data look like?

[
    {
        duration: 1,
        pixels: [
            {r: 0, g: 255, b: 0},
            {r: 0, g: 255, b: 0},
            {r: 0, g: 255, b: 0},
            {r: 0, g: 255, b: 0},
            {r: 0, g: 255, b: 0},
        ]
    },
    {
        duration: 1,
        pixels: [
            {r: 255, g: 255, b: 0},
            {r: 255, g: 255, b: 0},
            {r: 255, g: 255, b: 0},
            {r: 255, g: 255, b: 0},
            {r: 255, g: 255, b: 0},
        ]
    },
]
import { ledstrip } from '../index'
import Pixel from './pixel'

export default class Step {
    constructor(pixels = [], options = {}) {
        this.pixels = pixels
        this.duration = parseFloat(options.duration) || 1
        this.fps = 50
        this.frame = 0
    }

    async tween(nextStep) {
        const frames = this.getFrames()
        for (let frame of frames) {
            // for 1 second - this is 1/60 of a second
            await new Promise(resolve => setTimeout(resolve, 1000 / this.fps))
            this.frame = frame.index
            let pixels = this.pixels.map((pixel, index) => {
                let next = nextStep.pixels[index]
                if (!next || !pixel) {
                    return new Pixel()
                }
                // mapColor fades between two colors based on percentage
                return pixel.mapColor(next, frame.percent)
            })
            ledstrip.set(pixels)
        }
    }

    * getFrames() {
        const frameLength = this.fps * this.duration
        yield* Array.from(new Array(frameLength)).map((v, index) => {
            index += 1
            return {
                index,
                percent: 100 / frameLength * index,
            }
        })
    }
}
import http from 'http'
import Pixels from './classes/pixels'
import io from 'socket.io'

export const ledstrip = new Pixels({
    size: 50,
    rgborder: 'gbr',
    ledstrip: {
        invert: 0,
        frequency: 400000,
    }
})

process.on('SIGINT', () => {
    ledstrip.reset()
    process.nextTick(() => { process.exit(0) })
})

const socketServer = http.createServer()
const socketIo = io(socketServer)

socketIo.on('connection', function(socket) {
    socket.emit('current', JSON.stringify(ledstrip.get()))
    socket.on('setPixels', data => {
        ledstrip.set(JSON.parse(data))
    })
})

socketServer.listen(3000)

The Client

Ingredients

  • Color Picker
  • Effect buttons
  • Add/Remove/Copy buttons
  • Pixels
  • Some rendering mechanism (I use Vue)
  • SocketIO Client
methods: {
    isPixelActiveClass(pixel, index) {
        return {
            'pixel--selected': this.selectedPixelIndexes.includes(index)
        }
    },
    getPixelColor(pixel) {
        return { backgroundColor: `rgb(${pixel.r}, ${pixel.g}, ${pixel.b})` }
    },
    setActivePixel(event, index) {
        const pixel = this.pixels.get(index)
        this.selectedPixel = pixel
        if (!event.shiftKey) {
            this.selectedPixelIndexes = [index]
        } else {
            const startIndex = this.selectedPixelIndexes[0]
            const selectionLength = (startIndex > index ? startIndex - index : index - startIndex) + 1
            const directionUp = startIndex > index
            this.selectedPixelIndexes = [
                ...this.selectedPixelIndexes[0],
                ...Array.from(Array(selectionLength)).map((element, selectIndex) => index + (directionUp ? selectIndex : -selectIndex))
            ]
        }
        this.color = {
            rgba: {
                r: pixel.r,
                g: pixel.g,
                b: pixel.b,
                a: 1
            }
        }
    },
}

Client

Server

LED Strip

HTTP

Websocket

GPIO

Thank you!

https://wireupyourfrontend.com

https://gps-stuttgart.de/

https://github.com/MartinMuzatko/ledstrip-animator/

Code

Questions?

IoT for Beginners

By Martin Muzatko

IoT for Beginners

  • 343