Build Monitor 2.0 using ws2812 LED strip

As cool as a flashing light is, it just wasn't enough... 




LED strips are the way of the future... at least when it comes to a funky build monitor. 

The previous build monitor was built using a solid state relay, pi zero and a 220V LED downlight. Version 2 has stepped up a bit with the addition of a ws2812 led strip. 

The Go lang code also includes a ws2811 library for controlling the DMA and PWM of the raspberry pi zero. The library, written in c can be found on github. The library includes a wrapper for python as well as a wrapper for GO, which is the one I was interested in.

Challenge 1: The led strip came with a tiny data sheet specifying 14.4Watts per meter. Hmmm doesnt sound too bad at first until you realize that the strips require 5V, and this is a 5 meter strip ... so P= VI therefore A = (14.4 x 5m) / 5v = 14.4A. that's a lot of current being drawn by the LEDS. This meant I would need a pretty good power supply to be able to source all that current. My choice was a meanwell powersupply specifically the LRS-100-5  ie. 100 Watt 5V which should be able to supply at least 20A if required.

Challenge 2: The raspberry pi's GPIO pins work at 3V3 meaning it probably wouldn't be enough to turn on the LED strips ws2811 chips data line... enter the buffer chip 74ls125AN, (follow the link for the datasheet) a simple buffer that can be used to convert the 3V3 level to 5V. I used to veraboard to connect to the chip... very simple





Here we go, challenges met now for the setup:
Step 1: Install some dependencies

sudo apt-get update

sudo apt-get install build-essential python-dev git scons swig

scons will be used to build the library, and python just to do an initial test

Step 2: Get the library onto the PI, so my go stuff is located at /usr/share/go/src/ so to make life simple (this is probably not the best way to do it) I checked out a clone of the git repo into the src folder at /usr/share/go/src/pkg ... git clone https://github.com/jgarff/rpi_ws281x.git

Step 3: Build the library, run the following in the /usr/share/go/src/pkg/rpi_ws281x

sudo scons

Step 4: I had some issues with GCC includes and library path so I hacked it a little to save some time... so within the /usr/share/go/src/pkg/rpi_ws281x we're going to create some symbolic links to confuse any c madness into think our header files are in the right place:

run the following commands to create the links:

sudo ln -s /usr/share/go/src/pkg/rpi_ws281x/ws2811.h /usr/local/include/ws2811.h
sudo ln -s /usr/share/go/src/pkg/rpi_ws281x/pwm.h /usr/local/include/pwm.h
sudo ln -s /usr/share/go/src/pkg/rpi_ws281x/dma.h /usr/local/include/dma.h
sudo ln -s /usr/share/go/src/pkg/rpi_ws281x/clk.h /usr/local/include/clk.h
sudo ln -s /usr/share/go/src/pkg/rpi_ws281x/gpio.h /usr/local/include/gpio.h

sudo ln -s /usr/share/go/src/pkg/rpi_ws281x/rpihw.h /usr/local/include/rpihw.h

Step 5: Some code. We're going to use GPIO 18 for the comms to the ws2812 strip.





       
package main

import(

 "fmt"
 "log"
 "net/http"
 "html/template"
 "encoding/json"
 "github.com/stianeikeland/go-rpio"
        "os"
        "time"
 "rpi_ws281x/golang/ws2811"
 "gocron-master/gocron-master"
)


