vibration for pinetime

This commit is contained in:
Alexander NeonXP Kiryukhin 2024-07-31 03:14:52 +03:00
commit c13afa1677
Signed by: NeonXP
GPG key ID: 35E33E1AB7776B39
19 changed files with 4124 additions and 0 deletions

27
LICENSE.txt Normal file
View file

@ -0,0 +1,27 @@
Copyright (c) 2023 Ayke van Laethem. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the
distribution.
* Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

20
README.md Normal file
View file

@ -0,0 +1,20 @@
# TinyGo board abstraction
**This is an experiment.** It may go stale, or it may change in backwards incompatible ways. Don't rely on it too much for now.
## Goals
* Provide a common abstraction for boards supported by TinyGo.
* Provide a simulated board
* Auto-generate all board definitions from a devicetree-like format. Don't write any of the definitions by hand. (See Zephyr OS for example).
* Allow generating a similar board definition from target JSON files for custom boards.
* Make it possible to identify hardware at compile time instead of relying on build tags, as long as this doesn't impact binary size too much.
## Non-goals
* Provide access to hardware unique to a particular board. A new interface has to be reasonably common, and has to be supported by at least two different boards.
* Support custom boards from this package. Only boards that are (or were) actually produced should be supported. This includes dev boards, maker boards, electronic badges, etc.
## License
BSD 2-clause license, see LICENSE.txt for details.

139
board-badger2040.go Normal file
View file

@ -0,0 +1,139 @@
//go:build badger2040
package board
import (
"machine"
"math/bits"
"time"
"tinygo.org/x/drivers"
"tinygo.org/x/drivers/pixel"
"tinygo.org/x/drivers/uc8151"
)
const (
Name = "badger2040"
)
var (
Power = dummyBattery{state: UnknownBattery}
Sensors = baseSensors{}
Display = mainDisplay{}
Buttons = &gpioButtons{}
)
type mainDisplay struct{}
func (d mainDisplay) PPI() int {
return 102 // 296px wide display / 2.9 inches wide display
}
func (d mainDisplay) Configure() Displayer[pixel.Monochrome] {
machine.ENABLE_3V3.Configure(machine.PinConfig{Mode: machine.PinOutput})
machine.ENABLE_3V3.High()
machine.SPI0.Configure(machine.SPIConfig{
Frequency: 12 * machine.MHz,
SCK: machine.EPD_SCK_PIN,
SDO: machine.EPD_SDO_PIN,
})
display := uc8151.New(machine.SPI0, machine.EPD_CS_PIN, machine.EPD_DC_PIN, machine.EPD_RESET_PIN, machine.EPD_BUSY_PIN)
display.Configure(uc8151.Config{
Rotation: drivers.Rotation270,
Speed: uc8151.TURBO,
FlickerFree: true,
Blocking: false,
})
display.ClearDisplay()
return &display
}
func (d mainDisplay) MaxBrightness() int {
return 1
}
func (d mainDisplay) SetBrightness(level int) {
// Nothing to do here.
}
func (d mainDisplay) WaitForVBlank(defaultInterval time.Duration) {
dummyWaitForVBlank(defaultInterval)
}
func (d mainDisplay) ConfigureTouch() TouchInput {
return noTouch{}
}
type gpioButtons struct {
state uint8
previousState uint8
}
func (b *gpioButtons) Configure() {
machine.BUTTON_A.Configure(machine.PinConfig{Mode: machine.PinInput})
machine.BUTTON_B.Configure(machine.PinConfig{Mode: machine.PinInput})
machine.BUTTON_C.Configure(machine.PinConfig{Mode: machine.PinInput})
machine.BUTTON_UP.Configure(machine.PinConfig{Mode: machine.PinInput})
machine.BUTTON_DOWN.Configure(machine.PinConfig{Mode: machine.PinInput})
machine.BUTTON_USER.Configure(machine.PinConfig{Mode: machine.PinInput})
}
func (b *gpioButtons) ReadInput() {
state := uint8(0)
if !machine.BUTTON_A.Get() {
state |= 1
}
if !machine.BUTTON_B.Get() {
state |= 2
}
if !machine.BUTTON_C.Get() {
state |= 4
}
if !machine.BUTTON_UP.Get() {
state |= 8
}
if !machine.BUTTON_DOWN.Get() {
state |= 16
}
if !machine.BUTTON_USER.Get() {
state |= 32
}
b.state = state
}
var codes = [8]Key{
KeyA,
KeyB,
KeyRight,
KeyUp,
KeyDown,
KeyLeft,
}
func (b *gpioButtons) NextEvent() KeyEvent {
// The xor between the previous state and the current state is the buttons
// that changed.
change := b.state ^ b.previousState
if change == 0 {
return NoKeyEvent
}
// Find the index of the button with the lowest index that changed state.
index := bits.TrailingZeros32(uint32(change))
e := KeyEvent(codes[index])
if b.state&(1<<index) == 0 {
// The button state change was from 1 to 0, so it was released.
e |= keyReleased
}
// This button event was read, so mark it as such.
// By toggling the bit, the bit will be set to the value that is currently
// in b.state.
b.previousState ^= (1 << index)
return e
}

161
board-gameboy-advance.go Normal file
View file

@ -0,0 +1,161 @@
//go:build gameboyadvance
package board
import (
"device/gba"
"errors"
"math/bits"
"runtime/volatile"
"time"
"unsafe"
"tinygo.org/x/drivers"
"tinygo.org/x/drivers/pixel"
)
const (
Name = "gameboy-advance"
)
var (
Power = dummyBattery{state: UnknownBattery}
Sensors = baseSensors{}
Display = mainDisplay{}
Buttons = &gbaButtons{}
)
type mainDisplay struct{}
func (d mainDisplay) PPI() int {
return 99
}
func (d mainDisplay) Configure() Displayer[pixel.RGB555] {
// Use video mode 3 (in BG2, a 16bpp bitmap in VRAM) and Enable BG2.
gba.DISP.DISPCNT.Set(gba.DISPCNT_BGMODE_3<<gba.DISPCNT_BGMODE_Pos |
gba.DISPCNT_SCREENDISPLAY_BG2_ENABLE<<gba.DISPCNT_SCREENDISPLAY_BG2_Pos)
return gbaDisplay{}
}
func (d mainDisplay) MaxBrightness() int {
return 0
}
func (d mainDisplay) SetBrightness(level int) {
// The display doesn't have a backlight.
}
func (d mainDisplay) WaitForVBlank(time.Duration) {
// Wait until the VBlank flag is set.
// TODO: sleep until the next VBlank instead of busy waiting.
// (See VBlankIntrWait)
for gba.DISP.DISPSTAT.Get()&(1<<gba.DISPSTAT_VBLANK_Pos) == 0 {
}
}
func (d mainDisplay) ConfigureTouch() TouchInput {
return noTouch{}
}
type gbaDisplay struct{}
var displayFrameBuffer = (*[160 * 240]volatile.Register16)(unsafe.Pointer(uintptr(gba.MEM_VRAM)))
var errOutOfBounds = errors.New("rectangle coordinates outside display area")
const (
displayWidth = 240
displayHeight = 160
)
func (d gbaDisplay) Size() (x, y int16) {
return displayWidth, displayHeight
}
func (d gbaDisplay) Display() error {
// Nothing to do here.
return nil
}
func (d gbaDisplay) DrawBitmap(x, y int16, buf pixel.Image[pixel.RGB555]) error {
width, height := buf.Size()
if x < 0 || y < 0 || int(x)+width > displayWidth || int(y)+height > displayHeight {
return errOutOfBounds
}
// TODO: try to do a 4-byte memcpy if possible. That should significantly
// speed up the copying of this image.
for bufY := 0; bufY < int(height); bufY++ {
for bufX := 0; bufX < int(width); bufX++ {
val := buf.Get(bufX, bufY)
displayFrameBuffer[(int(y)+bufY)*240+int(x)+bufX].Set(uint16(val))
}
}
return nil
}
func (d gbaDisplay) Sleep(sleepEnabled bool) error {
return nil // nothign to do here
}
var errNoRotation = errors.New("error: SetRotation isn't supported")
func (d gbaDisplay) Rotation() drivers.Rotation {
return drivers.Rotation0
}
func (d gbaDisplay) SetRotation(rotation drivers.Rotation) error {
return errNoRotation
}
type gbaButtons struct {
state uint16
previousState uint16
}
func (b *gbaButtons) Configure() {
// nothing to configure
}
func (b *gbaButtons) ReadInput() {
b.state = gba.KEY.INPUT.Get() ^ 0x3ff
}
var codes = [16]Key{
KeyA,
KeyB,
KeySelect,
KeyStart,
KeyRight,
KeyLeft,
KeyUp,
KeyDown,
KeyR,
KeyL,
}
func (b *gbaButtons) NextEvent() KeyEvent {
// The xor between the previous state and the current state is the buttons
// that changed.
change := b.state ^ b.previousState
if change == 0 {
return NoKeyEvent
}
// Find the index of the button with the lowest index that changed state.
index := bits.TrailingZeros32(uint32(change))
e := KeyEvent(codes[index])
if b.state&(1<<index) == 0 {
// The button state change was from 1 to 0, so it was released.
e |= keyReleased
}
// This button event was read, so mark it as such.
// By toggling the bit, the bit will be set to the value that is currently
// in b.state.
b.previousState ^= (1 << index)
return e
}

242
board-gopher-badge.go Normal file
View file

@ -0,0 +1,242 @@
//go:build gopher_badge
package board
import (
"machine"
"math/bits"
"time"
"tinygo.org/x/drivers"
"tinygo.org/x/drivers/lis3dh"
"tinygo.org/x/drivers/pixel"
"tinygo.org/x/drivers/st7789"
"tinygo.org/x/drivers/ws2812"
)
const (
Name = "gopher-badge"
)
var (
Power = dummyBattery{state: UnknownBattery}
Sensors = &allSensors{}
Display = mainDisplay{}
Buttons = &gpioButtons{}
)
func init() {
AddressableLEDs = &ws2812LEDs{}
}
type allSensors struct {
baseSensors
accelX, accelY, accelZ int32
}
var accel lis3dh.Device
func (s *allSensors) Configure(which drivers.Measurement) error {
if which&(drivers.Acceleration|drivers.Temperature) != 0 {
machine.I2C0.Configure(machine.I2CConfig{
Frequency: 400 * machine.KHz,
SCL: machine.I2C0_SCL_PIN,
SDA: machine.I2C0_SDA_PIN,
})
accel = lis3dh.New(machine.I2C0)
accel.Configure()
}
return nil
}
func (s *allSensors) Update(which drivers.Measurement) error {
if which&drivers.Acceleration != 0 {
var err error
s.accelX, s.accelY, s.accelZ, err = accel.ReadAcceleration()
if err != nil {
return err
}
}
// TODO: read the temperature from the LIS3DH.
// I tried reading it uisng machine.ReadTemperature() but it was so
// inaccurate that it wasn't even usable (around -23°C in a >25°C room).
return nil
}
func (s *allSensors) Acceleration() (x, y, z int32) {
// Adjust accelerometer to match standard axes.
x = s.accelX
y = -s.accelY
z = -s.accelZ
return
}
type mainDisplay struct{}
var display st7789.DeviceOf[pixel.RGB565BE]
func (d mainDisplay) Configure() Displayer[pixel.RGB565BE] {
machine.SPI0.Configure(machine.SPIConfig{
// Mode 3 appears to be compatible with mode 0, but is slightly
// faster: each byte takes 9 clock cycles instead of 10.
// TODO: try to eliminate this last bit? Two ideas:
// - use 16-bit transfers, to halve the time the gap takes
// - use PIO, which apparently is able to send data without gap
// It would seem like TI mode would be faster (it has no gap), but
// it samples data on the falling edge instead of on the rising edge
// like the st7789 expects.
Mode: 3,
SCK: machine.SPI0_SCK_PIN,
SDO: machine.SPI0_SDO_PIN,
SDI: machine.SPI0_SDI_PIN,
Frequency: 62_500_000, // datasheet for st7789 says 16ns (62.5MHz) is the max clock speed
})
display = st7789.NewOf[pixel.RGB565BE](machine.SPI0,
machine.TFT_RST, // TFT_RESET
machine.TFT_WRX, // TFT_DC
machine.TFT_CS, // TFT_CS
machine.TFT_BACKLIGHT) // TFT_LITE
display.Configure(st7789.Config{
Rotation: st7789.ROTATION_270,
Height: 320,
// Gamma data obtained from example code provided with the display:
// https://www.buydisplay.com/2-4-inch-ips-240x320-tft-lcd-display-capacitive-touch-screen
// Without these values, most colors (especially green) don't look right.
PVGAMCTRL: []byte{0xF0, 0x00, 0x04, 0x04, 0x04, 0x05, 0x29, 0x33, 0x3E, 0x38, 0x12, 0x12, 0x28, 0x30},
NVGAMCTRL: []byte{0xF0, 0x07, 0x0A, 0x0D, 0x0B, 0x07, 0x28, 0x33, 0x3E, 0x36, 0x14, 0x14, 0x29, 0x32},
})
display.EnableBacklight(false)
return &display
}
func (d mainDisplay) MaxBrightness() int {
return 1
}
func (d mainDisplay) SetBrightness(level int) {
machine.TFT_BACKLIGHT.Set(level > 0)
}
func (d mainDisplay) WaitForVBlank(defaultInterval time.Duration) {
// Lower the SPI frequency for reading: the ST7789 supports high frequency
// writes but reading is much slower.
machine.SPI0.SetBaudRate(10_000_000)
// Wait until the scanline wraps around to 0.
// This is also what the TE line does internally.
for display.GetScanLine() == 0 {
}
for display.GetScanLine() != 0 {
}
// Restore old baud rate.
machine.SPI0.SetBaudRate(62_500_000)
}
func (d mainDisplay) PPI() int {
return 166 // 320px / (48.96mm / 25.4)
}
func (d mainDisplay) ConfigureTouch() TouchInput {
return noTouch{}
}
type gpioButtons struct {
state uint8
previousState uint8
}
func (b *gpioButtons) Configure() {
machine.BUTTON_A.Configure(machine.PinConfig{Mode: machine.PinInput})
machine.BUTTON_B.Configure(machine.PinConfig{Mode: machine.PinInput})
machine.BUTTON_UP.Configure(machine.PinConfig{Mode: machine.PinInput})
machine.BUTTON_LEFT.Configure(machine.PinConfig{Mode: machine.PinInput})
machine.BUTTON_DOWN.Configure(machine.PinConfig{Mode: machine.PinInput})
machine.BUTTON_RIGHT.Configure(machine.PinConfig{Mode: machine.PinInput})
}
func (b *gpioButtons) ReadInput() {
state := uint8(0)
if !machine.BUTTON_A.Get() {
state |= 1
}
if !machine.BUTTON_B.Get() {
state |= 2
}
if !machine.BUTTON_UP.Get() {
state |= 4
}
if !machine.BUTTON_LEFT.Get() {
state |= 8
}
if !machine.BUTTON_DOWN.Get() {
state |= 16
}
if !machine.BUTTON_RIGHT.Get() {
state |= 32
}
b.state = state
}
var codes = [8]Key{
KeyA,
KeyB,
KeyUp,
KeyLeft,
KeyDown,
KeyRight,
}
func (b *gpioButtons) NextEvent() KeyEvent {
// The xor between the previous state and the current state is the buttons
// that changed.
change := b.state ^ b.previousState
if change == 0 {
return NoKeyEvent
}
// Find the index of the button with the lowest index that changed state.
index := bits.TrailingZeros32(uint32(change))
e := KeyEvent(codes[index])
if b.state&(1<<index) == 0 {
// The button state change was from 1 to 0, so it was released.
e |= keyReleased
}
// This button event was read, so mark it as such.
// By toggling the bit, the bit will be set to the value that is currently
// in b.state.
b.previousState ^= (1 << index)
return e
}
type ws2812LEDs struct {
data [2]colorGRB
}
func (l *ws2812LEDs) Configure() {
machine.WS2812.Configure(machine.PinConfig{Mode: machine.PinOutput})
}
func (l *ws2812LEDs) Len() int {
return len(l.data)
}
func (l *ws2812LEDs) SetRGB(i int, r, g, b uint8) {
l.data[i] = colorGRB{
R: r,
G: g,
B: b,
}
}
// Send pixel data to the LEDs.
func (l *ws2812LEDs) Update() {
ws := ws2812.Device{Pin: machine.WS2812}
ws.Write(pixelsToBytes(l.data[:]))
}

