Chris Buttery
Published © MIT

Slack IoT Marquee

Use Slack to fire off custom messages to a Photon scrolling marquee.

IntermediateFull instructions provided1 hour1,265
Slack IoT Marquee

Things used in this project

Hardware components

Photon
Particle Photon
×1
Breadboard (generic)
Breadboard (generic)
×1
Adafruit MAX7219 Dot Matrix Module
×1
Buzzer, Piezo
Buzzer, Piezo
×1

Software apps and online services

Slack
Slack
Now — Global Serverless Deployments

Story

Read more

Schematics

Particle Photon

Code

slack-iot-marquee

C/C++
Firmware to be flashed to Particle.io device.
// This #include statement was automatically added by the Particle IDE.
#include <ledmatrix-max7219-max7221.h>

LEDMatrix *led;

String message = "";
int speakerPin = D3;
int bitmapWidth = 32;
int webcount = 60001;
int iterationCount = 2;
int textX = bitmapWidth;
int fontWidth = 5, space = 1;

/*
 * drawText
 * Draw each char to LED
 */
void drawText(String s, int x)
{
    int y = 0;
    for(int i = 0; i < s.length(); i++) {
        Serial.println(s[i]);  
        led->drawChar(x + i*(fontWidth+space), y, s[i], true, false, 1);
    }
}

/*
 * createDisplay
 * Create the LED
 */
void createDisplay() {
    led = new LEDMatrix(4, 1, D0, D1, D2);
    led->addMatrix(3, 0, 0, false, false);
    led->addMatrix(2, 0, 0, false, false);
    led->addMatrix(1, 0, 0, false, false);
    led->addMatrix(0, 0, 0, false, false);
    textX = bitmapWidth;
}

/*
 * killDisplay
 * Tear down the LED
 */
void killDisplay() {
    led->shutdown(true);
    delete led;
    led = NULL;
    iterationCount = 2;
}

/*
 * displayMessage
 * take a String param, kill any existing display, sound peizo and reset iterationCount
 */
int displayMessage(String command) {
    killDisplay();
    createDisplay();
    tone(speakerPin, 301, 1000);
    message = command;
    iterationCount = 0;
}

/*
 * setup
 * enable serial logging + create LED
 * Listen for 'matrix_display_message' and fire off to displayMessage
 */
void setup() {
    Serial.begin(9600);
    createDisplay();
    // on 'matrix_display_message' call display_message
    Particle.function("matrix_display_message", displayMessage);
}

void loop() {
    if(iterationCount < 2 && message != "") {
        drawText(message, textX--);
        
        int length = message.length();
        if(textX < length*(fontWidth+space)*(-1) ) {
            textX = bitmapWidth;
            led->flush();
            delay(333);
            led->fillScreen(false);
            iterationCount++;
        }
    
        led->flush();
        delay(50);
    } else {
        iterationCount = 2;
        message = "";
    }
}

.env

snippets
Optional environment variables for local dev
PDEVICE=particle_device
PUSER=particle_user
PPASS=particle_password
HRKEY=the_id_for_the_hr_slash_command
DISPLAYKEY=the_id_for_the_display_slash_command

index.js

JavaScript
nodeJS api to be hosted wherever you like.
I chose to use now.sh for this example.
require("dotenv").config()

const express = require('express')
const bodyParser = require('body-parser')
const cors = require('cors')
const path = require('path')
const app = express()
const particle = require('particle-api-js')

const Particle = new particle()
const portNumber = 8000
let auth = null

// credentials
const deviceId = process.env.PDEVICE
const username = process.env.PUSER
const password = process.env.PPASS
const hrKey = process.env.HRKEY
const displayKey = process.env.DISPLAYKEY

// middleware
app.use(cors())
app.use(bodyParser.json())
app.use(bodyParser.urlencoded({ extended: true }))

// routes
app.post('/api/display', async (req, res, next) => {
  const { token, text } = req.body  
  
  if (!auth) {
    await Particle.login({username, password})
      .then(data => {
        auth = data.body.access_token
      })
      .catch(({ status, message }) => 
        res.status(status).send({ error: message })
      )
  }

  if (!token || token !== displayKey) {
    return res.status(401).send({ error: '401 Unauthorized' })
  }

  await Particle.callFunction({
    deviceId,
    name: 'matrix_display_message',
    argument: text,
    auth
  })
  .then((data) => res.send(`Now displaying: ${data.body}`))
  .catch((err) => console.error(err))

  next()
})

app.post('/api/hr', async (req, res, next) => {
  const { token, text } = req.body  
  if (!auth) {
    await Particle.login({username, password})
      .then(data => {
        auth = data.body.access_token
      })
      .catch(({ status, message }) => 
        res.status(status).send({ error: message })
      )
  }

  if (!token || token !== hrKey) {
    return res.status(401).send({ error: '401 Unauthorized' })
  }

  await Particle.callFunction({
    deviceId,
    name: 'matrix_display_message',
    argument: `Go to HR ${text}`,
    auth
  })
  .then((data) => res.send(`${text} is required by HR.`))
  .catch((err) => console.error(err))

  next()
})

// error handling
app.use((err, req, res, next) => 
  res.status(422).send({ error: err.message })
)
// Go!
app.listen(portNumber, () => console.log(`Listening on port ${portNumber}`))

now.json

JSON
The now.json manifest that exposes routes.
Also references 'secret' variables.
You dont have to use this if you do not wish to host the api with now.sh
{
  "version": 2,
  "name": "slack-iot-marquee",
  "builds": [{
    "src": "./index.js",
    "use": "@now/node-server"
  }],
  "routes": [
    {
      "src": "/api/display",
      "methods": ["POST"],
      "dest": "index.js"
    },
    {
      "src": "/api/hr",
      "methods": ["POST"],
      "dest": "index.js"
    }
  ],
  "env": {
    "PDEVICE": "@slack-display-device",
    "PUSER": "@slack-display-user",
    "PPASS": "@slack-display-pass",
    "HRKEY": "@slack-display-hrkey",
    "DISPLAYKEY": "@slack-display-displaykey"
  }
}

package.json

JSON
nodeJS api manifest
{
  "name": "slack-iot-marquee",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "scripts": {
    "start": "node index.js"
  },
  "dependencies": {
    "body-parser": "^1.19.0",
    "cors": "^2.8.5",
    "dotenv": "^8.0.0",
    "express": "^4.17.0",
    "particle-api-js": "^7.4.1"
  }
}

Credits

Chris Buttery

Chris Buttery

2 projects • 1 follower
Full Stack Software Developer. JavaScript, nodeJS, elm, CSS and IoT.

Comments