Raspberry
Pi Powered
CF Server
Status
Monitor
Scott Steinbeck
- Software Developer
- 12+ Year of experience
- Father
- Hardware Tinkerer
Hobbies
- Coding on my free time
- Overcommitting myself
- Automating everything
- IOT Development
- Teaching Coding/Electronics
- Contributing to open source
Remote control
lawn mower
Dev
Pi Hole
Pi Dev
Parts
Wiring
Pi Config
Linux Setup
Commandbox install
Jar Loading
Boot Service
Make it work
Make it Pretty
What to expect
Parts
- Raspberry Pi 3+
- SD Card
- 2 Jumper wires
- Micro Usb Cord
- WS2811 5V DC
Addressable Round
LED Pixels - 5V - 10 Amp
DC Power Supply
with Female 12V
DC Power Jack
Computer vs microprocessor
Power
Speed
Efficiency
Protocols
Sleep Current
Storage
Complexity
Language
Plug & Play
Wifi/Bluetooth
LTE
Wiring
5V
Power
Pin 18
(PWM)
GND
PI: Pin 18 -> LEDs: Data/Signal
PI: Ground -> LED:Ground
5V DC: Ground -> LED: Ground
5V DC: 5V+ -> LED: 5V+
PWM
Pulse width modulation
GPIO - General Purpose I/O
Pi Communication Protocols
Configure SD Card
Install Latest Raspbian on SD Card
create empty SSH.TXT
edit config.txt
#comment out dtparam
#Enable audio (loads snd_bcm2835)
#dtparam=audio=on
After you save those changes
eject the SD Card
Insert in the Pi and plug it in
create wpa_supplicant.conf
ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
update_config=1
country=US
network={
ssid="<Name of your wireless LAN>"
psk="<Password for your wireless LAN>"
}
SSH into pi
ssh pi@raspberrypi.local
pi@raspberrypi.local's password: (default is raspberry)
//install wiringpi
sudo apt-get update && sudo apt-get install wiringpi
//disable audio (conflicts with PWM)
nano /etc/modprobe.d/alsa-blacklist.conf
//add line and save
blacklist snd_bcm2835
Install CommanDbox
curl -fsSl https://downloads.ortussolutions.com/debs/gpg | sudo apt-key add -
echo "deb https://downloads.ortussolutions.com/debs/noarch /" | sudo tee -a /etc/apt/sources.list.d/commandbox.list
sudo apt-get update && sudo apt-get install apt-transport-https commandbox
Start writing code
> nano index.cfm
NANO..... REALLY??
WE CAN DO BETTER
VS Code
Remote
Development
Talking to LEDs
<CFLED>
But, when all else fails
look for a JAVA library
Java -> C++
C++ -> Native GPIo
Create Project
> cd ~/Desktop/
> mkdir statusLights
> cd statusLights
> box
Find the Library jar
Welcome to CommandBox!
CommandBox> install "jar:https://repo1.maven.org/maven2/com/diozero/diozero-ws281x-java/0.11/diozero-ws281x-java-0.11.jar"
Add Jar Dependency
Done.... right?
But how do we use it?
java docs
Quick Start
Task runners
component {
function run(){
classLoad( '/home/pi/Desktop/lightStatus/lib/diozero-ws281x-java-0.11/diozero-ws281x-java-0.11.jar' );
var freq = 800000; //Default
var DMA = 5; //Default
var pin = 18; // GPIO Pin
var numOfLights = 50;
var brightness = 200; //0-255
// RGB, GBR, BGR
var stripType = createObject("java","com.diozero.ws281xj.StripType");
var ledController = createObject("java","com.diozero.ws281xj.rpiws281x.WS281x");
ledController.init(freq,DMA,pin,brightness,numofLights,stripType.WS2811_RGB);
for(var ledID=0; ledID < 50; ledID++ ){
//( ledID * 5) just uses the (ledID x 5) as the color to make a gradient
ledController.setPixelColourHSL(ledID,(ledID * 5),.5,.5);
ledController.render(); //have to render after set
}
}
lightTest.cfc
> Welcome to CommandBox!
> CommandBox> task run lightTest
Run Task
Profit
Quick Start 2
standard index.cfm
<cfscript>
for(var ledID=0; ledID < 50; ledID++ ){
//( ledID * 5) just uses the (ledID x 5) as the color to make a gradient
ledController.setPixelColourHSL(ledID,(ledID * 5),.5,.5);
ledController.render(); //have to render after set
}
</cfscript>
index.cfm
component{
this.name = "statusLights";
function onApplicationStart(){
var freq = 800000; //Default
var DMA = 5; //Default
var pin = 18; // GPIO Pin
var numOfLights = 50;
var brightness = 200; //0-255
// RGB, GBR, BGR
var stripType = createObject("java","com.diozero.ws281xj.StripType");
application.ledController = createObject("java","com.diozero.ws281xj.rpiws281x.WS281x");
application.ledController.init(freq,DMA,pin,brightness,numofLights,stripType.WS2811_RGB);
}
}
Application.cfc
Config settings
{
"dependencies":{
"diozero-ws281x-java-0.11":"jar:https://repo1.maven.org/maven2/com/diozero/diozero-ws281x-java/0.11/diozero-ws281x-java-0.11.jar"
},
"installPaths":{
"diozero-ws281x-java-0.11":"lib/diozero-ws281x-java-0.11/"
}
}
box.json
{
"name": "statuslights",
"jvm": {
"heapSize": 1024,
"minHeapSize": 256
},
"app": {
"libDirs": "lib"
},
"web": {
"host": "raspberrypi.local",
"http": {
"port": "80"
},
"webroot": "/"
},
"force": true
}
Server.json
Animations
<cfscript>
function blink(pixel,hueColor = 0,numberOfBlinks = 5){
for(var i=1; i<numberOfBlinks; i++){
ledController.setPixelColourHSL(pixel,hueColor,.5,.5);
ledController.render(); //have to render after set
sleep(100);
ledController.setPixelColourHSL(0,0,0); //OFF
ledController.render(); //have to render after set
sleep(100);
}
}
function rainbow(pixel){
for(var i=1; i<360; i++){
ledController.setPixelColourHSL(pixel,i,.5,.5);
ledController.render(); //have to render after set
sleep(60); //Slow it down so you can see it
}
}
</cfscript>
Running Mulitple
Animations
component {
function run(){
//blink(pixel,hueColor = 0,numberOfBlinks = 5)
blink(1,0,5);
blink(2,0,5);
blink(3,0,5);
blink(4,0,5);
blink(5,0,5);
// rainbow(pixel)
rainbow(6);
rainbow(7);
rainbow(8);
rainbow(9);
rainbow(10);
}
}
Do we have a problem?
Too
Much
Sleep
Async to the Rescue
Parallel Processing
<cfscript>
function rainbow(pixel){
for(var i=1; i<360; i++){
ledController.setPixelColourHSL(pixel,i,.5,.5);
ledController.render(); //have to render after set
sleep(60); //Slow it down so you can see it
}
}
//Create a thread
thread {
rainbow(1);
}
//Use run async
runAsync(function(){
return rainbow(1);
});
//Install Coldbox 6 BE
//use cbFutures
async = new coldbox.system.async.AsyncManager();
async.newFuture( () => rainbow(1) )
</cfscript>
Async
Uptime
component{
function run(){
fileSystemUtil.createMapping( "/coldbox", getCwd() & "coldbox" );
//load in diozero java lib
siteMonitor()
}
//Async health check
function pingServer(id,siteURL){
cfhttp(method="GET", charset="utf-8", url=siteURL, result="local.result", timeout=10);
return {
'id' : id,
'healthy': structKeyExists(local.result,'status_code') && local.result.status_code == 200
};
}
//Site Monitor (health check)
function siteMonitor(){
async = new coldbox.system.async.AsyncManager();
var sites = [
() => pingServer(4,"https://www.ortussolutions.com"),
() => pingServer(1,"http://my.agritrackingsystems.com"),
() => pingServer(2,"http://my.agrimapping.com"),
() => pingServer(3,"http://app.cropcast.com")
]
async
.all( sites )
.get()
.each( ( healthCheck ) => {
var ledID = variables.ledIndex['siteMonitor'][healthCheck.id];
setLEDHue(ledID,60,true);
setLEDHue(ledID,colorScale((healthCheck.healthy ? 1 : 0 ),'red','blue'),true);
})
}
}
Add 'coldbox@be' as a dependency in box.json
Scheduled Tasks
component{
async = new coldbox.system.async.AsyncManager();
taskManager = async.newScheduledExecutor( name: "myTasks", threads: 20 );
function run(){
var future = taskManager.newSchedule(updateClock)
.every( 60000 ) // Run every minute
.start();
}
function updateClock(){
var dt = now();
//update day of the week (1-7)
var curr_weekday = datePart('w',dt);
...
//update clock hours (1-24), cut in half for 12hr clock
var curr_hr = datePart('h',dt);
...
//update clock minutes (0-60) increments of 5
var curr_min = datePart('n',dt);
...
}
}
Map the LEDs
Make a design
1
2
3
4
//LED Lights ID Mapping
variables.ledIndex = {
"weekdays": [31, 30, 29, 28, 27, 26, 25],
"hours": [47, 48, 16, 19, 20, 23, 34, 33, 38, 39, 42, 44],
"minutes": [46, 49, 17, 18, 21, 22, 35, 36, 37, 40, 41, 45],
"temp": [15, 14, 13],
"piStatus": [11, 10, 9, 8],
"siteMonitor": [7, 6, 5, 4],
"workMonitor": [3, 2, 1, 0],
"pomodoro": [24],
"calStat": [32],
"genblink": [12],
"wheatley": [43]
};
Build Utility Methods
// Turn off LED by ID
function turnOffLED(id,forceUpdate = false){
variables.ledController.setPixelColourHSL(id,0,0,0);
if(forceUpdate) updateLights();
}
// Set LED Hue by ID
function setLEDHue(id,hue,forceUpdate = false){
variables.ledController.setPixelColourHSL(id,hue,1,.5);
if(forceUpdate) updateLights();
}
// Push light settings to the LEDs
function updateLights(){
variables.ledController.render();
}
// Blink light
function blink(ledID,hue,delay,blinkNum){
for(var i=1; i <=blinkNum; i++){
setLEDHue(ledID,i,true);
sleep(delay);
turnOffLED(ledID,true);
sleep(delay);
}
}
Build Weekdays & Clock
// Get the corresponding color from a color range based on a percentage
function colorScale(percent, lowColor, highColor){
if(percent > 1) percent = 1;
if(!isNumeric(lowColor)) lowColor = variables.colorIndex[lowColor];
if(!isNumeric(highColor)) highColor = variables.colorIndex[highColor];
var val = abs(lowColor - highColor) * percent;
return (highColor < lowColor) ? lowColor - val : lowColor + val;
}
// update clock (Day of week, hours, minutes, pomodoro timer)
function updateClock(){
//Needs to be set here, or in the admin to ensure the correct time zone
SetTimeZone(timezone='America/Los_Angeles');
var currTime = now();
var dow = datepart('w',currTime); //Day of the week 1-7
var curr_hour = datepart('h',currTime); //Hour 24hr clock
var curr_min = datepart('n',currTime); //Minutes
var hourLEDIdx = variables.ledIndex['hours']; //led Index for hours
var hourColor = (curr_hour/24*60)+200; //Hour gradient starting at 200 (lightblue)
var minuteLEDIdx = variables.ledIndex['minutes']; //led Index for minutes
var minuteColor = (curr_min/60*60)+30; //Minute gradient starting at 30 (purple)
// update pomodoro clock (0-25 min.) (25-30 min. Red, Get up and move)
var halftime = curr_min;
var pomoLED = variables.ledIndex['pomodoro'][1];
if(halftime > 30) halftime = halftime - 30;
if(halftime <= 25){
setLEDHue(pomoLED,colorScale(halftime/25,'green','purple'));
} else {
blink(pomoLED,0,100,6);
}
// Set day of the week ...
// Set hour of the day ...
// Set minute (increments of 5) ...
updateLights(); //update lights after everything is set
}
Build Status Functions
//System Stats
function SystemStats(){
var systemInfo=GetSystemMetrics(); //Get lucee System Metrics
var heap = getmemoryUsage("heap"); // Java heap usage
var nonHeap = getmemoryUsage("non_heap"); //Java Non-heap usage
var leds = variables.ledIndex['piStatus'];
setLEDHue(leds[1],colorScale(systemInfo.cpuSystem,'green','red'));
setLEDHue(leds[2],colorScale(heap.used/heap.max, 'green','red'));
setLEDHue(leds[3],colorScale(systemInfo.queueRequests, 'green','red'));
setLEDHue(leds[4],colorScale(randRange(0,1), 'green','red'));
updateLighleds
}
//Site Monitor (health check)
function siteMonitor(){
pingServer(1,"http://my.agritrackingsystems.com");
pingServer(2,"http://app.cropcast.com");
pingServer(3,"https://www.ortussolutions.com");
pingServer(4,"https://www.google.com/");
}
...
Spend hours poking holes
first prototype
Second prototype
Make it look prettier
Finally
Creating a startup service
[Unit]
Description='Status Lights'
[Service]
ExecStart='/usr/local/bin/box' server start name='statuslights' directory='/home/pi/Desktop/lightStatus/' serverConfigFile='/home/pi/Desktop/lightStatus/server.json' --noSaveSettings
ExecStop='/usr/local/bin/box' server stop name='statuslights' directory='/home/pi/Desktop/lightStatus/' serverConfigFile='/home/pi/Desktop/lightStatus/server.json'
Type=forking
SuccessExitStatus=143
Restart=on-failure
RestartSec=2000ms
[Install]
WantedBy=multi-user.target
Coldbox Site, Content Box (piBox), CFML Site
Creating a startup service
[Unit]
Description='Status Lights'
[Service]
ExecStart='/usr/local/bin/box' task run '/home/pi/Desktop/lightStatus/twinkleTask.cfc'
Type=simple
SuccessExitStatus=143
Restart=on-failure
RestartSec=2000ms
[Install]
WantedBy=multi-user.target
Task "Manager"
Raspberry Pi Powered CF Status Monitor
By uniquetrio2000
Raspberry Pi Powered CF Status Monitor
Never be in the dark when it comes to the status of your builds and servers, with this awesome Raspberry Pi LED Server Status Monitor. Whatever you want to know the status of can be sent directly to an LED Light strip so you know the moment there is a problem or a successful build.
- 654