var buildStable bool = true
var alerting bool = false
var (
        // Use mcu pin 10, corresponds to physical pin 19 on the pi
        pin = rpio.Pin(10)
)
var buildch chan bool
func main() {
 
  if err := ws2811.Init(18, 300, 32); err != nil {
                fmt.Println(err)
                os.Exit(1)
        }
 
 buildch := make(chan bool)
 
 // Open and map memory to access gpio, check for errors
        if err := rpio.Open(); err != nil {
                fmt.Println(err)
                os.Exit(1)
        }

        // Unmap gpio memory when done
        defer rpio.Close()

        // Set pin to output mode
        pin.Output()
 
 fmt.Println("Initialized")
 

   
 rainbowCycle()

 http.HandleFunc("/", Home)
 
 http.HandleFunc("/alertBuildUnstable", func(w http.ResponseWriter, r *http.Request) {
 
  handleBuildError(w, buildch)
 }) 
 
 http.HandleFunc("/alertTestUnstable", func(w http.ResponseWriter, r *http.Request) {
 
  handleTestError(w, buildch)
 })

 http.HandleFunc("/alertBuildStable", func(w http.ResponseWriter, r *http.Request) {
 
  handleBuildStable(w, buildch)
 }) 

 http.HandleFunc("/alertStop", func(w http.ResponseWriter, r *http.Request) {
 
  handleStop(w, buildch)
 }) 

 go scheduleJobs(buildch)
 log.Fatal(http.ListenAndServe(":8083", nil))
  // remove, clear and next_run
   
 
}

func scheduleJobs(buildch chan bool){

 //schedule some jobs
 // gocron.Every(1).Day().At("18:28").Do(standUp)
  gocron.Every(1).Day().At("10:30").Do(standUp, buildch)
  gocron.Every(1).Day().At("11:45").Do(standUp, buildch)
  gocron.Every(1).Day().At("14:00").Do(standUp, buildch)
   // gocron.Every(1).Monday().At("18:30").Do(task)   
    _, time := gocron.NextRun()
 fmt.Println("next run: ")
    fmt.Println(time)
    // function Start start all the pending jobs
    <- gocron.Start()
}

func standUp(buildch chan bool){ 
 if alerting == true {
  alerting = false
  buildch <- true
 }
 
 stripStandUp(getColour(255,255,255))
 ws2811.Clear()
 
 
 if buildStable == true {
  go setStripColour(254,0,0)  
 } else {
  alerting = true
  go toggleTestAlert(buildch)  
 }
 
}

func Home(w http.ResponseWriter, req *http.Request) {
    render(w, "/home/pi/gowork/alert/alert.html")
}

func render(w http.ResponseWriter, tmpl string) {
    tmpl = fmt.Sprintf("%s", tmpl)
    t, err := template.ParseFiles(tmpl)
    if err != nil {
        log.Print("template parsing error: ", err)
    }
    err = t.Execute(w, "")
    if err != nil {
        log.Print("template executing error: ", err)
    }
}

func handleBuildError(w http.ResponseWriter, buildch chan bool) {

 mapResponse, _ := json.Marshal("unstable")

 fmt.Fprintf(w, "%q", string(mapResponse))
 
 time.Sleep(time.Second)

 buildStable = false
 if alerting == false {
  alerting = true
  go toggleBuildAlert(buildch)
 }

}

func handleTestError(w http.ResponseWriter, buildch chan bool) {

 mapResponse, _ := json.Marshal("unstableTest")

 fmt.Fprintf(w, "%q", string(mapResponse))
 
 time.Sleep(time.Second)

 buildStable = false
 if alerting == false {
  alerting = true
  go toggleTestAlert(buildch)
 }

}

func toggleBuildAlert(buildch chan bool){  
 buildStable = false
 ws2811.Clear()
 for buildStable  == false {
  
 stripStart(getColour(0,255,0))
 
//  pin.Toggle()
  //              time.Sleep(time.Second / 6)
  // use select for non blocking operation
      select {
      case buildStable = <-buildch:
   fmt.Println("build stable? : ", buildStable)
      default:
   fmt.Println("build unstable!")
      }
 }
}

func toggleTestAlert(buildch chan bool){  
ws2811.Clear()
 buildStable  = false
 for buildStable  == false {
 stripStart(getColour(165 ,255,0))

//  pin.Toggle()
//                time.Sleep(time.Second)
  // use select for non blocking operation
      select {
      case buildStable = <-buildch:
   fmt.Println("build stable? : ", buildStable)
      default:
   fmt.Println("build unstable!")
      }
 }
}

