Skip to content

Commit 66bb362

Browse files
committed
Fix connect and add trayicon
1 parent 90d8001 commit 66bb362

File tree

6 files changed

+188
-33
lines changed

6 files changed

+188
-33
lines changed

connect_v1.go

Lines changed: 75 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -29,40 +29,43 @@
2929
package main
3030

3131
import (
32-
"log"
32+
"bytes"
33+
34+
"golang.org/x/net/context"
35+
36+
serial "go.bug.st/serial.v1"
3337

3438
"github.com/arduino/arduino-create-agent/app"
35-
"github.com/arduino/arduino-create-agent/connect"
39+
"github.com/codeclysm/cc"
3640
"github.com/goadesign/goa"
3741
"github.com/gorilla/websocket"
38-
"golang.org/x/net/context"
3942
)
4043

41-
type device struct {
42-
input chan []byte
43-
output chan []byte
44-
cancel func()
45-
}
44+
type conns map[*websocket.Conn]*cc.Stoppable
4645

4746
// ConnectV1Controller implements the connect_v1 resource.
4847
type ConnectV1Controller struct {
4948
*goa.Controller
50-
devices map[string]device
49+
sockets conns
5150
}
5251

5352
// NewConnectV1Controller creates a connect_v1 controller.
5453
func NewConnectV1Controller(service *goa.Service) *ConnectV1Controller {
5554
return &ConnectV1Controller{
5655
Controller: service.NewController("ConnectV1Controller"),
57-
devices: make(map[string]device),
56+
sockets: make(conns),
5857
}
5958
}
6059

6160
// Websocket runs the websocket action.
6261
func (c *ConnectV1Controller) Websocket(ctx *app.WebsocketConnectV1Context) error {
63-
cont, cancel := context.WithCancel(context.Background())
64-
input, output, err := connect.Open(cont, ctx.Port, ctx.Baud)
62+
// Open port
63+
mode := &serial.Mode{
64+
BaudRate: ctx.Baud,
65+
}
66+
port, err := serial.Open(ctx.Port, mode)
6567
if err != nil {
68+
goa.LogError(ctx, err.Error())
6669
return ctx.BadRequest()
6770
}
6871

@@ -73,28 +76,74 @@ func (c *ConnectV1Controller) Websocket(ctx *app.WebsocketConnectV1Context) erro
7376

7477
conn, err := upgrader.Upgrade(ctx.ResponseWriter, ctx.Request, nil)
7578
if err != nil {
79+
goa.LogError(ctx, err.Error())
7680
return ctx.BadRequest()
7781
}
7882

79-
go func() {
80-
for msg := range output {
81-
log.Println(len(msg))
83+
c.sockets[conn] = cc.Run(listen(ctx, conn, port))
8284

83-
conn.WriteMessage(websocket.TextMessage, msg)
84-
}
85+
<-c.sockets[conn].Stopped
86+
delete(c.sockets, conn)
87+
88+
conn.Close()
89+
90+
return nil
91+
}
92+
93+
// StopAll stops all websocket connections
94+
func (c *ConnectV1Controller) StopAll() {
95+
for conn, stoppable := range c.sockets {
8596
conn.Close()
86-
}()
97+
stoppable.Stop()
98+
<-stoppable.Stopped
99+
}
100+
}
87101

