Hackster will be offline on Monday, June 15 from 5pm to 7pm PDT to perform some scheduled maintenance.
Chris Buttery
Published

Broken CircleCI Builds and Particle Mesh

Display broken CircleCI builds with a Particle Mesh IoT network.

IntermediateFull instructions provided1 hour736
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
2 projects • 1 follower
Full Stack Software Developer. JavaScript, nodeJS, elm, CSS and IoT.

Comments