func handleBuildStable(w http.ResponseWriter, buildch chan bool) {

 mapResponse, _ := json.Marshal("stable")
 time.Sleep(time.Second) 
 fmt.Fprintf(w, "%q", string(mapResponse))
 alerting = false
 if buildStable == false {
  buildch <- true
  setStripColour(254,0,0)
  buildStable = true
 } else {
  //be proud make it blue
  setStripColour(0,0,254)
 }
}

func handleStop(w http.ResponseWriter, buildch chan bool) {

 mapResponse, _ := json.Marshal("stop")
 time.Sleep(time.Second) 
 fmt.Fprintf(w, "%q", string(mapResponse))
 if buildStable == false {
  buildch <- true
 }
 time.Sleep(time.Second) 
 ws2811.Clear()
 ws2811.Render()
 buildStable = true
 alerting = false 
}

func stripStart(colour uint32){
 for i := 0; i < 150; i++ {
                ws2811.SetLed(i, colour)
  ws2811.SetLed(299-i, colour)
                ws2811.Render()
                time.Sleep(time.Second / 60)
        }

        for i := 149; i>=0 ; i-- {
                ws2811.SetLed(i, 0)
  ws2811.SetLed(149+(149 - i),0)
                ws2811.Render()
                time.Sleep(time.Second / 60)
        }

}

func stripStandUp(colour uint32){
 for j := 0; j < 20 ; j++ {
   for i := 0; i < 300; i++ {
     ws2811.SetLed(i, colour)
   }
   ws2811.Render()
   time.Sleep(time.Second /2) 
   for i := 0; i < 300; i++ {
     ws2811.SetLed(i, 0)
   }
    ws2811.Render()
   time.Sleep(time.Second /2) 
 }  
}

func wheel(pos int) uint32 {
 //Generate rainbow colors across 0-255 positions
 var rgb uint32

 if pos < 85{
  rgb  = uint32(pos) * 3
  rgb = (rgb << 8) + 255 - uint32(pos)
  rgb = (rgb << 8) + 0
  return rgb
 } else if pos < 170{
  pos -= 85
  rgb  = 255 - uint32(pos) * 3
  rgb = (rgb << 8) + 0
  rgb = (rgb << 8) + uint32(pos) * 3
  return rgb
 } else {
  pos -= 170
  rgb  = 0
  rgb = (rgb << 8) + uint32(pos) * 3
  rgb = (rgb << 8) + 255 - uint32(pos) * 3
  return rgb
 }
}

func getColour(green uint32, red uint32, blue uint32) uint32{
 var rgb uint32
 rgb  = green
  rgb = (rgb << 8) + red
  rgb = (rgb << 8) + blue
 return rgb
}

func setStripColour(green uint32, red uint32, blue uint32){
 for i := 0; i < 300; i++ {
   ws2811.SetLed(i, getColour(green, red, blue))   
  }
 ws2811.Render()
}

func rainbowCycle(){
 //Draw rainbow that uniformly distributes itself across all pixels.
 var j int = 0
 //for j := 0; j <= 256 * 1 ; j++ {

  for i := 0; i < 300; i++ {
   ws2811.SetLed(i, wheel((int(i * 256 / 300) + j) & 255))
   ws2811.Render()
   time.Sleep(time.Second / 100)
  }
 //}
 setStripColour(0,0,254)
}






 


Share on Google Plus

About J@$E

Developer of stuffs ...
    Blogger Comment

4 comments:

  1. This comment has been removed by the author.

    ReplyDelete
  2. Good stuff! This post has helped me greatly to start work on rewriting https://github.com/ThePendulum/aurora in Go!

    ReplyDelete
  3. Thank you for your articles that you have shared with us. Hopefully you can give the article a good benefit to us. LED Driver Constant Voltage

    ReplyDelete