88-
for {
89-
_, msg, err := conn.ReadMessage()
90-
if err != nil {
91-
break
92-
}
102+
func listen(ctx context.Context, conn *websocket.Conn, port serial.Port) (reader, writer cc.StoppableFunc) {
103+
reader = func(done chan struct{}) {
104+
L:
105+
for {
106+
select {
107+
case <-done:
108+
break L
93109

94-
input <- msg
110+
default:
111+
msg := make([]byte, 1024)
112+
n, err := port.Read(msg)
113+
if err != nil {
114+
goa.LogError(ctx, err.Error(), "when", "read from port")
115+
break L
116+
}
117+
if n > 0 {
118+
conn.WriteMessage(websocket.TextMessage, bytes.Trim(msg, "\x00"))
119+
}
120+
}
121+
}
95122
}
96123

97-
cancel()
124+
writer = func(done chan struct{}) {
125+
L:
126+
for {
127+
select {
128+
case <-done:
129+
break L
98130

99-
return nil
131+
default:
132+
_, msg, err := conn.ReadMessage()
133+
if err != nil {
134+
goa.LogError(ctx, err.Error(), "when", "read from websocket")
135+
break L
136+
}
137+
_, err = port.Write(msg)
138+
if err != nil {
139+
goa.LogError(ctx, err.Error(), "when", "write on port")
140+
break L
141+
}
142+
}
143+
}
144+
port.Close()
145+
conn.Close()
146+
}
147+
148+
return reader, writer
100149
}

main.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,17 +31,30 @@
3131
package main
3232

3333
import (
34+
"flag"
35+
"log"
36+
"os"
37+
"os/exec"
38+
"strings"
3439
"time"
3540

3641
"golang.org/x/net/context"
3742

3843
"github.com/arduino/arduino-create-agent/app"
3944
"github.com/arduino/arduino-create-agent/discovery"
45+
"github.com/getlantern/systray"
4046
"github.com/goadesign/goa"
4147
"github.com/goadesign/goa/middleware"
48+
"github.com/kardianos/osext"
4249
)
4350

4451
func main() {
52+
var (
53+
hibernate = flag.Bool("hibernate", false, "start hibernated")
54+
)
55+
56+
flag.Parse()
57+
4558
// Create service
4659
service := goa.New("arduino-create-agent")
4760

@@ -67,9 +80,50 @@ func main() {
6780
public := NewPublicController(service)
6881
app.MountPublicController(service, public)
6982

83+
// Mount systray
84+
restart := restartFunc("", !*hibernate)
85+
shutdown := func() {
86+
os.Exit(0)
87+
}
88+
go setupSystray(*hibernate, "XXX", "YYY", restart, shutdown)
89+
7090
// Start service
7191
if err := service.ListenAndServe(":9000"); err != nil {
7292
service.LogError("startup", "err", err)
7393
}
94+
}
7495

96+
// RestartFunc launches itself before exiting. It works because we pass an option to tell it to wait for 5 seconds, which gives us time to exit and unbind from serial ports and TCP/IP
97+
// sockets like :8989
98+
func restartFunc(path string, hibernate bool) func() {
99+
return func() {
100+
// Quit systray
101+
systray.Quit()
102+
103+
// figure out current path of executable so we know how to restart
104+
// this process using osext
105+
exePath, err := osext.Executable()
106+
if err != nil {
107+
log.Fatalf("Error getting exe path using osext lib. err: %v\n", err)
108+
}
109+
110+
if path == "" {
111+
log.Printf("exePath using osext: %v\n", exePath)
112+
} else {
113+
exePath = path
114+
}
115+
exePath = strings.Trim(exePath, "\n")
116+
hiberString := ""
117+
if hibernate {
118+
hiberString = "-hibernate"
119+
}
120+
121+
// Execute command
122+
cmd := exec.Command(exePath, hiberString)
123+
err = cmd.Start()
124+
if err != nil {
125+
log.Fatalf("Got err restarting spjs: %v\n", err)
126+
}
127+
os.Exit(0)
128+
}
75129
}

pool/pool.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
package pool

pool/pool_test.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package pool_test
2+
3+
import (
4+
"io"
5+
"testing"
6+
)
7+
8+
func TestReaderFail(t *testing.T) {
9+
p := pool.New()
10+
11+
var rwc io.ReadWriteCloser
12+
13+
p.Write(rwc, []byte("msg"))
14+
15+
p.Read(rwc, func(n int, msg []byte) {
16+
17+
})
18+
19+
p.CloseAll()
20+
21+
}

systray.go

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,21 +29,24 @@
2929
package main
3030

3131
import (
32+
"os"
3233
"runtime"
3334

3435
"github.com/arduino/arduino-create-agent/icon"
3536
"github.com/getlantern/systray"
3637
"github.com/skratchdot/open-golang/open"
3738
)
3839

39-
func setupSystray(hybernate bool, version, revision string, shutdown func()) {
40+
func setupSystray(hibernate bool, version, revision string, restart, shutdown func()) {
4041
runtime.LockOSThread()
41-
if !hybernate {
42-
systray.Run(setupSystrayReal(version, revision, shutdown))
42+
if !hibernate {
43+
systray.Run(setupSystrayReal(version, revision, restart))
44+
} else {
45+
systray.Run(setupSysTrayHibernate(restart, shutdown))
4346
}
4447
}
4548

46-
func setupSystrayReal(version, revision string, shutdown func()) func() {
49+
func setupSystrayReal(version, revision string, restart func()) func() {
4750
return func() {
4851
systray.SetIcon(icon.GetIcon())
4952
mURL := systray.AddMenuItem("Go to Arduino Create", "Arduino Create")
@@ -58,14 +61,36 @@ func setupSystrayReal(version, revision string, shutdown func()) func() {
5861
for {
5962
select {
6063
case <-mPause.ClickedCh:
61-
shutdown()
62-
// TODO: Restart ?
64+
systray.Quit()
65+
restart()
6366
case <-mDebug.ClickedCh:
64-
open.Start("http://localhost:9000")
67+
open.Start("http://localhost:9000/debug")
6568
case <-mURL.ClickedCh:
6669
open.Start("https://create.arduino.cc")
6770
}
6871
}
6972
}()
7073
}
7174
}
75+
76+
func setupSysTrayHibernate(restart, shutdown func()) func() {
77+
return func() {
78+
systray.SetIcon(icon.GetIconHiber())
79+
mOpen := systray.AddMenuItem("Open Plugin", "")
80+
mQuit := systray.AddMenuItem("Kill Plugin", "")
81+
82+
// Listen for events
83+
go func() {
84+
for {
85+
select {
86+
case <-mOpen.ClickedCh:
87+
systray.Quit()
88+
restart()
89+
case <-mQuit.ClickedCh:
90+
systray.Quit()
91+
os.Exit(0)
92+
}
93+
}
94+
}()
95+
}
96+
}

templates/debug.html

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,11 @@ <h1>Arduino Create Agent - Debug</h1>
9494
var formEl = document.getElementById('connect-form');
9595
var codeEl = document.getElementById('connect-code');
9696

97+
if (ws) {
98+
ws.onclose = function () {};
99+
ws.close();
100+
}
101+
97102
codeEl.textContent = '';
98103
ws = new WebSocket("ws://localhost:9000/v1/connect?port=" + port + "&baud=" + baud);
99104
ws.onopen = function () {

0 commit comments

Comments
 (0)