summaryrefslogtreecommitdiff
path: root/wally/teensy.go
blob: 534cab043ca82e403b93321578244a8a892d186a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
package wally

import (
	"fmt"
	"github.com/google/gousb"
	"github.com/marcinbor85/gohex"
	"os"
	"time"
)

// TeensyFlash: Flashes Teensy boards.
// It opens the firmware file at the provided path, checks it's integrity, wait for the keyboard to be in Flash mode, flashes it and reboots the board.
func TeensyFlash(s *State) {
	file, err := os.Open(s.FirmwarePath)
	if err != nil {
		message := fmt.Sprintf("Error while opening firmware: %s", err)
		s.Log("error", message)
		return
	}
	defer file.Close()

	s.FlashProgress.Sent = 0
	s.FlashProgress.Total = ergodoxCodeSize

	firmware := gohex.NewMemory()
	err = firmware.ParseIntelHex(file)
	if err != nil {
		message := fmt.Sprintf("Error while parsing firmware: %s", err)
		s.Log("error", message)
		return
	}

	ctx := gousb.NewContext()
	defer ctx.Close()
	var dev *gousb.Device

	// Loop until a keyboard is ready to flash
	for {
		s.Log("info", "Waiting for a DFU capable device")
		// if the app is reset stop this goroutine and close the usb context
		if s.Step != 3 {
			s.Log("info", "App reset, interrupting the flashing process.")
			return
		}

		devs, err := ctx.OpenDevices(func(desc *gousb.DeviceDesc) bool {
			if desc.Vendor == gousb.ID(halfKayVendorID) && desc.Product == gousb.ID(halfKayProductID) {
				return true
			}
			return false
		})

		defer func() {
			for _, d := range devs {
				d.Close()
			}
		}()

		if err != nil {
			message := fmt.Sprintf("OpenDevices: %s", err)
			s.Log("warning", message)
		}

		if len(devs) > 0 {
			dev = devs[0]
			break
		}
		time.Sleep(1 * time.Second)
	}

	// Detach keyboard from the kernel
	dev.SetAutoDetach(true)

	// Claim usb device
	cfg, err := dev.Config(1)
	defer cfg.Close()
	if err != nil {
		message := fmt.Sprintf("Error while claiming the usb interface: %s", err)
		s.Log("error", message)
		return
	}

	s.Step = 4
	s.emitUpdate()

	// Loop on the firmware data and program
	var addr uint32
	for addr = 0; addr < ergodoxCodeSize; addr += ergodoxBlockSize {
		// set a longer timeout when writing the first block
		if addr == 0 {
			dev.ControlTimeout = 5 * time.Second
		} else {
			dev.ControlTimeout = 500 * time.Millisecond
		}
		// Prepare and write a firmware block
		// https://www.pjrc.com/teensy/halfkay_protocol.html
		buf := make([]byte, ergodoxBlockSize+2)
		buf[0] = byte(addr & 255)
		buf[1] = byte((addr >> 8) & 255)
		block := firmware.ToBinary(addr, ergodoxBlockSize, 255)
		for index := range block {
			buf[index+2] = block[index]
		}

		bytes, err := dev.Control(0x21, 9, 0x0200, 0, buf)
		if err != nil {
			message := fmt.Sprintf("Error while sending firmware bytes: %s", err)
			s.Log("error", message)
			return
		}

		message := fmt.Sprintf("Sent %d bytes out of %d", addr, ergodoxCodeSize)
		s.Log("info", message)
		s.FlashProgress.Sent += bytes
		s.emitUpdate()
	}

	time.Sleep(1 * time.Second)

	s.Log("info", "Sending the reboot command")
	buf := make([]byte, ergodoxBlockSize+2)
	buf[0] = byte(0xFF)
	buf[1] = byte(0xFF)
	buf[2] = byte(0xFF)
	_, err = dev.Control(0x21, 9, 0x0200, 0, buf)

	if err != nil {
		message := fmt.Sprintf("Error while rebooting device: %s", err)
		s.Log("error", message)
		return
	}

	s.Step = 5
	s.Log("info", "Flash complete")
}