102
board-mch2022.go Normal file
View file

@ -0,0 +1,102 @@
//go:build mch2022
package board
import (
"machine"
"time"
"tinygo.org/x/drivers/ili9341"
"tinygo.org/x/drivers/pixel"
"tinygo.org/x/drivers/ws2812"
)
const (
Name = "mch2022"
)
var (
Power = dummyBattery{state: UnknownBattery} // unimplemented
Sensors = baseSensors{}
Display = mainDisplay{}
Buttons = noButtons{}
)
func init() {
AddressableLEDs = &ws2812LEDs{}
}
type mainDisplay struct{}
func (d mainDisplay) Configure() Displayer[pixel.RGB565BE] {
machine.LCD_MODE.Configure(machine.PinConfig{Mode: machine.PinOutput})
machine.LCD_MODE.Low()
machine.SPI2.Configure(machine.SPIConfig{
Frequency: 80_000_000, // This is probably overclocking the ILI9341 but it seems to work.
SCK: 18,
SDO: 23,
SDI: 35,
})
display := ili9341.NewSPI(machine.SPI2, machine.LCD_DC, machine.SPI0_CS_LCD_PIN, machine.LCD_RESET)
display.Configure(ili9341.Config{
Rotation: ili9341.Rotation90,
})
return display
}
func (d mainDisplay) MaxBrightness() int {
return 0
}
func (d mainDisplay) SetBrightness(level int) {
// Brightness is controlled by the rp2040 chip.
}
func (d mainDisplay) WaitForVBlank(defaultInterval time.Duration) {
// The FPGA has a parallel output and can probably do tear-free updates, but
// not the ESP32.
dummyWaitForVBlank(defaultInterval)
}
func (d mainDisplay) PPI() int {
return 166 // 320px / (48.96mm / 25.4)
}
func (d mainDisplay) ConfigureTouch() TouchInput {
return noTouch{}
}
type ws2812LEDs struct {
data [5]colorGRB
}
func (l *ws2812LEDs) Configure() {
// Enable power to the LEDs
power := machine.PowerOn
power.Configure(machine.PinConfig{Mode: machine.PinOutput})
power.High()
// Initialize the WS2812 data pin.
machine.WS2812.Configure(machine.PinConfig{Mode: machine.PinOutput})
}
func (l *ws2812LEDs) Len() int {
return len(l.data)
}
func (l *ws2812LEDs) SetRGB(i int, r, g, b uint8) {
l.data[i] = colorGRB{
R: r,
G: g,
B: b,
}
}
// Send pixel data to the LEDs.
func (l *ws2812LEDs) Update() {
ws := ws2812.Device{Pin: machine.WS2812}
ws.Write(pixelsToBytes(l.data[:]))
}

534
board-pinetime.go Normal file
View file

@ -0,0 +1,534 @@
//go:build pinetime
package board
import (
"device/arm"
"device/nrf"
"machine"
"time"
"tinygo.org/x/drivers"
"tinygo.org/x/drivers/bma42x"
"tinygo.org/x/drivers/pixel"
"tinygo.org/x/drivers/st7789"
)
const (
Name = "pinetime"
touchInterruptPin = 28
spiFlashCSPin = machine.Pin(5)
chargeIndicationPin = machine.Pin(12)
vibrationPin = machine.Pin(16)
powerPresencePin = machine.Pin(19)
batteryVoltagePin = machine.Pin(31)
)
var (
Power = &mainBattery{}
Sensors = allSensors{}
Display = mainDisplay{}
Buttons = &singleButton{}
)
func init() {
// Enable the DC/DC regulator.
// This doesn't affect sleep power consumption, but significantly reduces
// runtime power consumpton of the CPU core (almost halving the current
// required).
nrf.POWER.DCDCEN.Set(nrf.POWER_DCDCEN_DCDCEN)
// The UART is left enabled in the Wasp-OS bootloader.
// This causes a 1.25mA increase in current consumption.
// https://github.com/wasp-os/wasp-bootloader/pull/3
nrf.UART0.ENABLE.Set(0)
vibrationPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
}
type mainBattery struct {
lastPercent int8
chargePPM int32
}
var batteryPercent = batteryApproximation{
// Data is taken from this pull request:
// https://github.com/InfiniTimeOrg/InfiniTime/pull/1444/files
voltages: [6]uint16{3500, 3600, 3700, 3750, 3900, 4180},
percents: [6]int8{0, 10, 25, 50, 75, 100},
}
func (b *mainBattery) Configure() {
chargeIndicationPin.Configure(machine.PinConfig{Mode: machine.PinInput})
powerPresencePin.Configure(machine.PinConfig{Mode: machine.PinInput})
// Configure the ADC.
// Using just one sample (instead of 256 for example), because we have our
// own filtering and long sample times actually drain a lot of power: around
// 6µA when measuing the battery every 5 seconds.
machine.InitADC()
machine.ADC{Pin: batteryVoltagePin}.Configure(machine.ADCConfig{
Reference: 3000,
SampleTime: 40, // use the longest acquisition time
Samples: 1,
})
}
func (b *mainBattery) Status() (status ChargeState, microvolts uint32, percent int8) {
rawValue := machine.ADC{Pin: batteryVoltagePin}.Get()
// Formula to calculate microvolts:
// rawValue * 6000_000 / 0x10000
// Simlified, to fit in 32-bit integers:
// rawValue * (6000_000/128) / (0x1000/128)
// rawValue * 46875 / 512
microvolts = uint32(rawValue) * 46875 / 512
isCharging := chargeIndicationPin.Get() == false // low when charging
isPowerPresent := powerPresencePin.Get() == false // low when present
if isCharging {
status = Charging
} else if isPowerPresent {
status = NotCharging
} else {
status = Discharging
}
// TODO: percent while charging
percentPPM := batteryPercent.approximatePPM(microvolts)
if b.chargePPM == 0 {
// first measurement, probably
b.chargePPM = percentPPM
} else {
b.chargePPM = (b.chargePPM*255 + percentPPM) / 256
}
newPercent := b.chargePPM / 10000
if newPercent < int32(b.lastPercent) || newPercent > int32(b.lastPercent)+1 {
// do some basic hysteresis
b.lastPercent = int8(newPercent)
}
percent = b.lastPercent
return
}
var spi0Configured bool
// Return SPI0 initialized and ready to use, configuring it if not already done.
func getSPI0() machine.SPI {
spi := machine.SPI0
if !spi0Configured {
// Set the chip select line for the flash chip to inactive.
spiFlashCSPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
spiFlashCSPin.High()
// Set the chip select line for the LCD controller to inactive.
machine.LCD_CS.Configure(machine.PinConfig{Mode: machine.PinOutput})
machine.LCD_CS.High()
// Configure the SPI bus.
spi.Configure(machine.SPIConfig{
Frequency: 8_000_000, // 8MHz is the maximum the nrf52832 supports
SCK: machine.SPI0_SCK_PIN,
SDO: machine.SPI0_SDO_PIN,
SDI: machine.SPI0_SDI_PIN,
Mode: 3,
})
// Put the flash controller in deep power-down.
// This is done so that as long as the SPI flash isn't explicitly
// initialized, it won't waste any power.
spiFlashCSPin.Low()
spi.Tx([]byte{0xB9}, nil) // deep power down
spiFlashCSPin.High()
}
return spi
}
type mainDisplay struct{}
var display *st7789.DeviceOf[pixel.RGB444BE]
func (d mainDisplay) Configure() Displayer[pixel.RGB444BE] {
// Configure the display.
// RGB444 reduces theoretic update time by up to 25%, from 115.2ms to 86.4ms
// (28.8ms reduction).
spi := getSPI0()
disp := st7789.NewOf[pixel.RGB444BE](spi,
machine.LCD_RESET,
machine.LCD_RS, // data/command
machine.LCD_CS,
machine.LCD_BACKLIGHT_HIGH) // TODO: allow better backlight control
disp.Configure(st7789.Config{
Width: 240,
Height: 240,
Rotation: drivers.Rotation0,
RowOffset: 80,
FrameRate: st7789.FRAMERATE_39,
VSyncLines: 32, // needed for VBlank, not sure why
})
disp.EnableBacklight(true) // disable the backlight
// Initialize these pins as regular pins too, for WaitForVBlank.
machine.LCD_SCK.Configure(machine.PinConfig{Mode: machine.PinOutput})
machine.LCD_SCK.Low()
machine.LCD_SDI.Configure(machine.PinConfig{Mode: machine.PinOutput})
display = &disp
return display
}
func (d mainDisplay) MaxBrightness() int {
return 1 // TODO: 0-7 is supported
}
func (d mainDisplay) SetBrightness(level int) {
machine.LCD_BACKLIGHT_HIGH.Set(!(level > 0)) // low means on, high means off
}
func (d mainDisplay) WaitForVBlank(defaultInterval time.Duration) {
// Disable the SPI so we can manually communicate with the display.
machine.SPI0.Bus.ENABLE.Set(nrf.SPIM_ENABLE_ENABLE_Disabled)
// Wait until the scanline wraps around to 0.
// This is also what the TE line does internally.
// TODO: use time.Sleep() if we can, to save power.
for readDisplayValue(st7789.GSCAN, 16) == 0 {
}
for readDisplayValue(st7789.GSCAN, 16) != 0 {
}
// Re-enable the SPI.
machine.SPI0.Bus.ENABLE.Set(nrf.SPIM_ENABLE_ENABLE_Enabled)
}
// Wait for enough time between bitbanged high and low SPI pulses.
func delaySPIClock() {
// 4 cycles, or 62.5ns.
// Together with the store, it is 6 cycles or 93.75ns.
arm.Asm("nop\nnop\nnop\nnop")
}
// Read a single value from the display, for example GSCAN, RDDID, etc.
// The bits parameter indicates the number of bits that will be received.
func readDisplayValue(cmd uint8, bits int) uint32 {
const (
cs = machine.LCD_CS
dc = machine.LCD_RS
sdi = machine.LCD_SDI
sck = machine.LCD_SCK
)
// Initialize bitbanged SPI.
delaySPIClock()
cs.Low()
dc.Low()
sdi.Configure(machine.PinConfig{Mode: machine.PinOutput})
// Clock out the command.
for i := 0; i < 8; i++ {
sdi.Set(cmd&0x80 != 0)
delaySPIClock()
sck.High()
delaySPIClock()
sck.Low()
cmd <<= 1
}
delaySPIClock()
// Dummy clock cycle (necessary for 24-bit and 32-bit read commands,
// according to the datasheet).
if bits >= 24 {
sck.High()
delaySPIClock()
sck.Low()
delaySPIClock()
}
// Read the result over SPI.
sdi.Configure(machine.PinConfig{Mode: machine.PinInputPulldown})
dc.High()
value := uint32(0)
for i := 0; i < bits; i++ {
sck.High()
delaySPIClock()
value <<= 1
if sdi.Get() {
value |= 1
}
sck.Low()
delaySPIClock()
}
// Dummy clock cycle, according to the datasheet needed in all cases but in
// my exprience only needed for 16-bit reads (GSCAN).
if bits == 16 {
sck.High()
delaySPIClock()
sck.Low()
delaySPIClock()
}
// Finish the transaction.
cs.High()
dc.High()
return value
}
func (d mainDisplay) PPI() int {
return 261
}
func (d mainDisplay) ConfigureTouch() TouchInput {
// Configure touch interrupt pin.
// After the pin goes low (for a very short time), the touch controller is
// accessible over I2C for as long as a finger touches the screen and a
// short time afterwards (a second or so) before going back to sleep.
//
// We don't actually use an interrupt here because pin change interrupts
// result in far too much current consumption (jumping from 0.19mA to
// 0.65mA), probably due to anomaly 97:
// https://infocenter.nordicsemi.com/index.jsp?topic=%2Ferrata_nRF52832_Rev2%2FERR%2FnRF52832%2FRev2%2Flatest%2Fanomaly_832_97.html
// Also see:
// https://devzone.nordicsemi.com/f/nordic-q-a/50624/about-current-consumption-of-gpio-and-gpiote
// We could use a PORT interrupt in GPIOTE, using it as a level interrupt.
// And it would be a good idea to implement this in TinyGo directly (as a
// level interrupt), but in the meantime we'll use this quick-n-dirty hack.
nrf.P0.PIN_CNF[touchInterruptPin].Set(nrf.GPIO_PIN_CNF_DIR_Input<<nrf.GPIO_PIN_CNF_DIR_Pos | nrf.GPIO_PIN_CNF_INPUT_Connect<<nrf.GPIO_PIN_CNF_INPUT_Pos | nrf.GPIO_PIN_CNF_SENSE_Low<<nrf.GPIO_PIN_CNF_SENSE_Pos)
configureI2CBus()
return touchInput{}
}
var touchPoints [1]TouchPoint
type touchInput struct{}
var touchID uint32 = 1
var touchData = make([]byte, 6)
var touchInitialized bool
const touchI2CAddress = 0x15
func (input touchInput) ReadTouch() []TouchPoint {
// The touch controller is very sparsely documented. You can find datasheet
// in English and Chinese on the PineTime wiki:
// https://wiki.pine64.org/wiki/PineTime#Component_Datasheets
// The best documentation is in the Chinese documentation, you can use
// Google Translate to translate it to English.
// Read the bit from the LATCH reister, which is set to high when TP_INT
// goes high but doesn't go low on its own. We do that manually once no more
// touches are read from the touch controller.
if nrf.P0.LATCH.Get()&(1<<touchInterruptPin) != 0 {
if !touchInitialized {
// Initialize the touch controller once we get the first touch.
// Doing it this way as the I2C bus appears unresponsive outside a
// touch event.
touchInitialized = true
// These are the values as set by InfiniTime.
// i2cBus.Tx(touchI2CAddress, []byte{0xEC, 0b00000101}, nil)
// i2cBus.Tx(touchI2CAddress, []byte{0xFA, 0b01110000}, nil)
// MotionMask register:
// [0] EnDClick (disabled, enabled in InfiniTime)
// [1] EnConUD (disabled)
// [2] EnConLR (enabled)
i2cBus.Tx(touchI2CAddress, []byte{0xEC, 0b0000_0100}, nil)
// IrqCtl register:
// [7] EnTest (disabled)
// [6] EnTouch (enabled)
// [5] EnChange (enabled)
// [4] EnMotion (enabled)
// [0] OnceWLP (disabled)
i2cBus.Tx(touchI2CAddress, []byte{0xFA, 0b0111_0000}, nil)
}
i2cBus.ReadRegister(touchI2CAddress, 1, touchData)
num := touchData[1] & 0x0f
if num == 0 {
touchID++ // for the next time
// Stop reading touch events.
// There may be a small race condition here, if the touch controller
// detects another touch while reading the touch data over I2C.
nrf.P0.LATCH.Set(1 << touchInterruptPin)
touchPoints[0].ID = 0
return nil
}
rawX := (uint16(touchData[2]&0xf) << 8) | uint16(touchData[3]) // x coord
rawY := (uint16(touchData[4]&0xf) << 8) | uint16(touchData[5]) // y coord
// Filter out erroneous data.
if rawX >= 240 || rawY >= 240 {
// X or Y are erroneous (this happens quite frequently).
// Just return the previous value as a fallback.
if touchPoints[0].ID != 0 {
return touchPoints[:1]
}
return nil
}
x := int16(rawX)
y := int16(rawY)
if display != nil {
// The screen is upside down from the configured rotation, so also
// rotate the touch coordinates.
if display.Rotation() == drivers.Rotation180 {
x = 239 - x
y = 239 - y
}
}
touchPoints[0] = TouchPoint{
X: x,
Y: y,
ID: touchID,
}
return touchPoints[:1]
}
return nil
}
// State for the one and only button on the PineTime.
type singleButton struct {
state bool
previousState bool
}
func (b *singleButton) Configure() {
// BUTTON_OUT must be held high for BUTTON_IN to read anything useful.
machine.BUTTON_OUT.Configure(machine.PinConfig{Mode: machine.PinOutput})
machine.BUTTON_OUT.Low()
machine.BUTTON_IN.Configure(machine.PinConfig{Mode: machine.PinInput})
}
func (b *singleButton) ReadInput() {
// BUTTON_OUT needs to be kept low most of the time to avoid a ~34µA current
// increase. However, setting it to high just before reading doesn't appear
// to be enough: a small delay is needed. This can be done by setting
// BUTTON_OUT high multiple times in a row, which doesn't do anything except
// introduce the needed delay.
// Four stores appear to be enough to get readings, I have added a few more
// for more reliable readings (especially as this is important for the
// watchdog timer).
machine.BUTTON_OUT.High()
machine.BUTTON_OUT.High()
machine.BUTTON_OUT.High()
machine.BUTTON_OUT.High()
machine.BUTTON_OUT.High()
machine.BUTTON_OUT.High()
machine.BUTTON_OUT.High()
machine.BUTTON_OUT.High()
state := machine.BUTTON_IN.Get()
machine.BUTTON_OUT.Low()
b.state = state
// Reset the watchdog timer only when the button is not pressed.
// The watchdog is configured in the Wasp-OS bootloader, and we have to be
// careful not to reset the watchdog while the button is pressed so that a
// long press forces a WDT reset and lets us enter the bootloader.
// For details, see:
// https://wasp-os.readthedocs.io/en/latest/wasp.html#watchdog-protocol
if !state {
nrf.WDT.RR[0].Set(0x6E524635)
}
}
func (b *singleButton) NextEvent() KeyEvent {
if b.state == b.previousState {
return NoKeyEvent
}
e := KeyEvent(KeyEnter)
if !b.state {
e |= keyReleased
}
b.previousState = b.state
return e
}
var i2cBus *machine.I2C
func initI2CBus() {
// Run I2C at a high speed (400KHz).
i2cBus.Configure(machine.I2CConfig{
Frequency: 400 * machine.KHz,
SDA: machine.Pin(6),
SCL: machine.Pin(7),
})
}
func configureI2CBus() {
if i2cBus == nil {
i2cBus = machine.I2C1
initI2CBus()
// Disable the heart rate sensor on startup, to be enabled when a driver
// configures it. It consumes around 110µA when left enabled.
machine.I2C1.WriteRegister(0x44, 0x0C, []byte{0x00})
}
}
type allSensors struct {
}
var accel *bma42x.Device
func (s allSensors) Configure(which drivers.Measurement) error {
// Configure the accelerometer (either BMA421 or BMA425, depending on the
// PineTime variant).
accel = bma42x.NewI2C(machine.I2C1, bma42x.Address)
err := accel.Configure(bma42x.Config{
Device: bma42x.DeviceBMA421 | bma42x.DeviceBMA425,
Features: bma42x.FeatureStepCounting,
})
if err != nil {
// Restart the I2C bus.
// I don't know why, but configuring the BMA421 while it is already
// configured freezes the I2C bus. The only recovery appears to be to
// restart the I2C bus entirely.
initI2CBus()
err = accel.Configure(bma42x.Config{
Device: bma42x.DeviceBMA421 | bma42x.DeviceBMA425,
Features: bma42x.FeatureStepCounting,
})
}
return err
}
func (s allSensors) Update(which drivers.Measurement) error {
if which&(drivers.Acceleration|drivers.Temperature) != 0 {
err := accel.Update(which & (drivers.Acceleration | drivers.Temperature))
if err != nil {
return err
}
}
return nil
}
func (s allSensors) Acceleration() (x, y, z int32) {
rawX, rawY, rawZ := accel.Acceleration()
// Adjust accelerometer to match standard axes.
x = -rawY
y = -rawX
z = -rawZ
return
}
func (s allSensors) Steps() (steps uint32) {
return accel.Steps()
}
func (s allSensors) Temperature() int32 {
return accel.Temperature()
}
type Vibro struct{}
func (Vibro) High() {
vibrationPin.High()
}
func (Vibro) Low() {
vibrationPin.Low()
}

