Chris Buttery
Published

Broken CircleCI Builds and Particle Mesh

Display broken CircleCI builds with a Particle Mesh IoT network.

IntermediateFull instructions provided1 hour242
Broken CircleCI Builds and Particle Mesh

Things used in this project

Hardware components

Argon
Particle Argon
×1
Xenon
Particle Xenon
×2
adafruit 1.2" 7-segment LCD HT16K33 Backpack
×1
Adafruit 4-in-1 Display Dot Matrix Module (16×8) – MAX7219 Microcontroller
×1

Software apps and online services

Now — Global Serverless Deployments
CircleCI

Story

Read more

Schematics

Argon (Gateway)

Xenon (Marquee)

Xenon (Counter)

Code

package.json

JSON
The api NPM dependencies
{
  "name": "particle-mesh-circleci",
  "private": true,
  "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"
  },
  "engines": {
    "node": ">= 10.9"
  }
}

argon-publish.ino

C/C++
Argon Gateway
int displayMessage(String command) {
    Mesh.publish("matrix_display_message", command);
    digitalWrite(D7, HIGH);
    delay(1000);
    digitalWrite(D7, LOW);
}

void setup() {
    pinMode(D7, OUTPUT);
    Particle.function("display_broken_build", displayMessage);
}

void loop() {}

xenon-subscribe-scrolling-marquee.ino

C/C++
Xeno scrolling marquee
/// 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
 */
void displayMessage(const char *event, const char *data) {
    killDisplay();
    createDisplay();
    tone(speakerPin, 301, 1000);
    message = data;
    Serial.println(message);
    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
    Mesh.subscribe("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 = "";
    }
}

xenon-subscribe-counter.ino

C/C++
Xenon counter
/// This #include statement was automatically added by the Particle IDE.
#include "adafruit-led-backpack.h"

Adafruit_7segment matrix = Adafruit_7segment();
int count = 0;

void logToDisplay(const char *event, const char *data) {
  digitalWrite(D7, HIGH);
  delay(1000);
  digitalWrite(D7, LOW);
  count++;
  matrix.print(count);
  matrix.writeDisplay();
}

void setup() {
  Mesh.subscribe("matrix_display_message", logToDisplay);
  matrix.begin(0x70);
  matrix.print(count);
  matrix.writeDisplay();
}

void loop() {}

config.yml

YAML
A failing circleci config
version: 2
jobs:
  build:
    working_directory: ~/repo
    docker:
      - image: circleci/node:8
    steps:
      - checkout
      - run: npm install
      # npm scrip 'foo' does not exist, so should fail
      - run: npm foo
      - run:
          name: Build Success
          when: on_success
          command: |
            if [ "$CIRCLE_NODE_INDEX" == 0 ]; then
              echo "Success"
            fi
      - run:
          name: Build Failed
          when: on_fail
          command: |
            if [ "$CIRCLE_NODE_INDEX" == 0 ]; then
              curl -d '{ "branch": "'"$CIRCLE_BRANCH"'", "user": "'"$CIRCLE_USERNAME"'"}' -H "Content-Type: application/json" -X POST https://mesh-circle-ci.chrisbuttery.now.sh/api/display
            fi

package.json

JSON
The basic package.json file to add to your failing circleci repo
{
  "name": "my-failing-circleci-app",
  "private": true,
  "version": "1.0.0",
  "description": "a basic manifest",
  "main": "index.js",
  "scripts": {},
  "devDependencies": {}
}

.env

C/C++
API environment variables
PDEVICE=particle_device_id
PUSER=particle_user
PPASS=particle_password

index.js

JavaScript
API server
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

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

// routes
app.post('/api/display', async (req, res, next) => {
  const { branch, user } = 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 })
      )
  }

  const message = `Broken Build! ${branch} | ${user}`
  await Particle.callFunction({
    deviceId,
    name: 'display_broken_build',
    argument: message,
    auth
  })
  .then((data) => res.send(message))
  .catch((err) => {
    console.error(err)
    res.send(`callFunction error: ${JSON.stringify(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
now.sh manifest
{
  "version": 2,
  "name": "particle-mesh-circleci",
  "builds": [{
    "src": "./index.js",
    "use": "@now/node-server"
  }],
  "routes": [
    {
      "src": "/api/display",
      "methods": ["POST"],
      "dest": "index.js"
    }
  ],
  "env": {
    "PDEVICE": "@mesh-pub-device",
    "PUSER": "@mesh-pub-user",
    "PPASS": "@mesh-pub-pass"
  }
}

Credits

Chris Buttery

Chris Buttery

2 projects • 0 followers
Full Stack Software Developer. JavaScript, nodeJS, elm, CSS and IoT.

Comments