Testing go 1.5 cross compilation on raspberry pi

I’m so excited with the new release of golang. One particular feature is now very easy to build for multiple architecture. If you seen my other posts, I also like to tinker with my raspberry-pi. On my previous project I use either ruby or python for building some stuff. One annoying thing is dependency, setup and compilation is usually quite slow. Would be cool if I could just create some stuff in desktop and just scp the binary to pi and everything should work!

There is this nice article that explain the process. Let’s start there and create simple application

 1package main
 2
 3import (
 4	"fmt"
 5	"runtime"
 6)
 7
 8func main() {
 9	fmt.Printf("Hello. I'm running %s on %s architecture\n", runtime.GOOS, runtime.GOARCH)
10}

Let’s try to build it

1# create binary called 'main' for raspberry-pi (1gen)
2$ env GOOS=linux GOARCH=arm GOARM=6 go build main.go
3
4# transfer the binary to pi
5$ scp main pi:

Now, crossing my finger, and run it on my pi

1$ ssh pi
2
3pi@raspbmc:~$ ./main 
4Hello. I'm running linux on arm architecture
5pi@raspbmc:~$ 

Holy crap, it’s that easy. I’m excited. Let’s see if go routine works

 1package main
 2
 3import (
 4	"fmt"
 5	"sync"
 6)
 7
 8func main() {
 9	var wg sync.WaitGroup
10	nWorker := 2
11	c := make(chan string)
12
13	// write 10 message to channel c
14	go func() {
15		defer close(c)
16		for i := 0; i < 10; i++ {
17			c <- fmt.Sprintf("item sequence %d", i)
18		}
19	}()
20
21	// create n worker to read from channel c
22	for i := 0; i < nWorker; i++ {
23		wg.Add(1)
24		go func(worker int) {
25			for msg := range c {
26				fmt.Printf("worker %d: msg %s\n", worker, msg)
27			}
28			wg.Done()
29		}(i)
30	}
31
32	wg.Wait()
33	fmt.Println("That's super awesome!!, cee ya!")
34}

Again, build and scp, and cross some more fingers

 1
 2@raspbmc:~$ ls -alh main
 3-rwxr-xr-- 1 pi pi 1.9M Aug 29 18:08 main
 4pi@raspbmc:~$ ./main 
 5worker 1: msg item sequence 0
 6worker 0: msg item sequence 1
 7worker 1: msg item sequence 2
 8worker 0: msg item sequence 3
 9worker 0: msg item sequence 4
10worker 0: msg item sequence 5
11worker 0: msg item sequence 6
12worker 0: msg item sequence 7
13worker 0: msg item sequence 8
14worker 1: msg item sequence 9
15That's super awesome!!, cee ya!

Super awesome indeed, it just works!. Oh and the binary is not that big 1.9M considering it statically include the library.

Hmm let’s try something fun that fiddle with the gpio pin. Let’s create something that what raspberry-pi made for.. blinking led :p

There is already library. Let’s starts there.

 1package main
 2
 3import (
 4	"fmt"
 5	"os"
 6	"os/signal"
 7	"time"
 8
 9	"github.com/davecheney/gpio"
10	"github.com/davecheney/gpio/rpi"
11)
12
13func main() {
14	// set GPIO25 to output mode
15	pin, err := gpio.OpenPin(rpi.GPIO25, gpio.ModeOutput)
16	if err != nil {
17		fmt.Printf("Error opening pin! %s\n", err)
18		return
19	}
20
21	// turn the led off on exit
22	c := make(chan os.Signal, 1)
23	signal.Notify(c, os.Interrupt)
24	go func() {
25		for _ = range c {
26			fmt.Printf("\nClearing and unexporting the pin.\n")
27			pin.Clear()
28			pin.Close()
29			os.Exit(0)
30		}
31	}()
32
33	dance(pin)
34}
35
36func dance(pin gpio.Pin) {
37	for {
38		pin.Set()
39		time.Sleep(500 * time.Millisecond)
40		pin.Clear()
41		time.Sleep(500 * time.Millisecond)
42	}
43}

Dayuumm.. It’s that easy. No more pip install or bundle install on pi. Just scp the binary!