206
board-pybadge.go Normal file
View file

@ -0,0 +1,206 @@
//go:build pybadge
package board
import (
"machine"
"math/bits"
"time"
"tinygo.org/x/drivers"
"tinygo.org/x/drivers/lis3dh"
"tinygo.org/x/drivers/pixel"
"tinygo.org/x/drivers/shifter"
"tinygo.org/x/drivers/st7735"
"tinygo.org/x/drivers/ws2812"
)
const (
Name = "pybadge"
)
var (
Power = mainBattery{}
Sensors = &allSensors{}
Display = mainDisplay{}
Buttons = &buttonsConfig{}
)
func init() {
AddressableLEDs = &ws2812LEDs{}
}
type mainBattery struct {
}
func (b mainBattery) Configure() {
machine.InitADC()
machine.ADC{Pin: machine.A6}.Configure(machine.ADCConfig{
Samples: 4, // 4 seems to be good enough
})
}
func (b mainBattery) Status() (ChargeState, uint32, int8) {
rawValue := machine.ADC{Pin: machine.A6}.Get()
// Formula to calculate microvolts:
// rawValue * 6600_000 / 0x10000
// Simlified, to fit in 32-bit integers:
// rawValue * 51562 / 512
microvolts := uint32(rawValue) * 51562 / 512
return UnknownBattery, microvolts, lithumBatteryApproximation.approximate(microvolts)
}
type allSensors struct {
baseSensors
accelX, accelY, accelZ int32
}
var accel lis3dh.Device
func (s *allSensors) Configure(which drivers.Measurement) error {
if which&drivers.Acceleration != 0 {
machine.I2C0.Configure(machine.I2CConfig{
Frequency: 400 * machine.KHz,
SCL: machine.SCL_PIN,
SDA: machine.SDA_PIN,
})
accel = lis3dh.New(machine.I2C0)
accel.Configure()
}
return nil
}
func (s *allSensors) Update(which drivers.Measurement) error {
// TODO:
// - read temperature from LIS3DH
// - read brightness value
if which&drivers.Acceleration != 0 {
var err error
s.accelX, s.accelY, s.accelZ, err = accel.ReadAcceleration()
if err != nil {
return err
}
}
return nil
}
func (s *allSensors) Acceleration() (x, y, z int32) {
// Adjust accelerometer to match standard axes.
x = -s.accelX
y = s.accelY
z = -s.accelZ
return
}
type mainDisplay struct{}
func (d mainDisplay) PPI() int {
return 116 // 160px / (35.04mm / 25.4)
}
func (d mainDisplay) Configure() Displayer[pixel.RGB565BE] {
machine.SPI1.Configure(machine.SPIConfig{
SCK: machine.SPI1_SCK_PIN,
SDO: machine.SPI1_SDO_PIN,
SDI: machine.SPI1_SDI_PIN,
Frequency: 15_000_000, // datasheet for st7735 says 66ns (~15.15MHz) is the max speed
})
display := st7735.New(machine.SPI1, machine.TFT_RST, machine.TFT_DC, machine.TFT_CS, machine.TFT_LITE)
display.Configure(st7735.Config{
Rotation: st7735.ROTATION_90,
})
display.EnableBacklight(false)
return &display
}
func (d mainDisplay) MaxBrightness() int {
return 1
}
func (d mainDisplay) SetBrightness(level int) {
machine.TFT_LITE.Set(level > 0)
}
func (d mainDisplay) WaitForVBlank(defaultInterval time.Duration) {
dummyWaitForVBlank(defaultInterval)
}
func (d mainDisplay) ConfigureTouch() TouchInput {
return noTouch{}
}
type buttonsConfig struct {
shifter.Device
lastState, currentState uint8
}
func (b *buttonsConfig) Configure() {
b.Device = shifter.NewButtons()
b.Device.Configure()
}
func (b *buttonsConfig) ReadInput() {
b.currentState, _ = b.Device.ReadInput()
}
var codes = [8]Key{
KeyLeft,
KeyUp,
KeyDown,
KeyRight,
KeySelect,
KeyStart,
KeyA,
KeyB,
}
func (b *buttonsConfig) NextEvent() KeyEvent {
// The xor between the previous state and the current state is the buttons
// that changed.
change := b.currentState ^ b.lastState
if change == 0 {
return NoKeyEvent
}
// Find the index of the button with the lowest index that changed state.
index := bits.TrailingZeros32(uint32(change))
e := KeyEvent(codes[index])
if b.currentState&(1<<index) == 0 {
// The button state change was from 1 to 0, so it was released.
e |= keyReleased
}
// This button event was read, so mark it as such.
// By toggling the bit, the bit will be set to the value that is currently
// in currentState.
b.lastState ^= (1 << index)
return e
}
type ws2812LEDs struct {
data [5]colorGRB
}
func (l *ws2812LEDs) Configure() {
machine.WS2812.Configure(machine.PinConfig{Mode: machine.PinOutput})
}
func (l *ws2812LEDs) Len() int {
return len(l.data)
}
func (l *ws2812LEDs) SetRGB(i int, r, g, b uint8) {
l.data[i] = colorGRB{
R: r,
G: g,
B: b,
}
}
// Send pixel data to the LEDs.
func (l *ws2812LEDs) Update() {
ws := ws2812.Device{Pin: machine.WS2812}
ws.Write(pixelsToBytes(l.data[:]))
}

257
board-pyportal.go Normal file
View file