Of course we’ve got this far, we must try to control it remotely via HTTP.


 1package main
 2
 3import (
 4	"fmt"
 5	"net/http"
 6	"os"
 7	"os/signal"
 8	"time"
 9
10	"github.com/davecheney/gpio"
11	"github.com/davecheney/gpio/rpi"
12)
13
14// channel to control start blinking or not
15var ctrlChan = make(chan bool)
16
17func main() {
18	// set GPIO25 to output mode
19	pin, err := gpio.OpenPin(rpi.GPIO25, gpio.ModeOutput)
20	if err != nil {
21		fmt.Printf("Error opening pin! %s\n", err)
22		return
23	}
24
25	// turn the led off on exit
26	c := make(chan os.Signal, 1)
27	signal.Notify(c, os.Interrupt)
28	go func() {
29		for _ = range c {
30			fmt.Printf("\nClearing and unexporting the pin.\n")
31			pin.Clear()
32			pin.Close()
33			os.Exit(0)
34		}
35	}()
36
37	go dance(pin, ctrlChan)
38	ctrlChan <- true
39
40	// http listen
41	http.HandleFunc("/dance", danceHandler)
42	http.ListenAndServe(":8080", nil)
43}
44
45func dance(pin gpio.Pin, ctrlChan chan bool) {
46	enabled := false
47	for {
48		select {
49		case val := <-ctrlChan:
50      // got value from danceHandler via the channel
51			fmt.Printf("dancing? %+v\n", val)
52			enabled = val
53		default:
54			if enabled {
55				pin.Set()
56				time.Sleep(500 * time.Millisecond)
57				pin.Clear()
58				time.Sleep(500 * time.Millisecond)
59			}
60		}
61	}
62}
63
64func danceHandler(w http.ResponseWriter, r *http.Request) {
65	fmt.Println("Received request")
66	s := r.URL.Query().Get("s")
67	ctrlChan <- s == "1" // tell dance to enable or not
68}

Look at that. All it takes to do that is just 67 lines of code. The main logic just about 31 lines. That’s including the fancy dancing blinking. No framework, only gpio dependency. Everything else is go stdlib.

Ok, let’s do one other thing. The reverse! Trigger something when button is pressed. For this let’s just use websocket, and see how hard it is to implement this. What this mean that if you have websocket client (web browser) you could listen to event and stream it directly from your raspberry-pi to your computer.


 1package main
 2
 3import (
 4	"bufio"
 5	"fmt"
 6	"net/http"
 7	"os"
 8	"os/signal"
 9	"time"
10
11	"github.com/davecheney/gpio"
12	"github.com/davecheney/gpio/rpi"
13	"golang.org/x/net/websocket"
14)
15
16// channel to control start blinking or not
17var ctrlChan = make(chan bool)
18
19func main() {
20	// set GPIO25 to output mode
21	pin, err := gpio.OpenPin(rpi.GPIO25, gpio.ModeOutput)
22	if err != nil {
23		fmt.Printf("Error opening pin! %s\n", err)
24		return
25	}
26
27	// turn the led off on exit
28	c := make(chan os.Signal, 1)
29	signal.Notify(c, os.Interrupt)
30	go func() {
31		for _ = range c {
32			fmt.Printf("\nClearing and unexporting the pin.\n")
33			pin.Clear()
34			pin.Close()
35			os.Exit(0)
36		}
37	}()
38
39	go buttonHandler(pin)
40
41	// http listen
42	http.Handle("/", websocket.Handler(EchoServer))
43	http.ListenAndServe(":8080", nil)
44}
45
46// handle websocket connection
47func EchoServer(ws *websocket.Conn) {
48	w := bufio.NewWriter(ws)
49	w.WriteString("Hello, i will tell you if button is pressed\n\n")
50	w.Flush()
51
52	for {
53		<-ctrlChan
54		w.WriteString("Kachhinggg... somebody pressed the button\n")
55		w.Flush()
56	}
57}
58
59// check if button is pressed
60func buttonHandler(p gpio.Pin) {
61	for {
62		if p.Get() {
63			ctrlChan <- true
64		}
65		time.Sleep(150 * time.Millisecond)
66	}
67}

You see there I connect 2 client. One browser and the other one is cli app that I created. Notice that they got their messages alternately between each other. This is because they are sharing the same channel. I will leave it to you my kind reader as an exercise to make it broadcast the message instead of distributing them :)

all source are available at https://github.com/yulrizka/go-pi-experiments