@ -0,0 +1,257 @@
//go:build pyportal
package board
import (
"machine"
"time"
"tinygo.org/x/drivers"
"tinygo.org/x/drivers/ili9341"
"tinygo.org/x/drivers/pixel"
"tinygo.org/x/drivers/touch/resistive"
)
const (
Name = "pyportal"
)
var (
Power = dummyBattery{state: NoBattery}
Sensors = baseSensors{} // TODO: light, temperature
Display = mainDisplay{}
Buttons = noButtons{}
)
type mainDisplay struct{}
var display *ili9341.Device
func (d mainDisplay) Configure() Displayer[pixel.RGB565BE] {
// Initialize backlight and disable at startup.
backlight := machine.TFT_BACKLIGHT
backlight.Configure(machine.PinConfig{Mode: machine.PinOutput})
backlight.Low()
// Enable and configure display.
display = ili9341.NewParallel(
machine.LCD_DATA0,
machine.TFT_WR,
machine.TFT_DC,
machine.TFT_CS,
machine.TFT_RESET,
machine.TFT_RD,
)
display.Configure(ili9341.Config{
Rotation: ili9341.Rotation270,
})
// Enable the TE ("tearing effect") pin to read vblank status.
te := machine.TFT_TE
te.Configure(machine.PinConfig{Mode: machine.PinInput})
display.EnableTEOutput(true)
return display
}
func (d mainDisplay) MaxBrightness() int {
return 1
}
func (d mainDisplay) SetBrightness(level int) {
machine.TFT_BACKLIGHT.Set(level > 0)
}
func (d mainDisplay) WaitForVBlank(defaultInterval time.Duration) {
// Wait until the display has finished updating.
// TODO: wait for a pin interrupt instead of blocking.
for machine.TFT_TE.Get() == true {
}
for machine.TFT_TE.Get() == false {
}
}
func (d mainDisplay) PPI() int {
return 166 // appears to be the same size/resolution as the Gopher Badge and the MCH2022 badge
}
// Configure the resistive touch input on this display.
func (d mainDisplay) ConfigureTouch() TouchInput {
machine.InitADC()
resistiveTouch.Configure(&resistive.FourWireConfig{
YP: machine.TOUCH_YD,
YM: machine.TOUCH_YU,
XP: machine.TOUCH_XR,
XM: machine.TOUCH_XL,
})
return touchInput{}
}
var resistiveTouch resistive.FourWire
var touchPoints [1]TouchPoint
type touchInput struct{}
var touchID uint32
// State associated with the touch input.
var (
medianFilterX, medianFilterY medianFilter
iirFilterX, iirFilterY iirFilter
lastPosX, lastPosY int
)
func (input touchInput) ReadTouch() []TouchPoint {
// Values calibrated on the PyPortal I have. Other boards might have
// slightly different values.
// TODO: make this configurable?
const (
xmin = 54000
xmax = 16000
ymin = 48000
ymax = 22000
)
point := resistiveTouch.ReadTouchPoint()
if point.Z > 8192 {
medianFilterX.add(point.X)
medianFilterY.add(point.Y)
var posX, posY int
if touchPoints[0].ID == 0 {
// First touch on the touch screen.
touchID++
touchPoints[0].ID = touchID
for i := 0; i < 4; i++ {
// Initialize the median filter at this point with some more
// samples, so that the entire median filter is filled.
point := resistiveTouch.ReadTouchPoint()
medianFilterX.add(point.X)
medianFilterY.add(point.Y)
}
// Reset the IIR filter, and use the position as-is.
iirFilterX.add(medianFilterX.value(), true)
iirFilterY.add(medianFilterY.value(), true)
posX = iirFilterX.value()
posY = iirFilterY.value()
} else {
// New touch value while we were touching before.
// Add the value to the IIR filter.
iirFilterX.add(medianFilterX.value(), false)
iirFilterY.add(medianFilterY.value(), false)
// Use some hysteresis to avoid moving the point when it didn't
// actually move.
posX = lastPosX
posY = lastPosY
const diff = 400 // arbitrary value that appears to work well
if iirFilterX.value() > lastPosX+diff {
posX = iirFilterX.value() - diff
}
if iirFilterX.value() < lastPosX-diff {
posX = iirFilterX.value() + diff
}
if iirFilterY.value() > lastPosY+diff {
posY = iirFilterY.value() - diff
}
if iirFilterY.value() < lastPosY-diff {
posY = iirFilterY.value() + diff
}
}
lastPosX = posX
lastPosY = posY
x := int16(clamp(posX, ymin, ymax, 0, 239))
y := int16(clamp(posY, xmin, xmax, 0, 319))
if display != nil {
// Adjust for screen rotation.
switch display.Rotation() {
case drivers.Rotation90:
x, y = y, 239-x
case drivers.Rotation180:
x = 239 - x
y = 319 - y
case drivers.Rotation270:
x, y = 319-y, x
}
}
touchPoints[0].Y = y
touchPoints[0].X = x
return touchPoints[:1]
} else {
touchPoints[0].ID = 0
}
return nil
}
// Map and clamp an input value to an output range.
func clamp(value, lowIn, highIn, lowOut, highOut int) int {
rangeIn := highIn - lowIn
rangeOut := highOut - lowOut
valueOut := (value - lowIn) * rangeOut / rangeIn
if valueOut > highOut {
valueOut = highOut
}
if valueOut < lowOut {
valueOut = lowOut
}
return valueOut
}
// Touch screen filtering has been implemented using the description in this
// article:
// https://dlbeer.co.nz/articles/tsf.html
// It works a lot better than the rather naive algorithm I implemented before.
type medianFilter [5]int
func (f *medianFilter) add(n int) {
// Shift the value into the array.
f[0] = f[1]
f[1] = f[2]
f[2] = f[3]
f[3] = f[4]
f[4] = n
}
func (f *medianFilter) value() int {
// Optimal sorting algorithm.
// It is based on the sorting algorithm described here:
// https://bertdobbelaere.github.io/sorting_networks.html
sorted := *f
compareSwap := func(a, b *int) {
if *a > *b {
*b, *a = *a, *b
}
}
compareSwap(&sorted[1], &sorted[4])
compareSwap(&sorted[0], &sorted[3])
compareSwap(&sorted[1], &sorted[3])
compareSwap(&sorted[0], &sorted[2])
compareSwap(&sorted[2], &sorted[4])
compareSwap(&sorted[0], &sorted[1])
compareSwap(&sorted[1], &sorted[2])
compareSwap(&sorted[3], &sorted[4])
compareSwap(&sorted[2], &sorted[3])
// Return the median value.
return sorted[2]
}
// Infinite impulse response filter, to smooth the input values somewhat.
type iirFilter struct {
state int
}
func (f *iirFilter) add(x int, reset bool) {
if reset {
f.state = x
}
// For every update, the new value is half of x and half of the old value,
// added together:
// f.state = f.state*0.5 + x*0.5
f.state = (f.state + x + 1) / 2
}
func (f *iirFilter) value() int {
return f.state
}

493
board-simulator.go Normal file
View file

@ -0,0 +1,493 @@
//go:build !baremetal
package board
// The generic board exists for testing locally without running on real
// hardware. This avoids potentially long edit-flash-test cycles.
import (
"bufio"
"errors"
"fmt"
"io"
"math/rand"
"os"
"os/exec"
"strings"
"sync"
"time"
"tinygo.org/x/drivers"
"tinygo.org/x/drivers/pixel"
)
const (
// The board name, as passed to TinyGo in the "-target" flag.
// This is the special name "simulator" for the simulator.
Name = "simulator"
)
// List of all devices.
//
// Support varies by board, but all boards have the following peripherals
// defined.
var (
Power = simulatedPower{}
Sensors = &simulatedSensors{}
Display = mainDisplay{}
Buttons = buttonsConfig{}
)
func init() {
AddressableLEDs = &simulatedLEDs{}
}
type simulatedPower struct{}
// Configure the battery status reader. This must be called before calling
// Status.
func (p simulatedPower) Configure() {
// Nothing to do here.
}
// Status returns the current charge status (charging, discharging) and the
// current voltage of the battery in microvolts. If the voltage is 0 it means
// there is no battery present, any other value means a value was read from the
// battery (but there may or may not be a battery attached).
//
// The percent is a rough approximation of the state of charge of the battery.
// The value -1 means the state of charge is unknown.
// It is often inaccurate while charging. It may be best to just show "charging"
// instead of a specific percentage.
func (p simulatedPower) Status() (state ChargeState, microvolts uint32, percent int8) {
// Pretend we're running on battery power and the battery is at 3.7V
// (typical lipo voltage).
actualMicrovolts := uint32(3700_000)
// Randomize the output a bit to fake ADC noise (programs should be able to
// deal with that).
microvolts = actualMicrovolts + rand.Uint32()%16384 - 8192
// Use a stable percent though, otherwise BLE battery level notifications
// will fluctuate way too much.
percent = lithumBatteryApproximation.approximate(actualMicrovolts)
return Discharging, microvolts, percent
}
type mainDisplay struct{}
type fyneScreen struct {
width int
height int
keyevents []KeyEvent
keyeventsLock sync.Mutex
touchID uint32
touches [1]TouchPoint
touchesLock sync.Mutex
}
var screen = &fyneScreen{}
// Configure returns a new display ready to draw on.
//
// Boards without a display will return nil.
func (d mainDisplay) Configure() Displayer[pixel.RGB888] {
startWindow()
screen.width = Simulator.WindowWidth
screen.height = Simulator.WindowHeight
windowSendCommand(fmt.Sprintf("display %d %d", screen.width, screen.height), nil)
return screen
}
// MaxBrightness returns the maximum brightness value. A maximum brightness
// value of 0 means that this display doesn't support changing the brightness.
func (d mainDisplay) MaxBrightness() int {
return 1
}
// SetBrightness sets brightness level of the display. It should be:
//
// 0 ≤ level ≤ MaxBrightness
//
// A value of 0 turns the backlight off entirely (but may leave the display
// running with nothing visible).
func (d mainDisplay) SetBrightness(level int) {
// Send the current and max brightness levels.
windowSendCommand(fmt.Sprintf("display-brightness %d %d", level, 1), nil)
}
// Wait until the next vertical blanking interval (vblank) interrupt is
// received. If the vblank interrupt is not available, it waits until the time
// since the previous call to WaitForVBlank is the default interval instead.
//
// The vertical blanking interval is the time between two screen refreshes. The
// vblank interrupt happens at the start of this interval, and indicates the
// period where the framebuffer is not being touched and can be updated without
// tearing.
//
// Don't use this method for timing, because vblank varies by hardware. Instead,
// use time.Now() to determine the current time and the amount of time since the
// last screen refresh.
//
// TODO: this is not a great API (it's blocking), it may change in the future.
func (d mainDisplay) WaitForVBlank(defaultInterval time.Duration) {
// I'm sure there is some SDL2 API we could use here, but I couldn't find
// one easily so just emulate it.
dummyWaitForVBlank(defaultInterval)
}
// Pixels per inch for this display.
func (d mainDisplay) PPI() int {
return Simulator.WindowPPI
}
func (d mainDisplay) ConfigureTouch() TouchInput {
startWindow()
return sdltouch{}
}
func (s *fyneScreen) Display() error {
// Nothing to do here.
return nil
}
func (s *fyneScreen) DrawBitmap(x, y int16, image pixel.Image[pixel.RGB888]) error {
displayWidth, displayHeight := s.Size()
width, height := image.Size()
if x < 0 || y < 0 || width <= 0 || height <= 0 ||
int(x)+width > int(displayWidth) || int(y)+height > int(displayHeight) {
return errors.New("board: drawing out of bounds")
}
buf := image.RawBuffer()
drawStart := time.Now()
lastUpdate := drawStart
for bufy := 0; bufy < int(height); bufy++ {
// Delay drawing a bit, to simulate a slow SPI bus.
if Simulator.WindowDrawSpeed != 0 {
now := time.Now()
expected := drawStart.Add(Simulator.WindowDrawSpeed * time.Duration(bufy*int(width)))
delay := expected.Sub(now)
if delay > 0 {
time.Sleep(delay)
now = time.Now()
}
if now.Sub(lastUpdate) > 5*time.Millisecond {
lastUpdate = now
}
}
index := (bufy * int(width)) * 3
lineBuf := buf[index : index+int(width)*3]
windowSendCommand(fmt.Sprintf("draw %d %d %d", x, int(y)+bufy, width), lineBuf)
}
return nil
}
func (s *fyneScreen) Size() (width, height int16) {
return int16(s.width), int16(s.height)
}
// Set sleep mode for this screen.
func (s *fyneScreen) Sleep(sleepEnabled bool) error {
// This is a no-op.
// TODO: use a different gray than when the backlight is set to zero, to
// indicate sleep mode.
return nil
}
var errNoRotation = errors.New("error: SetRotation isn't supported")
func (s *fyneScreen) Rotation() drivers.Rotation {
return drivers.Rotation0
}
func (s *fyneScreen) SetRotation(rotation drivers.Rotation) error {
// TODO: implement this, to be able to test rotation support.
return errNoRotation
}
func (s *fyneScreen) SetScrollArea(topFixedArea, bottomFixedArea int16) {
windowSendCommand(fmt.Sprintf("scroll-start %d %d", topFixedArea, bottomFixedArea), nil)
}
func (s *fyneScreen) SetScroll(line int16) {
windowSendCommand(fmt.Sprintf("scroll %d", line), nil)
}
func (s *fyneScreen) StopScroll() {
windowSendCommand(fmt.Sprintf("scroll-stop"), nil)
}
type sdltouch struct{}
func (s sdltouch) ReadTouch() []TouchPoint {
screen.touchesLock.Lock()
defer screen.touchesLock.Unlock()
if screen.touches[0].ID != 0 {
return screen.touches[:1]
}
return nil
}
type buttonsConfig struct{}
func (b buttonsConfig) Configure() {
}
func (b buttonsConfig) ReadInput() {
}
func (b buttonsConfig) NextEvent() KeyEvent {
screen.keyeventsLock.Lock()
defer screen.keyeventsLock.Unlock()
if len(screen.keyevents) != 0 {
event := screen.keyevents[0]
copy(screen.keyevents, screen.keyevents[1:])
screen.keyevents = screen.keyevents[:len(screen.keyevents)-1]
return event
}
return NoKeyEvent
}
type simulatedSensors struct {
configured drivers.Measurement
lock sync.Mutex
accelSource [3]float64
stepsSource uint32
accel [3]int32
steps uint32
temp int32
}
// Configure configures all sensors as specified in the which parameter.
// If there is an error, none of the sensors can be relied upon to work.
func (s *simulatedSensors) Configure(which drivers.Measurement) error {
s.configured = which
return nil
}
// Update updates the sensor values as given in the which parameter.
// All sensors in the which parameter must have been configured before, or the
// behavior may be unpredictable.
func (s *simulatedSensors) Update(which drivers.Measurement) error {
if which != s.configured&which {
// This is a bug. Don't check it on each board, but do check it in the
// simulator.
panic("asked to update sensors that weren't configured")
}
if which&drivers.Acceleration != 0 {
s.lock.Lock()
// Add some noise to the accelerometer to make the values more
// realistic.
s.accel[0] = rand.Int31n(30_000) - 15_000 + int32(s.accelSource[0]*1000_000) // x
s.accel[1] = rand.Int31n(30_000) - 15_000 + int32(s.accelSource[1]*1000_000) // y
s.accel[2] = rand.Int31n(30_000) - 15_000 + int32(s.accelSource[2]*1000_000) // z
s.steps = s.stepsSource
s.lock.Unlock()
}
if which&drivers.Temperature != 0 {
// Temperature around 20°C (with some jitter thrown in for a good
// simulation).
s.temp = 20000 + rand.Int31n(200) - 100
}
return nil
}
// Acceleration returns the last read acceleration in µg (micro-gravity). This
// includes gravity: when one of the axes is pointing straight to Earth and the
// sensor is not moving the returned value will be around 1000000 or -1000000.
//
// The accelerometer values match those used on Android. When the device is
// lying flat on a table, the Z axis is around 1g. When the device is rotated
// 90° upright, the Y axis is around 1g. When the device is then rotated 90° to
// the left (counter-clockwise), the X axis is around 1g.
//
// The simulator returns values as if the device is held upright like you'd hold
// a phone while taking a selfie.
func (s *simulatedSensors) Acceleration() (x, y, z int32) {
return s.accel[0], s.accel[1], s.accel[2]
}
// Steps returns the number of steps since the step counter started.
// The uint32 value is assumed to be large enough for all practical use cases.
//
// The value can be incremented from the simulator.
func (s *simulatedSensors) Steps() (steps uint32) {
return s.steps
}
// Temperature returns the temperature that was last read from the sensor.
// If there are multiple temperature sensors on a given board, the most accurate
// result will be returned.
//
// The simulator returns a fixed temperature, with some jitter to make it look
// more like a real-world sensor (no sensor is without noise).
func (s *simulatedSensors) Temperature() int32 {
return s.temp
}
type simulatedLEDs struct {
data []byte
}
// Initialize the addressable LEDs.
//
// The way to determine whether there are addressable LEDs on a given board, is
// to configure them and then check the length of board.AddressableLEDs.Data.
func (l *simulatedLEDs) Configure() {
startWindow()
l.data = make([]byte, Simulator.AddressableLEDs*3)
l.Update()
}
func (l *simulatedLEDs) Len() int {
return len(l.data) / 3
}
func (l *simulatedLEDs) SetRGB(i int, r, g, b uint8) {
l.data[i*3+0] = r
l.data[i*3+1] = g
l.data[i*3+2] = b
}
// Update the LEDs with the color data.
func (l *simulatedLEDs) Update() {
cmd := fmt.Sprintf("addressable-leds %d", l.Len())
windowSendCommand(cmd, l.data)
}
var (
fyneStart sync.Once
windowLock sync.Mutex
windowStdin io.WriteCloser
windowStdout io.ReadCloser
)
// Ensure the window is running in a separate process, starting it if necessary.
func startWindow() {
// Create a main loop for Fyne.
windowRunning := make(chan struct{})
fyneStart.Do(func() {
// Start the separate process that manages the window.
go func() {
cmd := exec.Command(os.Args[0], runWindowCommand)
cmd.Stderr = os.Stderr
windowStdin, _ = cmd.StdinPipe()
windowStdout, _ = cmd.StdoutPipe()
err := cmd.Start()
if err != nil {
fmt.Fprintln(os.Stdout, "could not start window process:", err)
os.Exit(1)
}
close(windowRunning)
err = cmd.Wait()
if err != nil {
if exitErr, ok := err.(*exec.ExitError); ok {
os.Exit(exitErr.ExitCode())
}
os.Exit(1)
}
// The window was closed, so exit.
os.Exit(0)
}()
<-windowRunning
// Listen for events (keyboard/touch).
go windowListenEvents()
// Do some initialization.
windowSendCommand("title "+Simulator.WindowTitle, nil)
})
}
// Send a command to the separate process that manages the window.
// The command is a single line (without newline). The data part is optional
// binary data that can be sent with the command. The size of this binary data
// must be part of the textual command.
func windowSendCommand(command string, data []byte) {
windowLock.Lock()
defer windowLock.Unlock()
windowStdin.Write([]byte(command + "\n"))
windowStdin.Write(data)
}
// Goroutine that listens for window events like button and touch (keyboard and
// mouse).
func windowListenEvents() {
r := bufio.NewReader(windowStdout)
for {
line, err := r.ReadString('\n')
if err != nil {
if err == io.EOF {
break
}
fmt.Fprintln(os.Stderr, "failed to read I/O events from child process:", err)
}
cmd := strings.Fields(line)[0]
switch cmd {
case "keypress", "keyrelease":
// Read the key code.
var key KeyEvent
fmt.Sscanf(line, "%s %d", &cmd, &key)
if cmd == "keyrelease" {
key |= keyReleased
}
// Add the key code to the
screen.keyeventsLock.Lock()
screen.keyevents = append(screen.keyevents, key)
screen.keyeventsLock.Unlock()
case "mousedown":
// Read the event.
var x, y int16
fmt.Sscanf(line, "%s %d %d", &cmd, &x, &y)
// Update the touch state.
screen.touchesLock.Lock()
screen.touchID++
screen.touches[0] = TouchPoint{
ID: screen.touchID,
X: x,
Y: y,
}
screen.touchesLock.Unlock()
case "mouseup":
// End the current touch.
screen.touchesLock.Lock()
screen.touches[0] = TouchPoint{} // no active touch
screen.touchesLock.Unlock()
case "mousemove":
// Read the event.
var x, y int16
fmt.Sscanf(line, "%s %d %d", &cmd, &x, &y)
// Update the touch state.
screen.touchesLock.Lock()
if screen.touches[0].ID != 0 {
screen.touches[0].X = x
screen.touches[0].Y = y
}
screen.touchesLock.Unlock()
case "accel":
var x, y, z float64
fmt.Sscanf(line, "%s %f %f %f", &cmd, &x, &y, &z)
Sensors.lock.Lock()
Sensors.accelSource[0] = x
Sensors.accelSource[1] = y
Sensors.accelSource[2] = z
Sensors.lock.Unlock()
case "steps":
var n uint32
fmt.Sscanf(line, "%s %d %d", &cmd, &n)
Sensors.lock.Lock()
Sensors.stepsSource = n
Sensors.lock.Unlock()
default:
fmt.Fprintln(os.Stderr, "unknown command:", cmd)
}
}
}

128
board-thumby.go Normal file
View file

@ -0,0 +1,128 @@
//go:build thumby
package board
import (
"machine"
"math/bits"
"time"
"tinygo.org/x/drivers/pixel"
"tinygo.org/x/drivers/ssd1306"
)
const (
Name = "pybadge"
)
var (
Power = dummyBattery{state: UnknownBattery}
Sensors = baseSensors{}
Display = mainDisplay{}
Buttons = &gpioButtons{}
)
type mainDisplay struct{}
func (d mainDisplay) PPI() int {
return 192 // 72px wide display / 3/8 of an inch wide display
}
func (d mainDisplay) Configure() Displayer[pixel.Monochrome] {
machine.SPI0.Configure(machine.SPIConfig{})
display := ssd1306.NewSPI(machine.SPI0, machine.THUMBY_DC_PIN, machine.THUMBY_RESET_PIN, machine.THUMBY_CS_PIN)
display.Configure(ssd1306.Config{
Width: 72,
Height: 40,
ResetCol: ssd1306.ResetValue{28, 99},
ResetPage: ssd1306.ResetValue{0, 5},
})
return &display
}
func (d mainDisplay) MaxBrightness() int {
return 1
}
func (d mainDisplay) SetBrightness(level int) {
// Nothing to do here.
}
func (d mainDisplay) WaitForVBlank(defaultInterval time.Duration) {
dummyWaitForVBlank(defaultInterval)
}
func (d mainDisplay) ConfigureTouch() TouchInput {
return noTouch{}
}
type gpioButtons struct {
state uint8
previousState uint8
}
func (b *gpioButtons) Configure() {
machine.THUMBY_BTN_A_PIN.Configure(machine.PinConfig{Mode: machine.PinInput})
machine.THUMBY_BTN_B_PIN.Configure(machine.PinConfig{Mode: machine.PinInput})
machine.THUMBY_BTN_UDPAD_PIN.Configure(machine.PinConfig{Mode: machine.PinInput})
machine.THUMBY_BTN_LDPAD_PIN.Configure(machine.PinConfig{Mode: machine.PinInput})
machine.THUMBY_BTN_DDPAD_PIN.Configure(machine.PinConfig{Mode: machine.PinInput})
machine.THUMBY_BTN_RDPAD_PIN.Configure(machine.PinConfig{Mode: machine.PinInput})
}
func (b *gpioButtons) ReadInput() {
state := uint8(0)
if !machine.THUMBY_BTN_A_PIN.Get() {
state |= 1
}
if !machine.THUMBY_BTN_B_PIN.Get() {
state |= 2
}
if !machine.THUMBY_BTN_UDPAD_PIN.Get() {
state |= 4
}
if !machine.THUMBY_BTN_LDPAD_PIN.Get() {
state |= 8
}
if !machine.THUMBY_BTN_DDPAD_PIN.Get() {
state |= 16
}
if !machine.THUMBY_BTN_RDPAD_PIN.Get() {
state |= 32
}
b.state = state
}
var codes = [8]Key{
KeyA,
KeyB,
KeyUp,
KeyLeft,
KeyDown,
KeyRight,
}
func (b *gpioButtons) NextEvent() KeyEvent {
// The xor between the previous state and the current state is the buttons
// that changed.
change := b.state ^ b.previousState
if change == 0 {
return NoKeyEvent
}
// Find the index of the button with the lowest index that changed state.
index := bits.TrailingZeros32(uint32(change))
e := KeyEvent(codes[index])
if b.state&(1<<index) == 0 {
// The button state change was from 1 to 0, so it was released.
e |= keyReleased
}
// This button event was read, so mark it as such.
// By toggling the bit, the bit will be set to the value that is currently
// in b.state.
b.previousState ^= (1 << index)
return e
}

327
common.go Normal file
View file

@ -0,0 +1,327 @@
package board
import (
"time"
"unsafe"
"tinygo.org/x/drivers"
"tinygo.org/x/drivers/pixel"
)
var (
AddressableLEDs LEDArray = dummyAddressableLEDs{}
)
// Settings for the simulator. These can be modified at any time, but it is
// recommended to modify them before configuring any of the board peripherals.
//
// These can be modified to match whatever board your main target is. For
// example, if your board has a display that's only 160 by 128 pixels, you can
// modify the window size here to get a realistic simulation.
var Simulator = struct {
WindowTitle string
// Width and height in virtual pixels (matching Size()). The window will
// take up more physical pixels on high-DPI screens.
WindowWidth int
WindowHeight int
// Pixels per inch. The default is 120, which matches many commonly used
// high-DPI screens (for example, Apple screens).
WindowPPI int
// How much time it takes (in nanoseconds) to draw a single pixel.
// For example, for 8MHz and 16 bits per color:
// time.Second * 16 / 8e6
WindowDrawSpeed time.Duration
// Number of addressable LEDs used by default.
AddressableLEDs int
}{
WindowTitle: "Simulator",
WindowWidth: 240,
WindowHeight: 240,
WindowPPI: 120, // common on many modern displays (for example Retina is 254 / 2 = 127)
// This matches common event badges like the PyBadge and the MCH2022 badge
// (but not the SHA2017 badge which uses 6 RGBW LEDs).
AddressableLEDs: 5,
}
// ChargeState is the charging status of a battery.
type ChargeState uint8
const (
// A battery might be attached, this is unknown (no way to know by reading a
// pin).
UnknownBattery ChargeState = iota
// This board doesn't have batteries.
NoBattery
// No battery is attached to the board, but there could be one.
BatteryUnavailable
// There is a battery attached and it's charging (usually from USB)
Charging
// Power is present, but the battery is not charging (usually when it is
// fully charged).
NotCharging
// There is a battery attached and it's not charging (no power connected).
Discharging
)
// Return a string representation of the charge status, mainly for debugging.
func (c ChargeState) String() string {
switch c {
default:
return "unknown"
case NoBattery:
return "none"
case BatteryUnavailable:
return "not connected"
case Charging:
return "charging"
case NotCharging:
return "not charging"
case Discharging:
return "discharging"
}
}
// A LED array is a sequence of individually addressable LEDs (like WS2812).
type LEDArray interface {
// Configure the LED array. This needs to be called before any other method
// (except Len).
Configure()
// Return the length of the LED array.
Len() int
// Set a given pixel to the RGB value. The index must be in bounds,
// otherwise this method will panic. The value is not immediately visible,
// call Update() to update the pixel array.
// Note that LED arrays are usually indexed from the end, because of the way
// data is sent to them.
SetRGB(index int, r, g, b uint8)
// Update the pixel array to the values previously set in SetRGB.
Update()
}
// The display interface shared by all supported displays.
type Displayer[T pixel.Color] interface {
// The display size in pixels.
Size() (width, height int16)
// DrawBitmap copies the bitmap to the internal buffer on the screen at the
// given coordinates. It returns once the image data has been sent
// completely.
DrawBitmap(x, y int16, buf pixel.Image[T]) error
// Display the written image on screen. This call may or may not be
// necessary depending on the screen, but it's better to call it anyway.
Display() error
// Enter or exit sleep mode.
Sleep(sleepEnabled bool) error
// Return the current screen rotation.
// Note that some screens are by default configured with rotation, so by
// default you may not get drivers.Rotation0.
Rotation() drivers.Rotation
// Set a given rotation. For example, to rotate by 180° you can use:
//
// SetRotation((Rotation() + 2) % 4)
//
// Not all displays support rotation, in which case they will return an
// error.
SetRotation(drivers.Rotation) error
}
// TouchInput reads the touch screen (resistive/capacitive) on a display and
// returns the current list of touch points.
type TouchInput interface {
ReadTouch() []TouchPoint
}
// A single touch point on the screen, from a finger, stylus, or something like
// that.
type TouchPoint struct {
// ID for this touch point. New touch events get a monotonically
// incrementing ID. Because it is a uint32 (and it's unlikely a screen will
// be touched more than 4 billion times), it can be treated as a unique ID.
ID uint32
// X and Y pixel coordinates.
X, Y int16
}
// Key is a single keyboard key (not to be confused with a single character).
type Key uint8
// List of all supported key codes.
const (
NoKey = iota
// Special keys.
KeyEscape
// Navigation keys.
KeyLeft
KeyRight
KeyUp
KeyDown
// Character keys.
KeyEnter
KeySpace
KeyA
KeyB
KeyL
KeyR
// Special keys, used on some boards.
KeySelect
KeyStart
)
// KeyEvent is a single key press or release event.
type KeyEvent uint16
const (
NoKeyEvent KeyEvent = iota // No key event was available.
keyReleased = KeyEvent(1 << 15) // The upper bit is set when this is a release event
)
// Key returns the key code for this key event.
func (k KeyEvent) Key() Key {
return Key(k) // lower 8 bits are the key code
}
// Pressed returns whether this event indicates a key press event. It returns
// true for a press, false for a release.
func (k KeyEvent) Pressed() bool {
return k&keyReleased == 0
}
// Default lithium battery charge curve.
// This data is taken from the InfiniTime project:
// https://github.com/InfiniTimeOrg/InfiniTime/pull/1444
// It is unlikely to be very accurate for other batteries, but it's a reasonable
// approximation if no specific discharge curve has been made.
var lithumBatteryApproximation = batteryApproximation{
voltages: [6]uint16{3500, 3600, 3700, 3750, 3900, 4180},
percents: [6]int8{0, 10, 25, 50, 75, 100},
}
type batteryApproximation struct {
voltages [6]uint16
percents [6]int8
}
func (approx *batteryApproximation) approximate(microvolts uint32) int8 {
if microvolts <= uint32(approx.voltages[0])*1000 {
return 0 // below the lowest value
}
for i, v := range approx.voltages {
if uint32(v)*1000 > microvolts {
voltStart := uint32(approx.voltages[i-1]) * 1000
voltEnd := uint32(v) * 1000
percentStart := approx.percents[i-1]
percentEnd := approx.percents[i]
voltOffset := microvolts - voltStart
voltDiff := voltEnd - voltStart
percentDiff := percentEnd - percentStart
percentOffset := voltOffset * uint32(percentDiff) / uint32(voltDiff)
return int8(percentOffset + uint32(percentStart))
}
}
// Outside the table, so must be 100%.
return 100
}
func (approx *batteryApproximation) approximatePPM(microvolts uint32) int32 {
if microvolts <= uint32(approx.voltages[0])*1000 {
return 0 // below the lowest value
}
for i, v := range approx.voltages {
if uint32(v)*1000 > microvolts {
voltStart := uint32(approx.voltages[i-1]) // mV
voltEnd := uint32(v) // mV
percentStart := approx.percents[i-1]
percentEnd := approx.percents[i]
voltOffset := microvolts - voltStart*1000 // µV
voltDiff := voltEnd - voltStart // mV
percentDiff := percentEnd - percentStart
percentOffset := voltOffset * uint32(percentDiff) * 10 / voltDiff
return int32(percentStart)*10000 + int32(percentOffset)
}
}
// Outside the table, so must be 100%.
return 1000_000
}
type dummyAddressableLEDs struct {
}
func (l dummyAddressableLEDs) Configure() {
// Nothing to do here.
}
func (l dummyAddressableLEDs) Len() int {
return 0 // always zero
}
func (l dummyAddressableLEDs) SetRGB(i int, r, g, b uint8) {
panic("no LEDs on this board")
}
func (l dummyAddressableLEDs) Update() {
// Nothing to do here.
}
type colorFormat interface {
colorGRB
}
type colorGRB struct{ G, R, B uint8 }
// Convert pixel data to a byte slice, for sending it to WS2812 LEDs for
// example.
func pixelsToBytes[T colorFormat](pix []T) []byte {
if len(pix) == 0 {
return nil
}
var zeroColor T
ptr := unsafe.Pointer(unsafe.SliceData(pix))
return unsafe.Slice((*byte)(ptr), len(pix)*int(unsafe.Sizeof(zeroColor)))
}
// Dummy sensor value, to be embedded in actual drivers.Sensor implementations.
type baseSensors struct {
}
func (s baseSensors) Configure(which drivers.Measurement) error {
return nil
}
func (s baseSensors) Update(which drivers.Measurement) error {
return nil
}
func (s baseSensors) Acceleration() (x, y, z int32) {
return 0, 0, 0
}
func (s baseSensors) Steps() uint32 {
return 0
}
func (s baseSensors) Temperature() int32 {
return 0
}

29
common_test.go Normal file
View file

@ -0,0 +1,29 @@
package board
import "testing"
func TestBatteryApprox(t *testing.T) {
for _, tc := range []struct {
microvolts uint32
percent int8
}{
{2900_000, 0},
{3400_000, 0},
{3500_000, 0},
{3510_000, 1},
{3528_000, 2}, // the value is rounded down (this is more like 2.8%)
{3730_000, 40}, // guess, probably higher
{3749_999, 49}, // rounded down
{3750_000, 50},
{3750_001, 50},
{4179_999, 99}, // rounded down
{4180_000, 100}, // exactly at 100%
{4180_001, 100}, // higher values get rounded down
{5000_000, 100}, // unlikely high voltage, still 100%
} {
percent := lithumBatteryApproximation.approximate(tc.microvolts)
if percent != tc.percent {
t.Errorf("for %.3fV, expected %d%% but got %d%%", float64(tc.microvolts)/1e6, tc.percent, percent)
}
}
}

57
dummy.go Normal file
View file

@ -0,0 +1,57 @@
package board
import "time"
// This file contains dummy devices, for devices which don't support a
// particular kind of device.
// Dummy button input that doesn't actually read any inputs.
// Used for boards that don't have any buttons.
type noButtons struct{}
func (b noButtons) Configure() {
}
func (b noButtons) ReadInput() {
}
func (b noButtons) NextEvent() KeyEvent {
return NoKeyEvent
}
// Dummy touch object that doesn't read any input.
// Used for displays without touch capabilities.
type noTouch struct{}
func (t noTouch) ReadTouch() []TouchPoint {
return nil
}
var lastWaitForVBlank time.Time
// Utility function for all those boards that don't support vblank.
func dummyWaitForVBlank(defaultInterval time.Duration) {
waitUntil := lastWaitForVBlank.Add(defaultInterval)
now := time.Now()
duration := waitUntil.Sub(now)
if duration < 0 {
lastWaitForVBlank = now
return
}
time.Sleep(duration)
lastWaitForVBlank = waitUntil
}
// Dummy implementation of the Power value, for devices with no battery or where
// the battery status cannot be read.
type dummyBattery struct {
state ChargeState
}
func (b dummyBattery) Configure() {
// nothing to do here
}
func (b dummyBattery) Status() (ChargeState, uint32, int8) {
return b.state, 0, -1
}

39
go.mod Normal file
View file

@ -0,0 +1,39 @@
module gitrepo.ru/neonxp/board
go 1.20
require (
fyne.io/fyne/v2 v2.3.4
golang.org/x/image v0.3.0
tinygo.org/x/drivers v0.27.1-0.20240525063452-831982ad33ee
)
require (
fyne.io/systray v1.10.1-0.20230403195833-7dc3c09283d6 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/fredbi/uri v0.1.0 // indirect
github.com/fsnotify/fsnotify v1.5.4 // indirect
github.com/fyne-io/gl-js v0.0.0-20220119005834-d2da28d9ccfe // indirect
github.com/fyne-io/glfw-js v0.0.0-20220120001248-ee7290d23504 // indirect
github.com/fyne-io/image v0.0.0-20220602074514-4956b0afb3d2 // indirect
github.com/go-gl/gl v0.0.0-20211210172815-726fda9656d6 // indirect
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20221017161538-93cebf72946b // indirect
github.com/go-text/typesetting v0.0.0-20230405155246-bf9c697c6e16 // indirect
github.com/godbus/dbus/v5 v5.1.0 // indirect
github.com/goki/freetype v0.0.0-20220119013949-7a161fd3728c // indirect
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
github.com/gopherjs/gopherjs v1.17.2 // indirect
github.com/jsummers/gobmp v0.0.0-20151104160322-e2ba15ffa76e // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/srwiley/oksvg v0.0.0-20220731023508-a61f04f16b76 // indirect
github.com/srwiley/rasterx v0.0.0-20210519020934-456a8d69b780 // indirect
github.com/stretchr/testify v1.8.0 // indirect
github.com/tevino/abool v1.2.0 // indirect
github.com/yuin/goldmark v1.4.13 // indirect
golang.org/x/mobile v0.0.0-20211207041440-4e6c2922fdee // indirect
golang.org/x/net v0.7.0 // indirect
golang.org/x/sys v0.5.0 // indirect
golang.org/x/text v0.7.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
honnef.co/go/js/dom v0.0.0-20210725211120-f030747120f2 // indirect
)

678
go.sum Normal file
View file

@ -0,0 +1,678 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg=
cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8=
cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
fyne.io/fyne/v2 v2.3.4 h1:CL8LBUoct2K3EF7Q7NdcDrDMcb3OrNJTghLYTFF400Q=
fyne.io/fyne/v2 v2.3.4/go.mod h1:X2+NrR+62mvAiAt2fwKT7035zQsE77KVV1NlvWo4vW8=
fyne.io/systray v1.10.1-0.20230403195833-7dc3c09283d6 h1:lHt8dm97Uy9ggtnt9N6XOlsp76wXmRAh3SjReWm1e2Q=
fyne.io/systray v1.10.1-0.20230403195833-7dc3c09283d6/go.mod h1:oM2AQqGJ1AMo4nNqZFYU8xYygSBZkW2hmdJ7n4yjedE=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/akavel/rsrc v0.10.2/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fredbi/uri v0.1.0 h1:8XBBD74STBLcWJ5smjEkKCZivSxSKMhFB0FbQUKeNyM=
github.com/fredbi/uri v0.1.0/go.mod h1:1xC40RnIOGCaQzswaOvrzvG/3M3F0hyDVb3aO/1iGy0=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI=
github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
github.com/fyne-io/gl-js v0.0.0-20220119005834-d2da28d9ccfe h1:A/wiwvQ0CAjPkuJytaD+SsXkPU0asQ+guQEIg1BJGX4=
github.com/fyne-io/gl-js v0.0.0-20220119005834-d2da28d9ccfe/go.mod h1:d4clgH0/GrRwWjRzJJQXxT/h1TyuNSfF/X64zb/3Ggg=
github.com/fyne-io/glfw-js v0.0.0-20220120001248-ee7290d23504 h1:+31CdF/okdokeFNoy9L/2PccG3JFidQT3ev64/r4pYU=
github.com/fyne-io/glfw-js v0.0.0-20220120001248-ee7290d23504/go.mod h1:gLRWYfYnMA9TONeppRSikMdXlHQ97xVsPojddUv3b/E=
github.com/fyne-io/image v0.0.0-20220602074514-4956b0afb3d2 h1:hnLq+55b7Zh7/2IRzWCpiTcAvjv/P8ERF+N7+xXbZhk=
github.com/fyne-io/image v0.0.0-20220602074514-4956b0afb3d2/go.mod h1:eO7W361vmlPOrykIg+Rsh1SZ3tQBaOsfzZhsIOb/Lm0=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-gl/gl v0.0.0-20211210172815-726fda9656d6 h1:zDw5v7qm4yH7N8C8uWd+8Ii9rROdgWxQuGoJ9WDXxfk=
github.com/go-gl/gl v0.0.0-20211210172815-726fda9656d6/go.mod h1:9YTyiznxEY1fVinfM7RvRcjRHbw2xLBJ3AAGIT0I4Nw=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20211213063430-748e38ca8aec/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20221017161538-93cebf72946b h1:GgabKamyOYguHqHjSkDACcgoPIz3w0Dis/zJ1wyHHHU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20221017161538-93cebf72946b/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-text/typesetting v0.0.0-20230405155246-bf9c697c6e16 h1:DvHeDNqK8cxdZ7C6y88pt3uE7euZH7/LluzyfnUfH/Q=
github.com/go-text/typesetting v0.0.0-20230405155246-bf9c697c6e16/go.mod h1:zvWM81wAVW6QfVDI6yxfbCuoLnobSYTuMsrXU/u11y8=
github.com/go-text/typesetting-utils v0.0.0-20230326210548-458646692de6 h1:zAAA1U4ykFwqPbcj6YDxvq3F2g0wc/ngPfLJjkR/8zs=
github.com/go-text/typesetting-utils v0.0.0-20230326210548-458646692de6/go.mod h1:RaqFwjcYyM5BjbYGwON0H5K0UqwO3sJlo9ukKha80ZE=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/goki/freetype v0.0.0-20220119013949-7a161fd3728c h1:JGCm/+tJ9gC6THUxooTldS+CUDsba0qvkvU3DHklqW8=
github.com/goki/freetype v0.0.0-20220119013949-7a161fd3728c/go.mod h1:wfqRWLHRBsRgkp5dmbG56SA0DmVtwrF5N3oPdI8t+Aw=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gopherjs/gopherjs v0.0.0-20211219123610-ec9572f70e60/go.mod h1:cz9oNYuRUWGdHmLF2IodMLkAhcPtXeULvcBNagUrxTI=
github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g=
github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k=
github.com/goxjs/gl v0.0.0-20210104184919-e3fafc6f8f2a/go.mod h1:dy/f2gjY09hwVfIyATps4G2ai7/hLwLkc5TrPqONuXY=
github.com/goxjs/glfw v0.0.0-20191126052801-d2efb5f20838/go.mod h1:oS8P8gVOT4ywTcjV6wZlOU4GuVFQ8F5328KY3MJ79CY=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jackmordaunt/icns/v2 v2.2.1/go.mod h1:6aYIB9eSzyfHHMKqDf17Xrs1zetQPReAkiUSHzdw4cI=
github.com/josephspurrier/goversioninfo v1.4.0/go.mod h1:JWzv5rKQr+MmW+LvM412ToT/IkYDZjaclF2pKDss8IY=
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/jsummers/gobmp v0.0.0-20151104160322-e2ba15ffa76e h1:LvL4XsI70QxOGHed6yhQtAU34Kx3Qq2wwBzGFKY8zKk=
github.com/jsummers/gobmp v0.0.0-20151104160322-e2ba15ffa76e/go.mod h1:kLgvv7o6UM+0QSf0QjAse3wReFDsb9qbZJdfexWlrQw=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/lucor/goinfo v0.0.0-20210802170112-c078a2b0f08b/go.mod h1:PRq09yoB+Q2OJReAmwzKivcYyremnibWGbK7WfftHzc=
github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mcuadros/go-version v0.0.0-20190830083331-035f6764e8d2/go.mod h1:76rfSfYPWj01Z85hUf/ituArm797mNKcvINh1OlsZKo=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
github.com/neelance/sourcemap v0.0.0-20200213170602-2833bce08e4c/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/shurcooL/go v0.0.0-20200502201357-93f07166e636/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk=
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns=
github.com/srwiley/oksvg v0.0.0-20220731023508-a61f04f16b76 h1:Ga2uagHhDeGysCixLAzH0mS2TU+CrbQavmsHUNkEEVA=
github.com/srwiley/oksvg v0.0.0-20220731023508-a61f04f16b76/go.mod h1:cNQ3dwVJtS5Hmnjxy6AgTPd0Inb3pW05ftPSX7NZO7Q=
github.com/srwiley/rasterx v0.0.0-20210519020934-456a8d69b780 h1:oDMiXaTMyBEuZMU53atpxqYsSB3U1CHkeAu2zr6wTeY=
github.com/srwiley/rasterx v0.0.0-20210519020934-456a8d69b780/go.mod h1:mvWM0+15UqyrFKqdRjY6LuAVJR0HOVhJlEgZ5JWtSWU=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/tevino/abool v1.2.0 h1:heAkClL8H6w+mK5md9dzsuohKeXHUpY7Vw0ZCKW+huA=
github.com/tevino/abool v1.2.0/go.mod h1:qc66Pna1RiIsPa7O4Egxxs9OqkuxDX55zznh9K07Tzg=
github.com/urfave/cli/v2 v2.4.0/go.mod h1:NX9W0zmTvedE5oDoOMs2RTC8RvdK98NTYZE5LbaEYPg=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.13 h1:fVcFKWvrslecOb/tg+Cc05dkeYx540o0FuFt3nUVDoE=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.0.0-20211028202545-6944b10bf410/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
golang.org/x/image v0.3.0 h1:HTDXbdK9bjfSWkPzDJIw89W8CAtfFGduujWs33NLLsg=
golang.org/x/image v0.3.0/go.mod h1:fXd9211C/0VTlYuAcOhW8dY/RtEJqODXOWBDpmYBf+A=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mobile v0.0.0-20211207041440-4e6c2922fdee h1:/tShaw8UTf0XzI8DOZwQHzC7d6Vi3EtrBnftiZ4vAvU=
golang.org/x/mobile v0.0.0-20211207041440-4e6c2922fdee/go.mod h1:pe2sM7Uk+2Su1y7u/6Z8KJ24D7lepUjFZbhFOrmDfuQ=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211118161319-6a13c67c3ce4/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.8-0.20211022200916-316ba0b74098/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU=
google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94=
google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/js/dom v0.0.0-20210725211120-f030747120f2 h1:oomkgU6VaQDsV6qZby2uz1Lap0eXmku8+2em3A/l700=
honnef.co/go/js/dom v0.0.0-20210725211120-f030747120f2/go.mod h1:sUMDUKNB2ZcVjt92UnLy3cdGs+wDAcrPdV3JP6sVgA4=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
tinygo.org/x/drivers v0.27.1-0.20240525063452-831982ad33ee h1:VG/EL7qodeEvq1gtKLHnvdHge40bWyj7cWr9qUevO6E=
tinygo.org/x/drivers v0.27.1-0.20240525063452-831982ad33ee/go.mod h1:T6snsUqS0RAxOANxiV81fQwLxDDNmprxTAYzmxoA7J0=

397
simulator.go Normal file
View file

@ -0,0 +1,397 @@
//go:build !baremetal
package board
// The simulator for a generic board. It can simulate various kinds of hardware,
// like:
// * event badges
// * GameBoy-like handhelds (Game Boy Advance, PyBadge)
// * smartwatches
// * boards with only a touchscreen (PyPortal).
// It is currently mostly made for boards with a display, but this is not at all
// a requirement in the API.
//
// The board API doesn't use a mainloop of any kind, which would not be
// necessary anyway on embedded systems. But it is necessary on OSes, so to work
// around this the simulator is actually run in a separate process by starting
// the current process again and communicating over pipes (stdin/stdout in the
// simulator process).
import (
"bufio"
"fmt"
"image"
"image/color"
"io"
"math/rand"
"os"
"strconv"
"strings"
"sync"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/app"
"fyne.io/fyne/v2/canvas"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/driver/desktop"
"fyne.io/fyne/v2/layout"
"fyne.io/fyne/v2/widget"
"golang.org/x/image/draw"
)
const runWindowCommand = "run-simulator-window"
func init() {
if len(os.Args) >= 2 && os.Args[1] == runWindowCommand {
// This is the simulator process.
// Run the entire window in an init function, because that's the only
// way to do this with the API that is exposed by the board package.
windowMain()
os.Exit(0)
}
}
var (
displayImageLock sync.Mutex
displayImage *image.RGBA
displayScrollTopFixed int
displayScrollBottomFixed int
displayScrollLine int
displayMaxBrightness = 1
displayBrightness = 0
ledsLock sync.Mutex
leds []color.RGBA
ledsPerRow = 6
)
// The main function for the window process.
func windowMain() {
// Create a raster image to use as a display buffer.
displayImage = image.NewRGBA(image.Rect(0, 0, 240, 240))
display := &displayWidget{}
display.Generator = func(w, h int) image.Image {
displayImageLock.Lock()
defer displayImageLock.Unlock()
img := image.NewRGBA(image.Rect(0, 0, w, h))
draw.Draw(img, image.Rect(0, 0, w, h), image.NewUniform(color.RGBA{
R: 192,
G: 192,
B: 192,
A: 255,
}), image.Pt(0, 0), draw.Over)
rect := displayImage.Bounds()
scale := h / rect.Dy()
width := rect.Dx() * scale
height := rect.Dy() * scale
x := (w - width) / 2
y := (h - height) / 2
displayRect := image.Rect(x, y, x+width, y+height)
if displayBrightness <= 0 {
// The backlight is off, so indicate this by making the screen gray.
draw.Draw(img, displayRect, image.NewUniform(color.RGBA{
R: 96,
G: 96,
B: 96,
A: 255,
}), image.Pt(0, 0), draw.Src)
} else {
// Draw the display as usual.
scrolledImage := displayImage
if displayScrollLine != 0 {
// Hardware scrolling is in use, so scroll the middle part of
// the screen.
scrolledImage = image.NewRGBA(displayImage.Rect)
topH := displayScrollTopFixed
bottomH := displayScrollBottomFixed
childH := rect.Dy() - topH - bottomH
rotated := displayScrollLine - topH
rotatedUpH := childH - rotated
rotatedDownH := childH - rotatedUpH
draw.Copy(scrolledImage, image.Pt(0, 0), displayImage, image.Rect(0, 0, rect.Dx(), topH), draw.Over, nil) // top fixed area
draw.Copy(scrolledImage, image.Pt(0, topH), displayImage, image.Rect(0, topH+rotatedDownH, rect.Dx(), topH+childH), draw.Over, nil) // rotated up part
draw.Copy(scrolledImage, image.Pt(0, topH+rotatedUpH), displayImage, image.Rect(0, topH, rect.Dx(), topH+rotatedDownH), draw.Over, nil) // rotated down part
draw.Copy(scrolledImage, image.Pt(0, rect.Dy()-bottomH), displayImage, image.Rect(0, rect.Dy()-bottomH, rect.Dx(), bottomH), draw.Over, nil) // bottom fixed area
}
draw.NearestNeighbor.Scale(img, displayRect, scrolledImage, scrolledImage.Bounds(), draw.Src, nil)
}
return img
}
// Create LEDs.
ledsWidget := canvas.NewRaster(func(w, h int) image.Image {
ledsLock.Lock()
defer ledsLock.Unlock()
img := image.NewRGBA(image.Rect(0, 0, w, h))
// Draw all the LEDs as squares, each 24 pixels in size with an 8 pixel
// gap.
rows := (len(leds) + ledsPerRow - 1) / ledsPerRow
scale := float64(h) / float64(rows*32)
col := 0
row := 0
for _, c := range leds {
x0 := int(float64(8+col*32) * scale)
x1 := int(float64(8+col*32+24) * scale)
y0 := int(float64(row*32) * scale)
y1 := int(float64(row*32+24) * scale)
area := image.Rect(x0, y0, x1, y1)
draw.Draw(img, area, image.NewUniform(c), image.Pt(0, 0), draw.Src)
col++
if col >= ledsPerRow {
col = 0
row++
}
}
return img
})
ledsWidget.Hidden = true
// X/Y/Z acceleration.
// Simulate the device in an upright position (like how you'd hold a phone
// when making a photo in portrait mode).
var accelX, accelY, accelZ = 0.0, 1.0, 0.0
accelContainer := container.New(layout.NewHBoxLayout(),
widget.NewLabel(strconv.FormatFloat(accelX, 'f', 2, 64)),
widget.NewLabel(strconv.FormatFloat(accelY, 'f', 2, 64)),
widget.NewLabel(strconv.FormatFloat(accelZ, 'f', 2, 64)))
fmt.Printf("accel %f %f %f\n", accelX, accelY, accelZ)
// Step count.
var stepCount uint32
stepCountWidget := widget.NewLabel("0")
stepCountIncrementButton := widget.NewButton("+", func() {
stepCount++
stepCountWidget.SetText(strconv.FormatUint(uint64(stepCount), 10))
fmt.Printf("steps %d\n", stepCount)
})
stepCountContainer := container.New(layout.NewHBoxLayout(), stepCountWidget, layout.NewSpacer(), stepCountIncrementButton)
paramGrid := container.New(layout.NewGridLayout(2),
widget.NewLabel("Accel X/Y/Z:"), accelContainer,
widget.NewLabel("Steps:"), stepCountContainer)
// Create a window.
a := app.New()
w := a.NewWindow("Simulator")
w.SetPadded(false)
w.SetFixedSize(true)
w.SetContent(fyne.NewContainerWithLayout(layout.NewVBoxLayout(), display, ledsWidget, paramGrid))
// Listen for keyboard events, and translate them to board API keycodes.
if deskCanvas, ok := w.Canvas().(desktop.Canvas); ok {
deskCanvas.SetOnKeyDown(func(event *fyne.KeyEvent) {
key := decodeFyneKey(event.Name)
if key != NoKey {
fmt.Printf("keypress %d\n", key)
}
})
deskCanvas.SetOnKeyUp(func(event *fyne.KeyEvent) {
key := decodeFyneKey(event.Name)
if key != NoKey {
fmt.Printf("keyrelease %d\n", key)
}
})
}
// Listen for events from the parent process (which includes display data).
go windowReceiveEvents(w, display, ledsWidget)
// Show the window.
w.ShowAndRun()
}
// Goroutine that listens for commands from the parent process.
func windowReceiveEvents(w fyne.Window, display *displayWidget, ledsWidget *canvas.Raster) {
r := bufio.NewReader(os.Stdin)
for {
line, err := r.ReadString('\n')
if err != nil {
if err != io.EOF {
fmt.Fprintln(os.Stderr, "unexpected error:", err)
os.Exit(1)
}
os.Exit(0)
}
cmd := strings.Fields(line)[0]
switch cmd {
case "display":
var width, height int
fmt.Sscanf(line, "%s %d %d\n", &cmd, &width, &height)
newImage := image.NewRGBA(image.Rect(0, 0, width, height))
for y := 0; y < height; y++ {
for x := 0; x < width; x++ {
r := rand.Uint32()
newImage.SetRGBA(x, y, color.RGBA{
R: uint8(r >> 0),
G: uint8(r >> 8),
B: uint8(r >> 16),
A: 255,
})
}
}
displayImageLock.Lock()
displayImage = newImage
display.SetMinSize(fyne.NewSize(float32(width), float32(height)))
displayImageLock.Unlock()
case "display-brightness":
displayImageLock.Lock()
fmt.Sscanf(line, "%s %d %d\n", &cmd, &displayBrightness, displayMaxBrightness)
displayImageLock.Unlock()
display.Refresh()
case "title":
w.SetTitle(strings.TrimSpace(line[len("title"):]))
case "draw":
// Read the image data (which is a single line).
var startX, startY, width int
fmt.Sscanf(line, "%s %d %d %d\n", &cmd, &startX, &startY, &width)
buf := make([]byte, width*3)
io.ReadFull(r, buf)
// Draw the image data to the image buffer.
displayImageLock.Lock()
for x := 0; x < width; x++ {
displayImage.SetRGBA(startX+x, startY, color.RGBA{
R: buf[x*3+0],
G: buf[x*3+1],
B: buf[x*3+2],
A: 255,
})
}
displayImageLock.Unlock()
display.Refresh()
case "scroll-start":
displayImageLock.Lock()
fmt.Sscanf(line, "%s %d %d\n", &cmd, &displayScrollTopFixed, &displayScrollBottomFixed)
displayImageLock.Unlock()
display.Refresh()
case "scroll":
displayImageLock.Lock()
fmt.Sscanf(line, "%s %d\n", &cmd, &displayScrollLine)
displayImageLock.Unlock()
display.Refresh()
case "scroll-stop":
displayImageLock.Lock()
displayScrollLine = 0
displayScrollTopFixed = 0
displayScrollBottomFixed = 0
displayImageLock.Unlock()
display.Refresh()
case "addressable-leds":
// Read the LED data.
var numLEDs int
fmt.Sscanf(line, "%s %d\n", &cmd, &numLEDs)
buf := make([]byte, numLEDs*3)
io.ReadFull(r, buf)
// Update the leds slice.
ledsLock.Lock()
if len(leds) != numLEDs {
// LEDs were configured for the first time (probably).
// Make sure we prepare for the given number of LEDs.
leds = make([]color.RGBA, numLEDs)
cols := ledsPerRow
if cols > len(leds) {
cols = len(leds)
}
rows := (len(leds) + ledsPerRow - 1) / ledsPerRow
ledsWidget.SetMinSize(fyne.NewSize(float32(cols*32+8), float32(rows*32)))
ledsWidget.Show()
}
for i := range leds {
leds[len(leds)-i-1] = color.RGBA{
R: gammaEncodeTable[buf[i*3+0]],
G: gammaEncodeTable[buf[i*3+1]],
B: gammaEncodeTable[buf[i*3+2]],
A: 255,
}
}
ledsLock.Unlock()
ledsWidget.Refresh()
default:
fmt.Fprintln(os.Stderr, "unknown command:", cmd)
}
}
}
func decodeFyneKey(key fyne.KeyName) KeyEvent {
var e KeyEvent
switch key {
case fyne.KeyLeft:
e = KeyLeft
case fyne.KeyRight:
e = KeyRight
case fyne.KeyUp:
e = KeyUp
case fyne.KeyDown:
e = KeyDown
case fyne.KeyEscape:
e = KeyEscape
case fyne.KeyReturn:
e = KeyEnter
case fyne.KeySpace:
e = KeySpace
case fyne.KeyA:
e = KeyA
case fyne.KeyB:
e = KeyB
default:
return NoKeyEvent
}
return e
}
var _ desktop.Mouseable = (*displayWidget)(nil)
var _ fyne.Draggable = (*displayWidget)(nil)
// Wrapper for canvas.Render that sends mouse events to the parent process.
type displayWidget struct {
canvas.Raster
}
func (r *displayWidget) CreateRenderer() fyne.WidgetRenderer {
return widget.NewSimpleRenderer(&r.Raster)
}
func (r *displayWidget) MouseDown(event *desktop.MouseEvent) {
if event.Button == desktop.MouseButtonPrimary {
fmt.Printf("mousedown %d %d\n", int(event.Position.X), int(event.Position.Y))
}
}
func (r *displayWidget) MouseUp(event *desktop.MouseEvent) {
if event.Button == desktop.MouseButtonPrimary {
fmt.Printf("mouseup\n")
}
}
func (r *displayWidget) Dragged(event *fyne.DragEvent) {
fmt.Printf("mousemove %d %d\n", int(event.PointEvent.Position.X), int(event.PointEvent.Position.Y))
}
func (r *displayWidget) DragEnd() {
// handled in MouseUp
}
// Gamma brightness lookup table:
// https://victornpb.github.io/gamma-table-generator
// gamma = 0.45 steps = 256 range = 0-255
var gammaEncodeTable = [256]uint8{
0, 21, 28, 34, 39, 43, 46, 50, 53, 56, 59, 61, 64, 66, 68, 70,
72, 74, 76, 78, 80, 82, 84, 85, 87, 89, 90, 92, 93, 95, 96, 98,
99, 101, 102, 103, 105, 106, 107, 109, 110, 111, 112, 114, 115, 116, 117, 118,
119, 120, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135,
136, 137, 138, 139, 140, 141, 142, 143, 144, 144, 145, 146, 147, 148, 149, 150,
151, 151, 152, 153, 154, 155, 156, 156, 157, 158, 159, 160, 160, 161, 162, 163,
164, 164, 165, 166, 167, 167, 168, 169, 170, 170, 171, 172, 173, 173, 174, 175,
175, 176, 177, 178, 178, 179, 180, 180, 181, 182, 182, 183, 184, 184, 185, 186,
186, 187, 188, 188, 189, 190, 190, 191, 192, 192, 193, 194, 194, 195, 195, 196,
197, 197, 198, 199, 199, 200, 200, 201, 202, 202, 203, 203, 204, 205, 205, 206,
206, 207, 207, 208, 209, 209, 210, 210, 211, 212, 212, 213, 213, 214, 214, 215,
215, 216, 217, 217, 218, 218, 219, 219, 220, 220, 221, 221, 222, 223, 223, 224,
224, 225, 225, 226, 226, 227, 227, 228, 228, 229, 229, 230, 230, 231, 231, 232,
232, 233, 233, 234, 234, 235, 235, 236, 236, 237, 237, 238, 238, 239, 239, 240,
240, 241, 241, 242, 242, 243, 243, 244, 244, 245, 245, 246, 246, 247, 247, 248,
248, 249, 249, 249, 250, 250, 251, 251, 252, 252, 253, 253, 254, 254, 255, 255,
}

53
testdata/smoketest.go vendored Normal file
View file

@ -0,0 +1,53 @@
package main
import (
"time"
"github.com/aykevl/board"
"tinygo.org/x/drivers"
"tinygo.org/x/drivers/pixel"
)
func main() {
// Verify board name constant.
var _ string = board.Name
// Assert that board.Display implements board.Displayer.
checkScreen(board.Display.Configure())
// Assert that Display uses the usual interface.
var _ interface {
//Configure() // already checked above
PPI() int
ConfigureTouch() board.TouchInput
MaxBrightness() int
SetBrightness(int)
WaitForVBlank(time.Duration)
} = board.Display
// Assert that board.Buttons uses the usual interface.
var _ interface {
Configure()
ReadInput()
NextEvent() board.KeyEvent
} = board.Buttons
// Assert that board.Power uses the usual interface.
var _ interface {
Configure()
Status() (state board.ChargeState, microvolts uint32, percent int8)
} = board.Power
// All sensors must implement the exact same interface, even if some methods
// are unsupported.
var _ interface {
Configure(which drivers.Measurement) error
Update(which drivers.Measurement) error
Acceleration() (x, y, z int32)
Steps() uint32
Temperature() int32
} = board.Sensors
}
func checkScreen[T pixel.Color](display board.Displayer[T]) {
}

235
tinygo_test.go Normal file
View file

@ -0,0 +1,235 @@
package board_test
import (
"bytes"
"flag"
"go/ast"
"go/parser"
"go/token"
"os/exec"
"testing"
)
var boards = []string{
// Please keep this list sorted!
"badger2040",
"gameboy-advance",
"gopher-badge",
"mch2022",
"pinetime",
"pybadge",
"pyportal",
"simulator",
"thumby",
}
func isXtensa(board string) bool {
return board == "mch2022"
}
var flagXtensa = flag.Bool("xtensa", false, "test Xtensa based boards")
// These method names should match the ones in testdata/smoketest.go, so that no
// method goes unchecked!
var definedGlobals = map[string][]string{
"Power": []string{
"Configure",
"Status",
},
"Sensors": []string{
"Configure",
"Update",
"Acceleration",
"Steps",
"Temperature",
},
"Display": []string{
"Configure",
"PPI",
"ConfigureTouch",
"MaxBrightness",
"SetBrightness",
"WaitForVBlank",
},
"Buttons": []string{
"Configure",
"ReadInput",
"NextEvent",
},
}
func TestBoards(t *testing.T) {
for _, board := range boards {
board := board
t.Run(board, func(t *testing.T) {
if isXtensa(board) && !*flagXtensa {
t.Skip("skipping Xtensa board:", board)
}
t.Parallel()
outbuf := &bytes.Buffer{}
var cmd *exec.Cmd
if board == "simulator" {
cmd = exec.Command("go", "build", "-o="+t.TempDir()+"/output", "./testdata/smoketest.go")
} else {
cmd = exec.Command("tinygo", "build", "-o="+t.TempDir()+"/output", "-target="+board, "./testdata/smoketest.go")
}
cmd.Stderr = outbuf
cmd.Stdout = outbuf
err := cmd.Run()
if err != nil {
t.Errorf("failed to compile smoke test: %s\n%s", err, outbuf.String())
}
})
}
}
// Test for exported names: all of them have to adhere to a strict API so that
// the API for all boards is the same.
func TestExported(t *testing.T) {
for _, board := range boards {
board := board
t.Run(board, func(t *testing.T) {
// Parse the Go file into an AST.
filename := "board-" + board + ".go"
fset := token.NewFileSet()
f, err := parser.ParseFile(fset, filename, nil, parser.SkipObjectResolution)
if err != nil {
t.Errorf("could not open/parse %s: %v", filename, err)
}
// Collect method names for (typically unexported) named types.
// Also set some defaults that aren't defined in board files (but in
// common.go, probably).
methodNames := map[string][]string{
"baseSensors": definedGlobals["Sensors"],
"dummyBattery": definedGlobals["Power"],
"noButtons": definedGlobals["Buttons"],
}
for _, decl := range f.Decls {
if decl, ok := decl.(*ast.FuncDecl); ok {
if decl.Name.IsExported() && decl.Recv != nil && len(decl.Recv.List) > 0 {
recvType := decl.Recv.List[0].Type
name := extractTypeName(recvType)
methodNames[name] = append(methodNames[name], decl.Name.Name)
}
}
}
// Check all exported types, variables, etc.
for _, decl := range f.Decls {
pos := fset.Position(decl.Pos())
switch decl := decl.(type) {
case *ast.FuncDecl:
if !decl.Name.IsExported() {
continue
}
if decl.Recv != nil && len(decl.Recv.List) > 0 {
// Method name, this is checked when checking named
// types.
continue
}
t.Errorf("%s: unexpected exported function %s", pos, decl.Name.Name)
case *ast.GenDecl:
switch decl.Tok {
case token.IMPORT:
// imports don't export anything
case token.CONST:
for _, spec := range decl.Specs {
pos := fset.Position(spec.Pos())
switch spec := spec.(type) {
case *ast.ValueSpec:
for _, name := range spec.Names {
if !name.IsExported() {
continue
}
if name.Name != "Name" {
// "Name" is the only allowed constant.
t.Errorf("%s: unexpected constant: %s", pos, name.Name)
}
}
default:
t.Errorf("%s: unexpected spec: %#v", pos, spec)
}
}
case token.VAR:
// Variables are things like Power, Display, etc that
// are typically defined using unexported named types.
// We need to check that they don't define any unexpected methods.
for _, spec := range decl.Specs {
pos := fset.Position(spec.Pos())
switch spec := spec.(type) {
case *ast.ValueSpec:
for _, name := range spec.Names {
if !name.IsExported() {
continue
}
if _, ok := definedGlobals[name.Name]; !ok {
t.Errorf("%s: unexpected variable: %s", pos, name.Name)
continue
}
if len(spec.Values) != 1 {
t.Errorf("%s: expected a single value for board.%s", pos, name.Name)
continue
}
typeName := extractTypeName(spec.Values[0])
if _, ok := methodNames[typeName]; !ok {
t.Errorf("%s: could not find methods for type %#v", pos, typeName)
continue
}
for _, typeMethod := range methodNames[typeName] {
found := false
for _, expectedMethod := range definedGlobals[name.Name] {
if typeMethod == expectedMethod {
found = true
}
}
if !found {
t.Errorf("%s: unexpected method %s on board.%s", pos, typeMethod, name)
}
}
}
default:
t.Errorf("%s: unexpected spec: %#v", pos, spec)
}
}
case token.TYPE:
// Boards shouldn't define any new types.
for _, spec := range decl.Specs {
pos := fset.Position(spec.Pos())
switch spec := spec.(type) {
case *ast.TypeSpec:
if !spec.Name.IsExported() {
continue
}
t.Errorf("%s: unexpected type: %s", pos, spec.Name)
default:
t.Errorf("%s: unexpected spec: %#v", pos, spec)
}
}
default:
t.Errorf("%s: unexpected declaration: %#v", pos, decl)
}
default:
t.Logf("%s: unexpected declaration: %#v", pos, decl)
}
}
})
}
}
// Extract the named type from the given AST expression (resolving things like
// *ast.StarExpr).
func extractTypeName(x ast.Expr) string {
switch value := x.(type) {
case *ast.Ident:
return value.Name
case *ast.CompositeLit:
return extractTypeName(value.Type)
case *ast.StarExpr:
return extractTypeName(value.X)
case *ast.UnaryExpr:
return extractTypeName(value.X)
default:
return "<unknown>"
}
}