Compare commits

..

14 Commits

Author SHA1 Message Date
soypat 85a28fb937 delay addition of extra error types until verifiably needed 2024-02-24 23:43:12 -03:00
Patricio Whittingslow 9b74ecbc36 Delete nets/example_test.go 2024-01-10 09:11:23 -03:00
Patricio Whittingslow 1690c2c2d8 Delete nets/nets.go
nets/nets.go was replaced by netif/netif.go
2024-01-10 09:11:09 -03:00
soypat cdc76f3ea7 modify netif.Resolver to use net.DefaultResolver method set 2024-01-07 15:42:43 -03:00
soypat 68ccd70c56 rename file 2024-01-07 15:28:48 -03:00
soypat f657269a66 rename package: nets -> netif 2024-01-07 15:26:55 -03:00
soypat 6913eaf2b9 add examples 2023-12-20 10:26:06 -03:00
soypat d4705e501d rethink netdev name again 2023-12-19 23:12:46 -03:00
soypat 728bdbba5b apply some of @scottfeldman's suggestions 2023-12-19 23:07:20 -03:00
soypat b4f43b4d21 remove old comment 2023-12-18 16:02:55 -08:00
soypat 570e3d2b71 change EthLinkPoller name 2023-12-18 16:01:59 -08:00
soypat 7e45551ff5 add more methods to Link interface 2023-12-18 16:01:17 -08:00
soypat 60cdb7fd50 add EthLinkPoller; 2023-12-18 15:56:35 -08:00
soypat 731362a0cf nets: new package with netdev redesign ideas 2023-12-18 15:32:49 -08:00
63 changed files with 904 additions and 2170 deletions
-125
View File
@@ -1,128 +1,3 @@
0.28.0
---
- **new devices**
- **epd2in66b**
- Waveshare 2.66inch E-Paper Display Module (B) for Raspberry Pi Pico (#673)
- **mcp9808**
- Add driver for MCP9808 i2c temperature sensor (#676)
- **enhancements**
- **encoders**
- add atsamd21, atsamd51, atsame5x
- **pixel**
- add support for Monochrome types such as the SSD1306 display
- **rtl8720dn**
- implement ConnectModeAP
- **servo**
- add function SetAngle() to simplify API for most common use case
- **ssd1306**
- add DrawBitmap() function to complete Displayer interface
- add rotation functions for Displayer interface
- add Sleep() function for Displayer interface
- **uc8151**
- improvements to speed and also add flicker-free mode based on @antirez code example
- update to support all functions needed by tinygl and board package Displayer interface
- **wifinina**
- implement ConnectModeAP
- **bugfixes**
- **ft6336**
- ignore bogus touch events
- **pixel**
- fix Image[Monochrome].Set for larger images
- **uc8151**
- correct DrawBitmap() also refactor SendCommand() and SendData() for clarity
- **ws2812**
- Fix typo and move initialization of neo to init()
- **examples**
- **ws2812**
- Simplify examples/ws2812
0.27.0
---
- **core**
- prepare for CGo changes in TinyGo
- **new devices**
- **adafruit4650**
- support for Adafruit 4650 feather OLED
- **net**
- new networking support based on tinygo net package
- **pixel**
- add package for efficiently working with raw pixel buffers
- **rotary**
- Adding driver for rotary encoder support
- **seesaw**
- Adding support for Adafruit Seesaw platform
- **sgp30**
- add SGP30 air quality sensor
- **sk6812**
- added support for SK6812 to WS2812 device (#610)
- **enhancements**
- **epd2in13**
- add Sleep method like other displays
- unify rotation configuration with other displays
- use better black/white approximation
- **ili9341**
- add DrawBitmap method
- **lora/lorawan**
- LoRa WAN US915 Support
- LoRa WAN add setter functions
- refactor shared functionality for channels/regions
- **mcp2515**
- Add more line speeds to mcp2515.go (#626)
- **rtl8720dn**
- use drivers package version as the driver version
- **ssd1306**
- improvements needed for Thumby SPI display
- **st7735**
- make the display generic over RGB565 and RGB444
- **st7789**
- add DrawBitmap method
- make the display generic over RGB565 and RGB444
- **wifinina**
- add ResetIsHigh cfg switch for MKR 1010 (copied from #561)
- maintenence. Also see PR #4085 in the main TinyGo repo
- use drivers package version as the driver version
- **bugfixes**
- **adxl345**
- Use int16 for ADXL345 readings (#656)
- **at24cx**
- fixed the description of the device struct
- **rtl8720dn**
- allow connecting to open wifi access points
- fix check for bad Wifi connect
- **sh1106**
- fix I2C interface and add smoketest
- fixed the description of the device struct
- **wifinina**
- add 'unknown failure' reason code for AP connect
- fix concurrency issues with multiple sockets
- fix wifinina UDP send
- **examples**
- **ds3231**
- fix the description in the example
- **lorawan**
- add missing functions for simulated interface
- modify atcmd and basic demo to support choosing any one of the supported regions at compile time by using ldflags
- **net**
- all networking examples now using netdev and netlink.
- **build**
- **all**
- fix broken testrunner
- migrated legacy I2C
- add natiu package for tests
- **smoketest**
- add stack-size param for net tests.
- allow stack-size flag as it is needed for net examples
0.26.0
---
- **core**
+1 -1
View File
@@ -1,4 +1,4 @@
Copyright (c) 2018-2024 The TinyGo Authors. All rights reserved.
Copyright (c) 2018-2023 The TinyGo Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
+1 -1
View File
@@ -19,7 +19,7 @@ rwildcard=$(foreach d,$(wildcard $1*),$(call rwildcard,$d/,$2) $(filter $(subst
# Recursively find all *_test.go files from cwd & reduce to unique dir names
HAS_TESTS = $(sort $(dir $(call rwildcard,,*_test.go)))
# Exclude anything we explicitly don't want to test for whatever reason
EXCLUDE_TESTS = image waveshare-epd/epd2in66b
EXCLUDE_TESTS = image
TESTS = $(filter-out $(addsuffix /%,$(EXCLUDE_TESTS)),$(HAS_TESTS))
unit-test:
+1 -1
View File
@@ -3,7 +3,7 @@
[![PkgGoDev](https://pkg.go.dev/badge/tinygo.org/x/drivers)](https://pkg.go.dev/tinygo.org/x/drivers) [![Build](https://github.com/tinygo-org/drivers/actions/workflows/build.yml/badge.svg?branch=dev)](https://github.com/tinygo-org/drivers/actions/workflows/build.yml)
This package provides a collection of over 100 different hardware drivers for devices such as sensors, displays, wireless adaptors, and actuators, that can be used together with [TinyGo](https://tinygo.org).
This package provides a collection of 101 different hardware drivers for devices such as sensors and displays that can be used together with [TinyGo](https://tinygo.org).
For the complete list, please see:
https://tinygo.org/docs/reference/devices/
+7 -7
View File
@@ -95,16 +95,16 @@ func (d *Device) Restart() {
func (d *Device) ReadAcceleration() (x int32, y int32, z int32, err error) {
rx, ry, rz := d.ReadRawAcceleration()
x = int32(d.dataFormat.convertToIS(rx))
y = int32(d.dataFormat.convertToIS(ry))
z = int32(d.dataFormat.convertToIS(rz))
x = d.dataFormat.convertToIS(rx)
y = d.dataFormat.convertToIS(ry)
z = d.dataFormat.convertToIS(rz)
return
}
// ReadRawAcceleration reads the sensor values and returns the raw x, y and z axis
// from the adxl345.
func (d *Device) ReadRawAcceleration() (x int16, y int16, z int16) {
func (d *Device) ReadRawAcceleration() (x int32, y int32, z int32) {
data := []byte{0, 0, 0, 0, 0, 0}
legacy.ReadRegister(d.bus, uint8(d.Address), REG_DATAX0, data)
@@ -140,7 +140,7 @@ func (d *Device) SetRange(sensorRange Range) bool {
}
// convertToIS adjusts the raw values from the adxl345 with the range configuration
func (d *dataFormat) convertToIS(rawValue int16) int16 {
func (d *dataFormat) convertToIS(rawValue int32) int32 {
switch d.sensorRange {
case RANGE_2G:
return rawValue * 4 // rawValue * 2 * 1000 / 512
@@ -190,6 +190,6 @@ func (b *bwRate) toByte() (bits uint8) {
}
// readInt converts two bytes to int16
func readIntLE(msb byte, lsb byte) int16 {
return int16(uint16(msb) | uint16(lsb)<<8)
func readIntLE(msb byte, lsb byte) int32 {
return int32(uint16(msb) | uint16(lsb)<<8)
}
-34
View File
@@ -1,34 +0,0 @@
package encoders
type QuadratureDevice struct {
cfg QuadratureConfig
impl quadratureImpl
}
type QuadratureConfig struct {
Precision int
}
type quadratureImpl interface {
configure(cfg QuadratureConfig) error
readValue() int
writeValue(int)
}
func (enc *QuadratureDevice) Configure(cfg QuadratureConfig) error {
if cfg.Precision < 1 {
cfg.Precision = 4
}
enc.cfg = cfg
return enc.impl.configure(cfg)
}
// Position returns the stored int value for the encoder
func (enc *QuadratureDevice) Position() int {
return enc.impl.readValue() / enc.cfg.Precision
}
// SetPosition overwrites the currently stored value with the specified int value
func (enc *QuadratureDevice) SetPosition(v int) {
enc.impl.writeValue(v * enc.cfg.Precision)
}
-69
View File
@@ -1,69 +0,0 @@
//go:build tinygo && (rp2040 || stm32 || k210 || esp32c3 || nrf || sam || (avr && (atmega328p || atmega328pb)))
// Implementation based on:
// https://gist.github.com/aykevl/3fc1683ed77bb0a9c07559dfe857304a
// Note: build constraints in this file list targets that define machine.PinToggle.
// If this is supported for additional targets in the future, they can be added above.
package encoders
import (
"machine"
"runtime/volatile"
)
var (
states = []int8{0, -1, 1, 0, 1, 0, 0, -1, -1, 0, 0, 1, 0, 1, -1, 0}
)
// NewQuadratureViaInterrupt returns a rotary encoder device that uses GPIO
// interrupts and a lookup table to keep track of quadrature state changes.
//
// This constructur is only available for TinyGo targets for which machine.PinToggle
// is defined as a valid interrupt type.
func NewQuadratureViaInterrupt(pinA, pinB machine.Pin) *QuadratureDevice {
return &QuadratureDevice{impl: &quadInterruptImpl{pinA: pinA, pinB: pinB, oldAB: 0b00000011}}
}
type quadInterruptImpl struct {
pinA machine.Pin
pinB machine.Pin
// precision int
oldAB int
value volatile.Register32
}
func (enc *quadInterruptImpl) configure(cfg QuadratureConfig) error {
enc.pinA.Configure(machine.PinConfig{Mode: machine.PinInputPullup})
enc.pinA.SetInterrupt(machine.PinToggle, enc.interrupt)
enc.pinB.Configure(machine.PinConfig{Mode: machine.PinInputPullup})
enc.pinB.SetInterrupt(machine.PinToggle, enc.interrupt)
return nil
}
func (enc *quadInterruptImpl) interrupt(pin machine.Pin) {
aHigh, bHigh := enc.pinA.Get(), enc.pinB.Get()
enc.oldAB <<= 2
if aHigh {
enc.oldAB |= 1 << 1
}
if bHigh {
enc.oldAB |= 1
}
enc.writeValue(enc.readValue() + int(states[enc.oldAB&0x0f]))
}
// readValue gets the value using volatile operations and returns it as an int
func (enc *quadInterruptImpl) readValue() int {
return int(enc.value.Get())
}
// writeValue set the value to the specified int using volatile operations
func (enc *quadInterruptImpl) writeValue(v int) {
enc.value.Set(uint32(v))
}
+4 -3
View File
@@ -122,7 +122,7 @@ func (d *Device) NetDisconnect() {
}
func (d *Device) NetNotify(cb func(netlink.Event)) {
fmt.Printf("\r\n%s\r\n", netlink.ErrNotSupported)
// Not supported
}
func (d *Device) GetHostByName(name string) (netip.Addr, error) {
@@ -218,8 +218,8 @@ func (d *Device) Listen(sockfd int, backlog int) error {
return nil
}
func (d *Device) Accept(sockfd int) (int, netip.AddrPort, error) {
return -1, netip.AddrPort{}, netdev.ErrNotSupported
func (d *Device) Accept(sockfd int, ip netip.AddrPort) (int, error) {
return -1, netdev.ErrNotSupported
}
func (d *Device) sendChunk(sockfd int, buf []byte, deadline time.Time) (int, error) {
@@ -473,6 +473,7 @@ func (d *Device) parseIPD(end int) error {
}
// load up the socket data
//d.data = append(d.data, d.response[e+1:end]...)
d.data = append(d.data, d.response[e+1:e+1+v]...)
return nil
}
@@ -1,28 +0,0 @@
//go:build macropad_rp2040
package main
import (
"machine"
"tinygo.org/x/drivers/encoders"
)
var (
enc = encoders.NewQuadratureViaInterrupt(machine.ROT_A, machine.ROT_B)
)
func main() {
enc.Configure(encoders.QuadratureConfig{
Precision: 4,
})
for oldValue := 0; ; {
if newValue := enc.Position(); newValue != oldValue {
println("value: ", newValue)
oldValue = newValue
}
}
}
-48
View File
@@ -1,48 +0,0 @@
package main
import (
"fmt"
"machine"
"time"
"tinygo.org/x/drivers/mcp9808"
)
func main() {
//tinygo monitor
time.Sleep(time.Millisecond * 5000)
//Configure I2C (in this case, I2C0 on RPI Pico), and wire the module accordingly
machine.I2C0.Configure(machine.I2CConfig{
SCL: machine.GP1,
SDA: machine.GP0,
})
//Create sensor
sensor := mcp9808.New(machine.I2C0)
if !sensor.Connected() {
println("MCP9808 not found")
return
} else {
println("MCP9808 found")
}
time.Sleep(time.Millisecond * 1000)
//Set resolution
sensor.SetResolution(mcp9808.Maximum)
time.Sleep(time.Millisecond * 1000)
//Read temp.
temp, err := sensor.ReadTemperature()
if err != nil {
println("MCP9808 error reading temperature")
println(err.Error())
return
} else {
fmt.Printf("Temperature: %.2f \n", temp)
}
return
}
+1 -1
View File
@@ -9,7 +9,7 @@
// examples/net/webclient (for HTTP)
// examples/net/tlsclient (for HTTPS)
//go:build ninafw || wioterminal
//go:build pyportal || nano_rp2040 || metro_m4_airlift || arduino_mkrwifi1010 || matrixportal_m4 || wioterminal
package main
+1 -1
View File
@@ -9,7 +9,7 @@
// examples/net/webclient (for HTTP)
// examples/net/tlsclient (for HTTPS)
//go:build ninafw || wioterminal
//go:build pyportal || nano_rp2040 || metro_m4_airlift || arduino_mkrwifi1010 || matrixportal_m4 || wioterminal
package main
+1 -1
View File
@@ -9,7 +9,7 @@
// examples/net/webclient (for HTTP)
// examples/net/tlsclient (for HTTPS)
//go:build ninafw || wioterminal
//go:build pyportal || nano_rp2040 || metro_m4_airlift || arduino_mkrwifi1010 || matrixportal_m4 || wioterminal
package main
+1 -1
View File
@@ -9,7 +9,7 @@
// examples/net/webclient (for HTTP)
// examples/net/tlsclient (for HTTPS)
//go:build ninafw || wioterminal
//go:build pyportal || nano_rp2040 || metro_m4_airlift || arduino_mkrwifi1010 || matrixportal_m4 || wioterminal
package main
+1 -1
View File
@@ -4,7 +4,7 @@
// Note: It may be necessary to increase the stack size when using
// paho.mqtt.golang. Use the -stack-size=4KB command line option.
//go:build ninafw || wioterminal || challenger_rp2040
//go:build pyportal || nano_rp2040 || metro_m4_airlift || arduino_mkrwifi1010 || matrixportal_m4 || wioterminal || challenger_rp2040
package main
+1 -1
View File
@@ -4,7 +4,7 @@
// Note: It may be necessary to increase the stack size when using
// paho.mqtt.golang. Use the -stack-size=4KB command line option.
//go:build ninafw || wioterminal || challenger_rp2040
//go:build pyportal || nano_rp2040 || metro_m4_airlift || arduino_mkrwifi1010 || matrixportal_m4 || wioterminal || challenger_rp2040
package main
+1 -1
View File
@@ -3,7 +3,7 @@
// It creates a UDP connection to request the current time and parse the
// response from a NTP server. The system time is set to NTP time.
//go:build ninafw || wioterminal || challenger_rp2040
//go:build pyportal || arduino_nano33 || nano_rp2040 || metro_m4_airlift || arduino_mkrwifi1010 || matrixportal_m4 || wioterminal || challenger_rp2040
package main
-30
View File
@@ -1,30 +0,0 @@
//go:build ninafw || wioterminal
package main
import (
"log"
"time"
"tinygo.org/x/drivers/netlink"
"tinygo.org/x/drivers/netlink/probe"
)
var (
ssid string
pass string
)
func init() {
time.Sleep(2 * time.Second)
link, _ := probe.Probe()
err := link.NetConnect(&netlink.ConnectParams{
Ssid: ssid,
Passphrase: pass,
})
if err != nil {
log.Fatal(err)
}
}
-120
View File
@@ -1,120 +0,0 @@
// This example is the classic snake network test. The snake is feed a steady
// diet of pkts and the pkts work themselves thru the snake segments and exit
// the tail. Each snake segment is a TCP socket connection to a server. The
// server echos pkts received back to the snake, and serves each segment on a
// different port. (See server/main.go for server).
//
// snake | server
// |
// head ----->---|-->--+
// seg a | |
// +---<-|--<--+
// | |
// +-->--|-->--+
// seg b | |
// +---<-|--<--+
// | |
// +-->--|-->--+
// seg c | |
// +---<-|--<--+
// | |
// +-->--|-->--+
// ... | |
// +---<-|--<--+
// | |
// +-->--|-->--+
// seg n | |
// tail -------<-|--<--+
// |
// The snake segments are linked by channels and each segment is run as a go
// func. This forces segments to connect and run concurrently, which is a good
// test of the underlying driver's ability to handle concurrent connections.
//go:build ninafw || wioterminal
package main
import (
_ "embed"
"fmt"
"log"
"net"
"strings"
"time"
)
//go:embed main.go
var code string
var (
server string = "10.0.0.100:8080"
)
func segment(in chan []byte, out chan []byte) {
var buf [512]byte
for {
c, err := net.Dial("tcp", server)
for ; err != nil; c, err = net.Dial("tcp", server) {
println(err.Error())
time.Sleep(5 * time.Second)
}
for {
select {
case msg := <-in:
_, err := c.Write(msg)
if err != nil {
log.Fatal(err.Error())
}
time.Sleep(100 * time.Millisecond)
n, err := c.Read(buf[:])
if err != nil {
log.Fatal(err.Error())
}
out <- buf[:n]
}
}
}
}
func feedit(head chan []byte) {
for i := 0; i < 100; i++ {
head <- []byte(fmt.Sprintf("\n---%d---\n", i))
for _, line := range strings.Split(code, "\n") {
if len(line) == 0 {
line = " "
}
head <- []byte(line)
}
}
}
var head = make(chan []byte)
var a = make(chan []byte)
var b = make(chan []byte)
var c = make(chan []byte)
var d = make(chan []byte)
var e = make(chan []byte)
var f = make(chan []byte)
var tail = make(chan []byte)
func main() {
// The snake
go segment(head, a)
go segment(a, b)
go segment(b, c)
go segment(c, d)
go segment(d, e)
go segment(e, f)
go segment(f, tail)
go feedit(head)
for {
select {
case msg := <-tail:
println(string(msg))
}
}
}
-34
View File
@@ -1,34 +0,0 @@
package main
import (
"io"
"log"
"net"
)
func main() {
// Listen for connections
l, err := net.Listen("tcp", ":8080")
if err != nil {
log.Fatal(err.Error())
}
defer l.Close()
println("Listening on port", ":8080")
for {
// Wait for a connection
conn, err := l.Accept()
if err != nil {
log.Fatal(err)
}
println("Accepted connection from", conn.RemoteAddr().String())
// Service the new connection in a goroutine.
// The loop then returns to accepting, so that
// multiple connections may be served concurrently
go func(c net.Conn) {
// Echo all incoming data
io.Copy(c, c)
// Shut down the connection
c.Close()
}(conn)
}
}
+1 -1
View File
@@ -4,7 +4,7 @@
//
// nc -lk 8080
//go:build ninafw || wioterminal || challenger_rp2040
//go:build pyportal || arduino_nano33 || nano_rp2040 || metro_m4_airlift || arduino_mkrwifi1010 || matrixportal_m4 || wioterminal || challenger_rp2040
package main
+1 -1
View File
@@ -5,7 +5,7 @@
//
// nc -lk 8080
//go:build ninafw || wioterminal || challenger_rp2040 || pico
//go:build pyportal || arduino_nano33 || nano_rp2040 || metro_m4_airlift || arduino_mkrwifi1010 || matrixportal_m4 || wioterminal || challenger_rp2040 || pico
package main
+2 -5
View File
@@ -7,7 +7,7 @@
//
// $ nc 10.0.0.2 8080 <file >copy ; cmp file copy
//go:build ninafw || wioterminal
//go:build pyportal || arduino_nano33 || nano_rp2040 || metro_m4_airlift || arduino_mkrwifi1010 || matrixportal_m4 || wioterminal
package main
@@ -30,18 +30,16 @@ var (
var buf [1024]byte
func echo(conn net.Conn) {
println("Client", conn.RemoteAddr(), "connected")
defer conn.Close()
_, err := io.CopyBuffer(conn, conn, buf[:])
if err != nil && err != io.EOF {
log.Fatal(err.Error())
}
println("Client", conn.RemoteAddr(), "closed")
}
func main() {
time.Sleep(2 * time.Second)
time.Sleep(time.Second)
link, _ := probe.Probe()
@@ -53,7 +51,6 @@ func main() {
log.Fatal(err)
}
println("Starting TCP server listening on", port)
l, err := net.Listen("tcp", port)
if err != nil {
log.Fatal(err.Error())
+1 -1
View File
@@ -5,7 +5,7 @@
//
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security
//go:build ninafw || wioterminal
//go:build pyportal || nano_rp2040 || metro_m4_airlift || arduino_mkrwifi1010 || matrixportal_m4 || wioterminal
package main
+1 -1
View File
@@ -17,7 +17,7 @@
// }
// ---------------------------------------------------------------------------
//go:build ninafw || wioterminal
//go:build pyportal || arduino_nano33 || nano_rp2040 || metro_m4_airlift || arduino_mkrwifi1010 || matrixportal_m4 || wioterminal
package main
+1 -1
View File
@@ -6,7 +6,7 @@
// Note: It may be necessary to increase the stack size when using "net/http".
// Use the -stack-size=4KB command line option.
//go:build ninafw || wioterminal
//go:build pyportal || nano_rp2040 || metro_m4_airlift || arduino_mkrwifi1010 || matrixportal_m4 || wioterminal
package main
+1 -1
View File
@@ -6,7 +6,7 @@
// Note: It may be necessary to increase the stack size when using
// "golang.org/x/net/websocket". Use the -stack-size=4KB command line option.
//go:build ninafw || wioterminal
//go:build pyportal || nano_rp2040 || metro_m4_airlift || arduino_mkrwifi1010 || matrixportal_m4 || wioterminal
package main
+1 -1
View File
@@ -6,7 +6,7 @@
// Note: It may be necessary to increase the stack size when using
// "golang.org/x/net/websocket". Use the -stack-size=4KB command line option.
//go:build ninafw || wioterminal
//go:build pyportal || nano_rp2040 || metro_m4_airlift || arduino_mkrwifi1010 || matrixportal_m4 || wioterminal
package main
+1 -1
View File
@@ -3,7 +3,7 @@
// Note: It may be necessary to increase the stack size when using "net/http".
// Use the -stack-size=4KB command line option.
//go:build ninafw || wioterminal
//go:build pyportal || nano_rp2040 || metro_m4_airlift || arduino_mkrwifi1010 || matrixportal_m4 || wioterminal
package main
+13 -19
View File
@@ -25,25 +25,19 @@ func main() {
return
}
println("setting to 0°")
s.SetMicroseconds(1000)
time.Sleep(3 * time.Second)
println("setting to 45°")
s.SetMicroseconds(1500)
time.Sleep(3 * time.Second)
println("setting to 90°")
s.SetMicroseconds(2000)
time.Sleep(3 * time.Second)
for {
println("setting to 0°")
s.SetAngle(0)
time.Sleep(3 * time.Second)
println("setting to 45°")
s.SetAngle(45)
time.Sleep(3 * time.Second)
println("setting to 90°")
s.SetAngle(90)
time.Sleep(3 * time.Second)
println("setting to 135°")
s.SetAngle(135)
time.Sleep(3 * time.Second)
println("setting to 180°")
s.SetAngle(180)
time.Sleep(3 * time.Second)
time.Sleep(time.Second)
}
}
-50
View File
@@ -1,50 +0,0 @@
// This example using the SSD1306 OLED display over SPI on the Thumby board
// A very tiny 72x40 display.
package main
import (
"image/color"
"machine"
"time"
"tinygo.org/x/drivers/ssd1306"
)
func main() {
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},
})
display.ClearDisplay()
x := int16(36)
y := int16(20)
deltaX := int16(1)
deltaY := int16(1)
for {
pixel := display.GetPixel(x, y)
c := color.RGBA{255, 255, 255, 255}
if pixel {
c = color.RGBA{0, 0, 0, 255}
}
display.SetPixel(x, y, c)
display.Display()
x += deltaX
y += deltaY
if x == 0 || x == 71 {
deltaX = -deltaX
}
if y == 0 || y == 39 {
deltaY = -deltaY
}
time.Sleep(1 * time.Millisecond)
}
}
+12 -40
View File
@@ -3,9 +3,7 @@ package main
import (
"image/color"
"machine"
"time"
"tinygo.org/x/drivers"
"tinygo.org/x/drivers/uc8151"
)
@@ -16,56 +14,30 @@ func main() {
led = machine.LED
led.Configure(machine.PinConfig{Mode: machine.PinOutput})
machine.SPI0.Configure(machine.SPIConfig{
Frequency: 12 * machine.MHz,
Frequency: 12000000,
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,
Rotation: uc8151.ROTATION_270,
Speed: uc8151.MEDIUM,
Blocking: true,
})
black := color.RGBA{1, 1, 1, 255}
display.ClearDisplay()
mod := int16(1)
for {
// checkerboard
for i := int16(0); i < 11; i++ {
if mod == 1 {
mod = 0
} else {
mod = 1
}
display.ClearBuffer()
for i := int16(0); i < 37; i++ {
for j := int16(0); j < 16; j++ {
if (i+j)%2 == mod {
showRect(i*8, j*8, 8, 8, black)
}
}
}
display.Display()
time.Sleep(500 * time.Millisecond)
}
// moving line
for i := int16(16); i < 21; i++ {
display.ClearBuffer()
for j := int16(0); j < 16; j++ {
if (i+j)%2 == 0 {
showRect(i*8, j*8, 8, 8, black)
}
display.Display()
time.Sleep(250 * time.Millisecond)
display.ClearBuffer()
display.Display()
for i := int16(0); i < 37; i++ {
for j := int16(0); j < 16; j++ {
if (i+j)%2 == 0 {
showRect(i*8, j*8, 8, 8, black)
}
}
}
display.Display()
}
func showRect(x int16, y int16, w int16, h int16, c color.RGBA) {
-84
View File
@@ -1,84 +0,0 @@
package main
import (
"image/color"
"machine"
"time"
"tinygo.org/x/drivers"
"tinygo.org/x/drivers/waveshare-epd/epd2in66b"
"tinygo.org/x/tinyfont"
"tinygo.org/x/tinyfont/freemono"
)
var (
black = color.RGBA{0, 0, 0, 0xff}
white = color.RGBA{0xff, 0xff, 0xff, 0xff}
red = color.RGBA{0xff, 0, 0, 0xff}
)
func main() {
machine.Serial.Configure(machine.UARTConfig{})
time.Sleep(2 * time.Second)
machine.SPI1.Configure(machine.SPIConfig{
Frequency: epd2in66b.Baudrate,
})
println("started")
// in case you have a Pico module, you can directly use
// dev, err := epd2in66b.NewPicoModule()
display := epd2in66b.New(machine.SPI1)
cfg := epd2in66b.Config{
DataPin: machine.GP8,
ChipSelectPin: machine.GP9,
ResetPin: machine.GP12,
BusyPin: machine.GP13,
}
err := display.Configure(cfg)
if err != nil {
panic(err)
}
err = display.Reset()
if err != nil {
panic(err)
}
println("draw checkerboard")
drawCheckerBoard(&display)
println("draw 'hello'")
tinyfont.WriteLineRotated(&display, &freemono.Bold24pt7b, 40, 10, "Hello!", white, tinyfont.ROTATION_90)
tinyfont.WriteLineRotated(&display, &freemono.Bold12pt7b, 10, 10, "tinygo rocks", white, tinyfont.ROTATION_90)
err = display.Display()
if err != nil {
panic(err)
}
}
func drawCheckerBoard(display drivers.Displayer) {
s := 8
width, height := display.Size()
for x := 0; x <= int(width)-s; x += s {
for y := 0; y <= int(height)-s; y += s {
c := red
if (x/s)%2 == (y/s)%2 {
c = black
}
showRect(display, x, y, s, s, c)
}
}
}
func showRect(display drivers.Displayer, x int, y int, w int, h int, c color.RGBA) {
for i := x; i < x+w; i++ {
for j := y; j < y+h; j++ {
display.SetPixel(int16(i), int16(j), c)
}
}
}
+4 -5
View File
@@ -4,8 +4,7 @@ package main
import "machine"
func init() {
// Replace neo in the code below to match the pin
// that you are using if different.
neo = machine.D2
}
// Replace neo and led in the code below to match the pin
// that you are using if different.
var neo = machine.D2
var led = machine.LED
+5 -6
View File
@@ -4,9 +4,8 @@ package main
import "machine"
func init() {
// This is the pin assignment for the Digispark only.
// Replace neo and led in the code below to match the pin
// that you are using if different.
neo = machine.Pin(0)
}
// This is the pin assignment for the Digispark only.
// Replace neo and led in the code below to match the pin
// that you are using if different.
var neo machine.Pin = 0
var led = machine.LED
+4 -4
View File
@@ -12,12 +12,11 @@ import (
"tinygo.org/x/drivers/ws2812"
)
var (
neo machine.Pin
leds [10]color.RGBA
)
var leds [10]color.RGBA
func main() {
led.Configure(machine.PinConfig{Mode: machine.PinOutput})
neo.Configure(machine.PinConfig{Mode: machine.PinOutput})
ws := ws2812.NewWS2812(neo)
@@ -36,6 +35,7 @@ func main() {
}
ws.WriteColors(leds[:])
led.Set(rg)
time.Sleep(100 * time.Millisecond)
}
}
+5 -6
View File
@@ -1,11 +1,10 @@
//go:build !digispark && !arduino
//go:build !digispark && !arduino && !qtpy && !m5stamp_c3 && !thingplus_rp2040
package main
import "machine"
func init() {
// Replace neo in the code below to match the pin
// that you are using if different.
neo = machine.WS2812
}
// Replace neo and led in the code below to match the pin
// that you are using if different.
var neo machine.Pin = machine.WS2812
var led = machine.LED
+10
View File
@@ -0,0 +1,10 @@
//go:build qtpy || m5stamp_c3
package main
import "machine"
// Replace neo and led in the code below to match the pin
// that you are using if different.
var neo machine.Pin = machine.WS2812
var led = machine.NoPin
+12
View File
@@ -0,0 +1,12 @@
//go:build thingplus_rp2040
package main
import "machine"
// This is the pin assignment for the internal neopixel of the
// Sparkfun thingplus rp2040.
// Replace neo and led in the code below to match the pin
// that you are using if different.
var neo machine.Pin = machine.GPIO8
var led = machine.LED
+1 -2
View File
@@ -80,8 +80,7 @@ func (d *Device) Read() []byte {
func (d *Device) ReadTouchPoint() touch.Point {
d.Read()
z := 0xFFFFF
switch d.buf[0] {
case 0, 255:
if d.buf[0] == 0 {
z = 0
}
-2
View File
@@ -2,8 +2,6 @@ module tinygo.org/x/drivers
go 1.18
replace tinygo.org/x/drivers/mcp9808 => /home/kasterby/Documents/drivers/mcp9808
require (
github.com/eclipse/paho.mqtt.golang v1.2.0
github.com/frankban/quicktest v1.10.2
-98
View File
@@ -1,98 +0,0 @@
// Package mcp9808 implements a driver for the MCP9808 High Accuracy I2C Temperature Sensor
//
// Datasheet: https://cdn-shop.adafruit.com/datasheets/MCP9808.pdf
// Module: https://www.adafruit.com/product/1782
// Only implemented: temperature reading, resolution read & set
package mcp9808
import (
"encoding/binary"
"errors"
"tinygo.org/x/drivers"
)
type Device struct {
bus drivers.I2C
Address uint16
}
func New(bus drivers.I2C) Device {
return Device{bus, MCP9808_I2CADDR_DEFAULT}
}
func (d *Device) Connected() bool {
data := make([]byte, 2)
d.Read(MCP9808_REG_DEVICE_ID, &data)
return binary.BigEndian.Uint16(data) == MCP9808_DEVICE_ID
}
func (d *Device) ReadTemperature() (float64, error) {
data := make([]byte, 2)
var temp float64
if err := d.Read(MCP9808_REG_AMBIENT_TEMP, &data); err != nil {
return 0, err
}
data[0] = data[0] & 0x1F
if data[0]&0x10 == 0x10 {
data[0] = data[0] & 0x0F
temp = float64(data[0])*16 + float64(data[1])/16.0 - 256
}
temp = float64(data[0])*16 + float64(data[1])/16.0
return temp, nil
}
func (d *Device) ReadResolution() (resolution, error) {
data := make([]byte, 2)
err := d.Read(MCP9808_REG_RESOLUTION, &data)
if err != nil {
return 0, err
}
switch data[0] {
case 0:
return Low, nil
case 1:
return Medium, nil
case 2:
return High, nil
case 3:
return Maximum, nil
default:
return 0, errors.New("unknown resolution")
}
}
func (d *Device) SetResolution(r resolution) error {
switch r {
case Low:
if err := d.Write(MCP9808_REG_RESOLUTION, []byte{0x00}); err != nil {
return err
}
case Medium:
if err := d.Write(MCP9808_REG_RESOLUTION, []byte{0x01}); err != nil {
return err
}
case High:
if err := d.Write(MCP9808_REG_RESOLUTION, []byte{0x02}); err != nil {
return err
}
case Maximum:
if err := d.Write(MCP9808_REG_RESOLUTION, []byte{0x03}); err != nil {
return err
}
default:
return nil
}
return nil
}
func (d *Device) Write(register byte, data []byte) error {
buf := append([]byte{register}, data...)
return d.bus.Tx(d.Address, buf, nil)
}
func (d *Device) Read(register byte, data *[]byte) error {
return d.bus.Tx(d.Address, []byte{register}, *data)
}
-53
View File
@@ -1,53 +0,0 @@
// Package mcp9808 implements a driver for the MCP9808 High Accuracy I2C Temperature Sensor
//
// Datasheet: https://cdn-shop.adafruit.com/datasheets/MCP9808.pdf
// Module: https://www.adafruit.com/product/1782
package mcp9808
// Constants/addresses used for I2C.
const (
MCP9808_DEVICE_ID = 0x0400
MCP9808_MANUF_ID = 0x0054
MCP9808_I2CADDR_DEFAULT = 0x18 //default I2C address
MCP9808_REG_CONFIG = 0x01 //MCP9808 config register
MCP9808_REG_CONFIG_SHUTDOWN = 0x0100 //shutdown config
MCP9808_REG_CONFIG_CRITLOCKED = 0x0080 //critical trip lock
MCP9808_REG_CONFIG_WINLOCKED = 0x0040 //alarm window lock
MCP9808_REG_CONFIG_INTCLR = 0x0020 //interrupt clear
MCP9808_REG_CONFIG_ALERTSTAT = 0x0010 //alert output status
MCP9808_REG_CONFIG_ALERTCTRL = 0x0008 //alert output control
MCP9808_REG_CONFIG_ALERTSEL = 0x0004 //alert output select
MCP9808_REG_CONFIG_ALERTPOL = 0x0002 //alert output polarity
MCP9808_REG_CONFIG_ALERTMODE = 0x0001 //alert output mode
MCP9808_REG_UPPER_TEMP = 0x02 //upper alert boundary
MCP9808_REG_LOWER_TEMP = 0x03 //lower alert boundery
MCP9808_REG_CRIT_TEMP = 0x04 //critical temperature
MCP9808_REG_AMBIENT_TEMP = 0x05 //ambient temperature
MCP9808_REG_MANUF_ID = 0x06 //manufacturer ID
MCP9808_REG_DEVICE_ID = 0x07 //device ID
MCP9808_REG_RESOLUTION = 0x08 //resolution
)
/*
======= ============ ==============
value resolution reading Time
======= ============ ==============
0 0.5°C 30 ms
1 0.25°C 65 ms
2 0.125°C 130 ms
3 0.0625°C 250 ms
======= ============ ==============
*/
type resolution uint8
const (
Low resolution = iota
Medium
High
Maximum
)
+1 -3
View File
@@ -36,11 +36,9 @@ var (
ErrFamilyNotSupported = errors.New("Address family not supported")
ErrProtocolNotSupported = errors.New("Socket protocol/type not supported")
ErrStartingDHCPClient = errors.New("Error starting DHPC client")
ErrStartingDHCPServer = errors.New("Error starting DHPC server")
ErrNoMoreSockets = errors.New("No more sockets")
ErrClosingSocket = errors.New("Error closing socket")
ErrNotSupported = errors.New("Not supported")
ErrInvalidSocketFd = errors.New("Invalid socket fd")
)
// Duplicate of non-exported net.errTimeout
@@ -84,7 +82,7 @@ type Netdever interface {
Bind(sockfd int, ip netip.AddrPort) error
Connect(sockfd int, host string, ip netip.AddrPort) error
Listen(sockfd int, backlog int) error
Accept(sockfd int) (int, netip.AddrPort, error)
Accept(sockfd int, ip netip.AddrPort) (int, error)
Send(sockfd int, buf []byte, flags int, deadline time.Time) (int, error)
Recv(sockfd int, buf []byte, flags int, deadline time.Time) (int, error)
Close(sockfd int) error
+97
View File
@@ -0,0 +1,97 @@
package netif
import (
"net"
"time"
)
func AutoProbe(timeout time.Duration) (Stack, error) {
// This function automatically gets the first available network device
// and returns a stack for it.
// It is intended to be used by the user as a convenience function.
// Will be guarded by build tags and specific for whether the device
// is a StackWifi, InterfaceEthPollWifi or InterfaceEthPoller.
return nil, nil
}
func ProbeStackWifi() StackWifi {
return nil
}
func ProbeEthPollWifi() InterfaceEthPollWifi {
return nil
}
func ProbeEthPoller() InterfaceEthPoller {
return nil
}
func StackForEthPoll(dev InterfaceEthPoller) Stack {
// Use seqs to generate a stack.
return nil
}
// OSI layer 4 enabled WIFI chip. i.e.: ESP32
func ExampleProbeStackWifi() {
dev := ProbeStackWifi()
wifiparams := WifiParams{
SSID: "myssid",
ConnectMode: ConnectModeSTA,
Passphrase: "mypassphrase",
Auth: AuthTypeWPA2,
CountryCode: "US",
}
err := StartWifiAutoconnect(dev, WifiAutoconnectParams{
WifiParams: wifiparams,
})
if err != nil {
panic(err)
}
for dev.NetFlags()&net.FlagRunning == 0 {
time.Sleep(100 * time.Millisecond)
}
UseStack(dev)
net.Dial("tcp", "192.168.1.1:33")
}
// Simplest case, OSI layer 2 enabled WIFI chip, i.e: CYW43439
func ExampleProbeEthPollWifi() {
dev := ProbeEthPollWifi()
wifiparams := WifiParams{
SSID: "myssid",
ConnectMode: ConnectModeSTA,
Passphrase: "mypassphrase",
Auth: AuthTypeWPA2,
CountryCode: "US",
}
err := StartWifiAutoconnect(dev, WifiAutoconnectParams{
WifiParams: wifiparams,
})
if err != nil {
panic(err)
}
for dev.NetFlags()&net.FlagRunning == 0 {
time.Sleep(100 * time.Millisecond)
}
stack := StackForEthPoll(dev)
UseStack(stack)
net.Dial("tcp", "192.168.1.1:33")
}
// OSI level 2 chip with wired connection, i.e. ENC28J60.
func ExampleProbeEthPoller() {
dev := ProbeEthPoller()
if dev.NetFlags()&net.FlagRunning == 0 {
panic("ethernet not connected")
}
stack := StackForEthPoll(dev)
UseStack(stack)
net.Dial("tcp", "192.168.1.1:33")
}
func ExampleAutoProbe() {
stack, err := AutoProbe(10 * time.Second)
if err != nil {
panic(err)
}
UseStack(stack)
net.Dial("tcp", "192.168.1.1:33")
}
+211
View File
@@ -0,0 +1,211 @@
package netif
import (
"context"
"errors"
"net"
"net/netip"
"time"
_ "unsafe"
)
//go:linkname UseStack net.useNetdev
func UseStack(stack Stack)
// Socket errors.
var (
ErrProtocolNotSupported = errors.New("socket protocol/type not supported")
)
// Wifi errors.
var (
ErrAuthFailure = errors.New("wifi authentication failure")
ErrAuthUnsupported = errors.New("wifi authorization type not supported")
)
const (
_AF_INET = 0x2
_SOCK_STREAM = 0x1
_SOCK_DGRAM = 0x2
_SOL_SOCKET = 0x1
_SO_KEEPALIVE = 0x9
_SOL_TCP = 0x6
_TCP_KEEPINTVL = 0x5
_IPPROTO_TCP = 0x6
_IPPROTO_UDP = 0x11
// Made up, not a real IP protocol number. This is used to create a
// TLS socket on the device, assuming the device supports mbed TLS.
_IPPROTO_TLS = 0xFE
_F_SETFL = 0x4
)
// Interface is the minimum interface that need be implemented by any network
// device driver and is based on [net.Interface].
type Interface interface {
// HardwareAddr6 returns the device's 6-byte [MAC address].
//
// [MAC address]: https://en.wikipedia.org/wiki/MAC_address
HardwareAddr6() ([6]byte, error)
// NetFlags returns the net.Flag values for the interface. It includes state of connection.
NetFlags() net.Flags
// MTU returns the maximum transmission unit size.
MTU() int
// Notify to register callback for network events. May not be supported for certain devices.
NetNotify(cb func(Event)) error
}
// InterfaceEthPoller is implemented by devices that send/receive ethernet packets.
type InterfaceEthPoller interface {
Interface
// SendEth sends an Ethernet packet
SendEth(pkt []byte) error
// RecvEthHandle sets recieve Ethernet packet callback function
RecvEthHandle(func(pkt []byte) error)
// PollOne tries to receive one Ethernet packet and returns true if one was
PollOne() (bool, error)
}
// InterfaceWifi is implemented by a interface device that has the capacity
// to connect to wifi networks.
type InterfaceWifi interface {
Interface
// Connect device to network
NetConnect(params WifiParams) error
// Disconnect device from network
NetDisconnect()
}
// InterfaceEthPollWifi is implemented by devices that connect to wifi networks
// and send/receive ethernet packets (OSI level 2).
type InterfaceEthPollWifi interface {
InterfaceWifi
InterfaceEthPoller
}
type Stack interface {
// GetHostByName returns the IP address of either a hostname or IPv4
// address in standard dot notation
// GetHostByName(name string) (netip.Addr, error)
// Addr returns IP address assigned to the interface, either by
// DHCP or statically
Addr() (netip.Addr, error)
// Berkely Sockets-like interface, Go-ified. See man page for socket(2), etc.
Socket(domain int, stype int, protocol int) (int, error)
Bind(sockfd int, ip netip.AddrPort) error
Connect(sockfd int, host string, ip netip.AddrPort) error
Listen(sockfd int, backlog int) error
Accept(sockfd int) (int, netip.AddrPort, error)
Send(sockfd int, buf []byte, flags int, deadline time.Time) (int, error)
Recv(sockfd int, buf []byte, flags int, deadline time.Time) (int, error)
Close(sockfd int) error
SetSockOpt(sockfd int, level int, opt int, value interface{}) error
}
// Resolver is implemented by DNS resolvers, notably Go's [net.DefaultResolver].
type Resolver interface {
// LookupNetIP looks up host using the local resolver.
// It returns a slice of that host's IP addresses of the type specified by
// network.
// The network must be one of "ip", "ip4" or "ip6".
LookupNetIP(ctx context.Context, network, host string) ([]netip.Addr, error)
}
// StackWifi is returned by `Probe` function for devices that communicate
// on the OSI level 4 (transport) layer.
type StackWifi interface {
InterfaceWifi
Stack
}
type WifiParams struct {
// Connect mode
ConnectMode ConnectMode
// SSID of Wifi AP
SSID string
// Passphrase of Wifi AP
Passphrase string
// Wifi authorization type
Auth AuthType
// Wifi country code as two-char string. E.g. "XX" for world-wide,
// "US" for USA, etc.
CountryCode string
}
type Event uint8
// Network events
const (
// The device's network connection is now UP
EventNetUp Event = iota
// The device's network connection is now DOWN
EventNetDown
)
type ConnectMode uint8
// Connect modes
const (
ConnectModeSTA = iota // Connect as Wifi station (default)
ConnectModeAP // Connect as Wifi Access Point
)
type AuthType uint8
// Wifi authorization types. Used when setting up an access point, or
// connecting to an access point
const (
AuthTypeWPA2 = iota // WPA2 authorization (default)
AuthTypeOpen // No authorization required (open)
AuthTypeWPA // WPA authorization
AuthTypeWPA2Mixed // WPA2/WPA mixed authorization
)
type WifiAutoconnectParams struct {
WifiParams
// Retries is how many attempts to connect before returning with a
// "Connect failed" error. Zero means infinite retries.
// Retries int // Probably should be implemented as a function
// Timeout duration for each connection attempt. The default zero
// value means 10sec.
ConnectTimeout time.Duration
// Watchdog ticker duration. On tick, the watchdog will check for
// downed connection or hardware fault and try to recover the
// connection. Set to zero to disable watchodog.
WatchdogTimeout time.Duration
}
func StartWifiAutoconnect(dev InterfaceWifi, cfg WifiAutoconnectParams) error {
if dev == nil {
return errors.New("nil device")
}
go func() {
// Wifi autoconnect algorithm in one place,
// no need to implement for every single netdever.
RECONNECT:
for i := 0; i < 4; i++ {
err := dev.NetConnect(cfg.WifiParams)
if err != nil {
time.Sleep(cfg.ConnectTimeout)
goto RECONNECT
}
// Once connected reset the retry counter.
i = 0
for cfg.WatchdogTimeout != 0 {
time.Sleep(cfg.WatchdogTimeout)
if dev.NetFlags()&net.FlagRunning == 0 {
goto RECONNECT
}
}
}
}()
return nil
}
-1
View File
@@ -13,7 +13,6 @@ var (
ErrConnectFailed = errors.New("Connect failed")
ErrConnectTimeout = errors.New("Connect timed out")
ErrMissingSSID = errors.New("Missing WiFi SSID")
ErrShortPassphrase = errors.New("Invalid Wifi Passphrase < 8 chars")
ErrAuthFailure = errors.New("Wifi authentication failure")
ErrAuthTypeNoGood = errors.New("Wifi authorization type not supported")
ErrConnectModeNoGood = errors.New("Connect mode not supported")
+1 -1
View File
@@ -1,4 +1,4 @@
//go:build ninafw && !arduino_mkrwifi1010
//go:build pyportal || arduino_nano33 || nano_rp2040 || metro_m4_airlift || matrixportal_m4
package probe
+8 -49
View File
@@ -22,13 +22,12 @@ func NewImage[T Color](width, height int) Image[T] {
}
var zeroColor T
var data unsafe.Pointer
switch {
case zeroColor.BitsPerPixel()%8 == 0:
if zeroColor.BitsPerPixel()%8 == 0 {
// Typical formats like RGB888 and RGB565.
// Each color starts at a whole byte offset from the start.
buf := make([]T, width*height)
data = unsafe.Pointer(&buf[0])
default:
} else {
// Formats like RGB444 that have 12 bits per pixel.
// We access these as bytes, so allocate the buffer as a byte slice.
bufBits := width * height * zeroColor.BitsPerPixel()
@@ -81,11 +80,10 @@ func (img Image[T]) Len() int {
func (img Image[T]) RawBuffer() []uint8 {
var zeroColor T
var numBytes int
switch {
case zeroColor.BitsPerPixel()%8 == 0:
if zeroColor.BitsPerPixel()%8 == 0 {
// Each color starts at a whole byte offset.
numBytes = int(unsafe.Sizeof(zeroColor)) * int(img.width) * int(img.height)
default:
} else {
// Formats like RGB444 that aren't a whole number of bytes.
numBits := zeroColor.BitsPerPixel() * int(img.width) * int(img.height)
numBytes = (numBits + 7) / 8 // round up (see NewImage)
@@ -101,20 +99,7 @@ func (img Image[T]) Size() (int, int) {
func (img Image[T]) setPixel(index int, c T) {
var zeroColor T
switch {
case zeroColor.BitsPerPixel() == 1:
// Monochrome.
x := index % int(img.width)
y := index / int(img.width)
offset := x + (y/8)*int(img.width)
ptr := (*byte)(unsafe.Add(img.data, offset))
if c != zeroColor {
*((*byte)(ptr)) |= 1 << uint8(y%8)
} else {
*((*byte)(ptr)) &^= 1 << uint8(y%8)
}
return
case zeroColor.BitsPerPixel()%8 == 0:
if zeroColor.BitsPerPixel()%8 == 0 {
// Each color starts at a whole byte offset.
// This is the easy case.
offset := index * int(unsafe.Sizeof(zeroColor))
@@ -162,15 +147,7 @@ func (img Image[T]) Get(x, y int) T {
var zeroColor T
index := y*int(img.width) + x // index into img.data
switch {
case zeroColor.BitsPerPixel() == 1:
// Monochrome.
var c Monochrome
offset := x + (y/8)*int(img.width)
ptr := (*byte)(unsafe.Add(img.data, offset))
c = (*ptr >> uint8(y%8) & 0x1) == 1
return any(c).(T)
case zeroColor.BitsPerPixel()%8 == 0:
if zeroColor.BitsPerPixel()%8 == 0 {
// Colors like RGB565, RGB888, etc.
offset := index * int(unsafe.Sizeof(zeroColor))
ptr := unsafe.Add(img.data, offset)
@@ -204,26 +181,8 @@ func (img Image[T]) Get(x, y int) T {
func (img Image[T]) FillSolidColor(color T) {
var zeroColor T
switch {
case zeroColor.BitsPerPixel() == 1:
// Monochrome.
var colorByte uint8
if color != zeroColor {
colorByte = 0xff
}
numBytes := int(img.width) * int(img.height) / 8
for i := 0; i < numBytes; i++ {
// TODO: this can be optimized a lot.
// - The store can be done as a 32-bit integer, after checking for
// alignment.
// - Perhaps the loop can be unrolled to improve copy performance.
ptr := (*byte)(unsafe.Add(img.data, i))
*((*byte)(ptr)) = colorByte
}
return
case zeroColor.BitsPerPixel()%8 == 0:
// Fast pass for colors of 8, 16, 24, etc bytes in size.
// Fast pass for colors of 8, 16, 24, etc bytes in size.
if zeroColor.BitsPerPixel()%8 == 0 {
ptr := img.data
for i := 0; i < img.Len(); i++ {
// TODO: this can be optimized a lot.
-100
View File
@@ -1,9 +1,7 @@
package pixel_test
import (
goimage "image"
"image/color"
"math/rand"
"testing"
"tinygo.org/x/drivers/pixel"
@@ -64,101 +62,3 @@ func TestImageRGB444BE(t *testing.T) {
}
}
}
func TestImageMonochrome(t *testing.T) {
image := pixel.NewImage[pixel.Monochrome](5, 3)
if width, height := image.Size(); width != 5 && height != 3 {
t.Errorf("image.Size(): expected 5, 3 but got %d, %d", width, height)
}
for _, expected := range []color.RGBA{
{R: 0xff, G: 0xff, B: 0xff},
{G: 0xff},
{R: 0xff, G: 0xff},
{G: 0xff, B: 0xff},
{R: 0x00},
{G: 0x00, A: 0xff},
{B: 0x00, A: 0xff},
} {
encoded := pixel.NewColor[pixel.Monochrome](expected.R, expected.G, expected.B)
image.Set(4, 2, encoded)
actual := image.Get(4, 2).RGBA()
switch {
case expected.R == 0 && expected.G == 0 && expected.B == 0:
// should be false eg black
if actual.R != 0 || actual.G != 0 || actual.B != 0 {
t.Errorf("failed to roundtrip color: expected %v but got %v", expected, actual)
}
case int(expected.R)+int(expected.G)+int(expected.B) > 128*3:
// should be true eg white
if actual.R == 0 || actual.G == 0 || actual.B == 0 {
t.Errorf("failed to roundtrip color: expected %v but got %v", expected, actual)
}
}
}
}
// Test pixel formats by filling them with noise and checking whether they
// contain the same data afterwards.
func TestImageNoise(t *testing.T) {
t.Run("RGB888", func(t *testing.T) {
testImageNoise[pixel.RGB888](t)
})
t.Run("RGB565BE", func(t *testing.T) {
testImageNoise[pixel.RGB565BE](t)
})
t.Run("RGB555", func(t *testing.T) {
testImageNoise[pixel.RGB555](t)
})
t.Run("RGB444BE", func(t *testing.T) {
testImageNoise[pixel.RGB444BE](t)
})
t.Run("Monochrome", func(t *testing.T) {
testImageNoise[pixel.Monochrome](t)
})
}
func testImageNoise[T pixel.Color](t *testing.T) {
// Create an image of a random width/height for extra testing.
width := rand.Int()%500 + 10
height := rand.Int()%500 + 10
t.Log("image size:", width, height)
// Create two images: the to-be-tested image object and a reference image.
img := pixel.NewImage[T](width, height)
ref := goimage.NewRGBA(goimage.Rect(0, 0, width, height))
// Fill the two images with noise.
for y := 0; y < height; y++ {
for x := 0; x < width; x++ {
// Set a random color in both images.
c := pixel.NewColor[T](uint8(rand.Uint32()), uint8(rand.Uint32()), uint8(rand.Uint32()))
img.Set(x, y, c)
ref.Set(x, y, c.RGBA())
}
}
// Compare the two images. They should match.
mismatch := 0
firstX := 0
firstY := 0
var firstExpected, firstActual color.RGBA
for y := 0; y < height; y++ {
for x := 0; x < width; x++ {
c := img.Get(x, y).RGBA()
r2, g2, b2, _ := ref.At(x, y).RGBA()
c2 := color.RGBA{R: uint8(r2 >> 8), G: uint8(g2 >> 8), B: uint8(b2 >> 8), A: 255}
if c != c2 {
mismatch++
if mismatch == 1 {
firstX = x
firstY = y
firstExpected = c
firstActual = c2
}
}
}
}
if mismatch != 0 {
t.Errorf("mismatch found: %d pixels are different (first diff at (%d, %d), expected %v, actual %v)", mismatch, firstX, firstY, firstExpected, firstActual)
}
}
+2 -33
View File
@@ -14,9 +14,9 @@ import (
// Pixel with a particular color, matching the underlying hardware of a
// particular display. Each pixel is at least 1 byte in size.
// The color format is sRGB (or close to it) in all cases except for 1-bit.
// The color format is sRGB (or close to it) in all cases.
type Color interface {
RGB888 | RGB565BE | RGB555 | RGB444BE | Monochrome
RGB888 | RGB565BE | RGB555 | RGB444BE
BaseColor
}
@@ -50,8 +50,6 @@ func NewColor[T Color](r, g, b uint8) T {
return any(NewRGB555(r, g, b)).(T)
case RGB444BE:
return any(NewRGB444BE(r, g, b)).(T)
case Monochrome:
return any(NewMonochrome(r, g, b)).(T)
default:
panic("unknown color format")
}
@@ -204,35 +202,6 @@ func (c RGB444BE) RGBA() color.RGBA {
return color
}
type Monochrome bool
func NewMonochrome(r, g, b uint8) Monochrome {
// Very simple black/white split.
// This isn't very accurate (especially for sRGB colors) but is close enough.
if int(r)+int(g)+int(b) > 128*3 { // light, convert to white
return Monochrome(true)
}
// dark, convert to black
return Monochrome(false)
}
func (c Monochrome) BitsPerPixel() int {
return 1
}
func (c Monochrome) RGBA() color.RGBA {
value := uint8(0)
if c {
value = 255
}
return color.RGBA{
R: value,
G: value,
B: value,
A: 255,
}
}
// Gamma brightness lookup table:
// https://victornpb.github.io/gamma-table-generator
// gamma = 0.45 steps = 256 range = 0-255
+17 -98
View File
@@ -17,7 +17,6 @@ import (
"sync"
"time"
"tinygo.org/x/drivers"
"tinygo.org/x/drivers/netdev"
"tinygo.org/x/drivers/netlink"
)
@@ -28,13 +27,13 @@ var _debug debug = debugBasic
//var _debug debug = debugBasic | debugNetdev | debugRpc
var (
version = "0.0.1"
driverName = "Realtek rtl8720dn Wifi network device driver (rtl8720dn)"
)
const (
O_NONBLOCK = 1 // note: different value than syscall.O_NONBLOCK (0x800)
RTW_MODE_STA = 0x00000001
defaultChannel = 6
O_NONBLOCK = 1 // note: different value than syscall.O_NONBLOCK (0x800)
RTW_MODE_STA = 0x00000001
)
type sock int32
@@ -103,22 +102,14 @@ func (r *rtl8720dn) connectToAP() error {
return netlink.ErrMissingSSID
}
if len(r.params.Passphrase) != 0 && len(r.params.Passphrase) < 8 {
return netlink.ErrShortPassphrase
}
if debugging(debugBasic) {
fmt.Printf("Connecting to Wifi SSID '%s'...", r.params.Ssid)
}
// Start the connection process
securityType := uint32(0) // RTW_SECURITY_OPEN
if len(r.params.Passphrase) != 0 {
securityType = 0x00400004 // RTW_SECURITY_WPA2_AES_PSK
}
securityType := uint32(0x00400004)
result := r.rpc_wifi_connect(r.params.Ssid, r.params.Passphrase, securityType, -1, 0)
if result != 0 {
if result == -1 {
if debugging(debugBasic) {
fmt.Printf("FAILED\r\n")
}
@@ -136,51 +127,6 @@ func (r *rtl8720dn) connectToAP() error {
return r.startDhcpc()
}
func (r *rtl8720dn) startDhcps() error {
if result := r.rpc_tcpip_adapter_dhcps_start(0); result == -1 {
return netdev.ErrStartingDHCPServer
}
return nil
}
func (r *rtl8720dn) startAP() error {
if len(r.params.Ssid) == 0 {
return netlink.ErrMissingSSID
}
if len(r.params.Passphrase) != 0 && len(r.params.Passphrase) < 8 {
return netlink.ErrShortPassphrase
}
if debugging(debugBasic) {
fmt.Printf("Starting Wifi AP as SSID '%s'...", r.params.Ssid)
}
// Start the connection process
securityType := uint32(0) // RTW_SECURITY_OPEN
if len(r.params.Passphrase) != 0 {
securityType = 0x00400004 // RTW_SECURITY_WPA2_AES_PSK
}
result := r.rpc_wifi_start_ap(r.params.Ssid, r.params.Passphrase, securityType, defaultChannel)
if result != 0 {
if debugging(debugBasic) {
fmt.Printf("FAILED\r\n")
}
return netlink.ErrConnectFailed
}
if debugging(debugBasic) {
fmt.Printf("LISTENING\r\n")
}
if r.notifyCb != nil {
r.notifyCb(netlink.EventNetUp)
}
return r.startDhcps()
}
func (r *rtl8720dn) showDriver() {
if r.driverShown {
return
@@ -188,7 +134,7 @@ func (r *rtl8720dn) showDriver() {
if debugging(debugBasic) {
fmt.Printf("\r\n")
fmt.Printf("%s\r\n\r\n", driverName)
fmt.Printf("Driver version : %s\r\n", drivers.Version)
fmt.Printf("Driver version : %s\r\n", version)
}
r.driverShown = true
}
@@ -292,27 +238,14 @@ func (r *rtl8720dn) netConnect(reset bool) error {
}
r.showDevice()
retry:
for i := 0; r.params.Retries == 0 || i < r.params.Retries; i++ {
switch r.params.ConnectMode {
case netlink.ConnectModeAP:
if err := r.startAP(); err != nil {
if err == netlink.ErrConnectFailed {
continue
}
return err
if err := r.connectToAP(); err != nil {
if err == netlink.ErrConnectFailed {
continue
}
break retry
default:
if err := r.connectToAP(); err != nil {
if err == netlink.ErrConnectFailed {
continue
}
return err
}
break retry
return err
}
break
}
if r.networkDown() {
@@ -504,12 +437,6 @@ func ipToName(ip netip.AddrPort) []byte {
return name
}
func nameToIp(name []byte) netip.AddrPort {
port := uint16(name[2])<<8 | uint16(name[3])
addr, _ := netip.AddrFromSlice(name[4:8])
return netip.AddrPortFrom(addr, port)
}
func (r *rtl8720dn) Bind(sockfd int, ip netip.AddrPort) error {
if debugging(debugNetdev) {
@@ -607,10 +534,10 @@ func (r *rtl8720dn) Listen(sockfd int, backlog int) error {
return nil
}
func (r *rtl8720dn) Accept(sockfd int) (int, netip.AddrPort, error) {
func (r *rtl8720dn) Accept(sockfd int, ip netip.AddrPort) (int, error) {
if debugging(debugNetdev) {
fmt.Printf("[Accept] sockfd: %d\r\n", sockfd)
fmt.Printf("[Accept] sockfd: %d, peer: %s\r\n", sockfd, ip)
}
r.mu.Lock()
@@ -619,12 +546,12 @@ func (r *rtl8720dn) Accept(sockfd int) (int, netip.AddrPort, error) {
var newSock int32
var lsock = sock(sockfd)
var socket = r.sockets[lsock]
var name = ipToName(netip.AddrPort{})
var name = ipToName(ip)
switch socket.protocol {
case netdev.IPPROTO_TCP:
default:
return -1, netip.AddrPort{}, netdev.ErrProtocolNotSupported
return -1, netdev.ErrProtocolNotSupported
}
for {
@@ -643,14 +570,6 @@ func (r *rtl8720dn) Accept(sockfd int) (int, netip.AddrPort, error) {
continue
}
// Get remote peer ip:port
namelen = uint32(len(name))
result := r.rpc_lwip_getpeername(int32(newSock), name, &namelen)
if result == -1 {
return -1, netip.AddrPort{}, fmt.Errorf("Getpeername failed")
}
raddr := nameToIp(name)
// If we've already seen this socket, we can re-use
// the socket and return it. But, only if the socket
// is closed. If it's not closed, we'll just come back
@@ -663,12 +582,12 @@ func (r *rtl8720dn) Accept(sockfd int) (int, netip.AddrPort, error) {
continue
}
// Reuse client socket
return int(newSock), raddr, nil
return int(newSock), nil
}
// Create new socket for client and return fd
r.sockets[sock(newSock)] = newSocket(socket.protocol)
return int(newSock), raddr, nil
return int(newSock), nil
}
}
+1 -23
View File
@@ -1,12 +1,6 @@
package servo
import (
"machine"
"errors"
)
var ErrInvalidAngle = errors.New("servo: invalid angle")
import "machine"
// PWM is the interface necessary for controlling typical servo motors.
type PWM interface {
@@ -86,19 +80,3 @@ func (s Servo) SetMicroseconds(microseconds int16) {
value := uint64(s.pwm.Top()) * uint64(microseconds) / (pwmPeriod / 1000)
s.pwm.Set(s.channel, uint32(value))
}
// SetAngle sets the angle of the servo in degrees. The angle should be between
// 0 and 180, where 0 is the minimum angle and 180 is the maximum angle.
// This function should work for most servos, but if it doesn't work for yours
// you can use SetMicroseconds directly instead.
func (s Servo) SetAngle(angle int) error {
if angle < 0 || angle > 180 {
return ErrInvalidAngle
}
// 0° is 1000µs, 180° is 2000µs. See explanation in SetMicroseconds.
microseconds := angle*1000/180 + 1000
s.SetMicroseconds(int16(microseconds))
return nil
}
-3
View File
@@ -80,7 +80,6 @@ tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/vl6
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/waveshare-epd/epd2in13/main.go
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/waveshare-epd/epd2in13x/main.go
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/waveshare-epd/epd4in2/main.go
tinygo build -size short -o ./build/test.hex -target=pico ./examples/waveshare-epd/epd2in66b/main.go
tinygo build -size short -o ./build/test.hex -target=circuitplay-express ./examples/ws2812
tinygo build -size short -o ./build/test.bin -target=m5stamp-c3 ./examples/ws2812
tinygo build -size short -o ./build/test.hex -target=feather-nrf52840 ./examples/is31fl3731/main.go
@@ -130,8 +129,6 @@ tinygo build -size short -o ./build/test.hex -target=microbit ./examples/ndir/ma
tinygo build -size short -o ./build/test.hex -target=arduino-nano33 ./examples/ndir/main_ndir.go
tinygo build -size short -o ./build/test.uf2 -target=pico ./examples/mpu9150/main.go
tinygo build -size short -o ./build/test.hex -target=macropad-rp2040 ./examples/sh1106/macropad_spi
tinygo build -size short -o ./build/test.hex -target=macropad-rp2040 ./examples/encoders/quadrature-interrupt
tinygo build -size short -o ./build/test.uf2 -target=pico ./examples/mcp9808/main.go
# network examples (espat)
tinygo build -size short -o ./build/test.hex -target=challenger-rp2040 ./examples/net/ntpclient/
# network examples (wifinina)
+6 -73
View File
@@ -11,17 +11,8 @@ import (
"tinygo.org/x/drivers"
"tinygo.org/x/drivers/internal/legacy"
"tinygo.org/x/drivers/pixel"
)
var (
errBufferSize = errors.New("invalid size buffer")
errOutOfRange = errors.New("out of screen range")
errNotImplemented = errors.New("not implemented")
)
type ResetValue [2]byte
// Device wraps I2C or SPI connection.
type Device struct {
bus Buser
@@ -31,8 +22,6 @@ type Device struct {
bufferSize int16
vccState VccMode
canReset bool
resetCol ResetValue
resetPage ResetValue
}
// Config is the configuration for the display
@@ -41,13 +30,6 @@ type Config struct {
Height int16
VccState VccMode
Address uint16
// ResetCol and ResetPage are used to reset the screen to 0x0
// This is useful for some screens that have a different size than 128x64
// For example, the Thumby's screen is 72x40
// The default values are normally set automatically based on the size.
// If you're using a different size, you might need to set these values manually.
ResetCol ResetValue
ResetPage ResetValue
}
type I2CBus struct {
@@ -97,7 +79,6 @@ func NewSPI(bus drivers.SPI, dcPin, resetPin, csPin machine.Pin) Device {
// Configure initializes the display with default configuration
func (d *Device) Configure(cfg Config) {
var zeroReset ResetValue
if cfg.Width != 0 {
d.width = cfg.Width
} else {
@@ -116,16 +97,6 @@ func (d *Device) Configure(cfg Config) {
} else {
d.vccState = SWITCHCAPVCC
}
if cfg.ResetCol != zeroReset {
d.resetCol = cfg.ResetCol
} else {
d.resetCol = ResetValue{0, uint8(d.width - 1)}
}
if cfg.ResetPage != zeroReset {
d.resetPage = cfg.ResetPage
} else {
d.resetPage = ResetValue{0, uint8(d.height/8) - 1}
}
d.bufferSize = d.width * d.height / 8
d.buffer = make([]byte, d.bufferSize)
d.canReset = cfg.Address != 0 || d.width != 128 || d.height != 64 // I2C or not 128x64
@@ -215,11 +186,11 @@ func (d *Device) Display() error {
// Since we're printing the whole buffer, avoid resetting it in this case
if d.canReset {
d.Command(COLUMNADDR)
d.Command(d.resetCol[0])
d.Command(d.resetCol[1])
d.Command(0)
d.Command(uint8(d.width - 1))
d.Command(PAGEADDR)
d.Command(d.resetPage[0])
d.Command(d.resetPage[1])
d.Command(0)
d.Command(uint8(d.height/8) - 1)
}
return d.Tx(d.buffer, false)
@@ -252,7 +223,8 @@ func (d *Device) GetPixel(x int16, y int16) bool {
// SetBuffer changes the whole buffer at once
func (d *Device) SetBuffer(buffer []byte) error {
if int16(len(buffer)) != d.bufferSize {
return errBufferSize
//return ErrBuffer
return errors.New("wrong size buffer")
}
for i := int16(0); i < d.bufferSize; i++ {
d.buffer[i] = buffer[i]
@@ -344,42 +316,3 @@ func (b *SPIBus) tx(data []byte, isCommand bool) error {
func (d *Device) Size() (w, h int16) {
return d.width, d.height
}
// DrawBitmap copies the bitmap to the screen at the given coordinates.
func (d *Device) DrawBitmap(x, y int16, bitmap pixel.Image[pixel.Monochrome]) error {
width, height := bitmap.Size()
if x < 0 || x+int16(width) > d.width || y < 0 || y+int16(height) > d.height {
return errOutOfRange
}
for i := 0; i < width; i++ {
for j := 0; j < height; j++ {
d.SetPixel(x+int16(i), y+int16(j), bitmap.Get(i, j).RGBA())
}
}
return nil
}
// Rotation returns the currently configured rotation.
func (d *Device) Rotation() drivers.Rotation {
return drivers.Rotation0
}
// SetRotation changes the rotation of the device (clock-wise).
// Would have to be implemented in software for this device.
func (d *Device) SetRotation(rotation drivers.Rotation) error {
return errNotImplemented
}
// Set the sleep mode for this display. When sleeping, the panel uses a lot
// less power. The display won't show an image anymore, but the memory contents
// should be kept.
func (d *Device) Sleep(sleepEnabled bool) error {
if sleepEnabled {
d.Command(DISPLAYOFF)
} else {
d.Command(DISPLAYON)
}
return nil
}
-30
View File
@@ -1,30 +0,0 @@
package uc8151
// LUTType is the look-up table for the display
type LUTType [42]uint8
type LUTSet struct {
VCOM LUTType
WW LUTType
BW LUTType
WB LUTType
BB LUTType
}
func (lut *LUTType) Clear() {
for i := range lut {
lut[i] = 0
}
}
func (lut *LUTType) SetRow(row int, pat uint8, dur [4]uint8, rep uint8) error {
index := row * 6
lut[index] = pat
lut[index+1] = dur[0]
lut[index+2] = dur[1]
lut[index+3] = dur[2]
lut[index+4] = dur[3]
lut[index+5] = rep
return nil
}
+7 -12
View File
@@ -1,7 +1,5 @@
package uc8151
import "tinygo.org/x/drivers"
// Registers
const (
// Display resolution
@@ -143,16 +141,13 @@ const (
HZ_100 = 0b00111010
HZ_200 = 0b00111001
// deprecated constants, just here for backward compatibility.
NO_ROTATION = drivers.Rotation0
ROTATION_90 = drivers.Rotation90
ROTATION_180 = drivers.Rotation180
ROTATION_270 = drivers.Rotation270
NO_ROTATION Rotation = 0
ROTATION_90 Rotation = 1 // 90 degrees clock-wise rotation
ROTATION_180 Rotation = 2
ROTATION_270 Rotation = 3
DEFAULT Speed = 0
SLOW Speed = 1
MEDIUM Speed = 2
FAST Speed = 3
FASTER Speed = 4
TURBO Speed = 5
MEDIUM Speed = 1
FAST Speed = 2
TURBO Speed = 3
)
+349 -196
View File
@@ -1,7 +1,6 @@
// Package uc8151 implements a driver for e-ink displays controlled by UC8151
//
// Inspired by https://github.com/pimoroni/pimoroni-pico/blob/main/drivers/uc8151/uc8151.cpp
// Additional inspiration from https://github.com/antirez/uc8151_micropython
// Datasheet: https://www.buydisplay.com/download/ic/UC8151C.pdf
package uc8151 // import "tinygo.org/x/drivers/uc8151"
@@ -12,43 +11,35 @@ import (
"time"
"tinygo.org/x/drivers"
"tinygo.org/x/drivers/pixel"
)
var (
errOutOfRange = errors.New("out of screen range")
)
type Config struct {
Width int16
Height int16
Rotation drivers.Rotation // Rotation is clock-wise
Speed Speed // Value from DEFAULT, SLOW, MEDIUM, FAST, FASTER, TURBO
Blocking bool // block on calls to display or return immediately
FlickerFree bool // if we should avoid flickering
UpdateAfter int // if we are using flicker-free mode, how often we should update the screen
Width int16
Height int16
Rotation Rotation // Rotation is clock-wise
Speed Speed // Value from DEFAULT, MEDIUM, FAST, TURBO
Blocking bool
}
type Device struct {
bus drivers.SPI
cs machine.Pin
dc machine.Pin
rst machine.Pin
busy machine.Pin
width int16
height int16
buffer []uint8
bufferLength uint32
rotation drivers.Rotation
speed Speed
blocking bool
flickerFree bool
updateCount, updateAfter int
bus drivers.SPI
cs machine.Pin
dc machine.Pin
rst machine.Pin
busy machine.Pin
width int16
height int16
buffer []uint8
bufferLength uint32
rotation Rotation
speed Speed
blocking bool
}
type Rotation uint8
type Speed uint8
// New returns a new uc8151 driver. Pass in a fully configured SPI bus.
// New returns a new epd2in13x driver. Pass in a fully configured SPI bus.
func New(bus drivers.SPI, csPin, dcPin, rstPin, busyPin machine.Pin) Device {
csPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
dcPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
@@ -78,8 +69,6 @@ func (d *Device) Configure(cfg Config) {
d.rotation = cfg.Rotation
d.speed = cfg.Speed
d.blocking = cfg.Blocking
d.flickerFree = cfg.FlickerFree
d.updateAfter = cfg.UpdateAfter
d.bufferLength = (uint32(d.width) * uint32(d.height)) / 8
d.buffer = make([]uint8, d.bufferLength)
for i := uint32(0); i < d.bufferLength; i++ {
@@ -95,14 +84,14 @@ func (d *Device) Configure(cfg Config) {
d.SendData(RES_128x296 | LUT_REG | FORMAT_BW | SHIFT_RIGHT | BOOSTER_ON | RESET_NONE | SCAN_UP)
}
d.SetLUT(d.speed, d.flickerFree)
d.SetLUT(d.speed)
d.SendCommand(PWR)
d.SendData(VDS_INTERNAL | VDG_INTERNAL)
d.SendData(VCOM_VD | VGHL_16V)
d.SendData(0b100110) // +10v VDH
d.SendData(0b100110) // -10v VDL
d.SendData(0b000011) // VDHR default (For red pixels, not used here)
d.SendData(VCOM_VG | VGHL_16V)
d.SendData(0x2B)
d.SendData(0x2B)
d.SendData(0x2B)
d.SendCommand(PON)
d.WaitUntilIdle()
@@ -113,7 +102,7 @@ func (d *Device) Configure(cfg Config) {
d.SendData(START_10MS | STRENGTH_3 | OFF_6_58US)
d.SendCommand(PFS)
d.SendData(FRAMES_4)
d.SendData(FRAMES_1)
d.SendCommand(TSE)
d.SendData(TEMP_INTERNAL | OFFSET_0)
@@ -122,13 +111,14 @@ func (d *Device) Configure(cfg Config) {
d.SendData(0x22)
d.SendCommand(CDI)
d.SendData(0b11_00_1100)
d.SendData(0x4C) // 4C //5C
d.SendCommand(PLL)
d.SendData(HZ_100)
d.SendCommand(POF)
d.WaitUntilIdle()
}
// Reset resets the device
@@ -145,30 +135,31 @@ func (d *Device) PowerOff() {
d.SendCommand(POF)
}
// PowerOn power on the device
func (d *Device) PowerOn() {
d.SendCommand(PON)
}
// SendCommand sends a command to the display
func (d *Device) SendCommand(command uint8) {
d.dc.Low()
d.cs.Low()
d.bus.Transfer(command)
d.cs.High()
d.sendDataCommand(true, command)
}
// SendData sends a data byte to the display
func (d *Device) SendData(data ...uint8) {
d.dc.High()
func (d *Device) SendData(data uint8) {
d.sendDataCommand(false, data)
}
// sendDataCommand sends image data or a command to the screen
func (d *Device) sendDataCommand(isCommand bool, data uint8) {
if isCommand {
d.dc.Low()
} else {
d.dc.High()
}
d.cs.Low()
d.bus.Tx(data, nil)
d.bus.Transfer(data)
d.cs.High()
}
// SetPixel modifies the internal buffer in a single pixel.
// The display have 2 colors: black and white
// We use RGBA(0, 0, 0) as white (transparent)
// We use RGBA(0,0,0, 255) as white (transparent)
// Anything else as black
func (d *Device) SetPixel(x int16, y int16, c color.RGBA) {
x, y = d.xy(x, y)
@@ -177,60 +168,31 @@ func (d *Device) SetPixel(x int16, y int16, c color.RGBA) {
return
}
byteIndex := x/8 + y*(d.width/8)
if c.R != 0 || c.G != 0 || c.B != 0 {
d.buffer[byteIndex] |= 0x80 >> uint8(x%8)
} else {
if c.R == 0 && c.G == 0 && c.B == 0 { // TRANSPARENT / WHITE
d.buffer[byteIndex] &^= 0x80 >> uint8(x%8)
} else { // WHITE / EMPTY
d.buffer[byteIndex] |= 0x80 >> uint8(x%8)
}
}
// DrawBitmap copies the bitmap to the screen at the given coordinates.
func (d *Device) DrawBitmap(x, y int16, bitmap pixel.Image[pixel.Monochrome]) error {
dw, dh := d.Size()
bw, bh := bitmap.Size()
if x < 0 || x+int16(bw) > dw || y < 0 || y+int16(bh) > dh {
return errOutOfRange
}
for i := 0; i < bw; i++ {
for j := 0; j < bh; j++ {
d.SetPixel(x+int16(i), y+int16(j), bitmap.Get(i, j).RGBA())
}
}
return nil
}
// Display sends the buffer to the screen.
func (d *Device) Display() error {
if d.blocking {
d.WaitUntilIdle()
}
if d.flickerFree && d.updateAfter != 0 && d.updateCount%d.updateAfter == 0 {
// we need full refresh here
d.SetLUT(MEDIUM, false)
} else {
d.SetLUT(d.speed, d.flickerFree)
}
d.updateCount++
d.PowerOn()
d.SendCommand(PON)
d.SendCommand(PTOU)
d.SendCommand(DTM2)
d.SendData(d.buffer...)
for i := uint32(0); i < d.bufferLength; i++ {
d.SendData(d.buffer[i])
}
d.SendCommand(DSP)
d.SendCommand(DRF)
d.SetLUT(d.speed, d.flickerFree)
if d.blocking {
d.WaitUntilIdle()
d.PowerOff()
}
return nil
}
@@ -246,14 +208,13 @@ func (d *Device) DisplayRect(x int16, y int16, width int16, height int16) error
if x < 0 || y < 0 || x >= d.width || y >= d.height || width < 0 || height < 0 {
return errors.New("wrong rectangle")
}
switch d.rotation {
case drivers.Rotation0:
if d.rotation == ROTATION_90 {
width, height = height, width
x -= width
case drivers.Rotation90:
} else if d.rotation == ROTATION_180 {
x -= width - 1
y -= height - 1
case drivers.Rotation180:
} else if d.rotation == ROTATION_270 {
width, height = height, width
y -= height
}
@@ -301,12 +262,6 @@ func (d *Device) DisplayRect(x int16, y int16, width int16, height int16) error
// ClearDisplay erases the device SRAM
func (d *Device) ClearDisplay() {
ff := d.flickerFree
d.flickerFree = false
defer func() {
d.flickerFree = ff
}()
d.ClearBuffer()
d.Display()
}
@@ -314,7 +269,7 @@ func (d *Device) ClearDisplay() {
// WaitUntilIdle waits until the display is ready
func (d *Device) WaitUntilIdle() {
for !d.busy.Get() {
time.Sleep(10 * time.Millisecond)
time.Sleep(100 * time.Millisecond)
}
}
@@ -332,32 +287,15 @@ func (d *Device) ClearBuffer() {
// Size returns the current size of the display.
func (d *Device) Size() (w, h int16) {
if d.rotation == drivers.Rotation90 || d.rotation == drivers.Rotation270 {
if d.rotation == ROTATION_90 || d.rotation == ROTATION_270 {
return d.height, d.width
}
return d.width, d.height
}
// Rotation returns the currently configured rotation.
func (d *Device) Rotation() drivers.Rotation {
return d.rotation
}
// SetRotation changes the rotation (clock-wise) of the device
func (d *Device) SetRotation(rotation drivers.Rotation) error {
func (d *Device) SetRotation(rotation Rotation) {
d.rotation = rotation
return nil
}
// Set the sleep mode for this display.
func (d *Device) Sleep(sleepEnabled bool) error {
if sleepEnabled {
d.PowerOff()
return nil
}
d.PowerOn()
return nil
}
// SetBlocking changes the blocking flag of the device
@@ -365,16 +303,16 @@ func (d *Device) SetBlocking(blocking bool) {
d.blocking = blocking
}
// xy changes the coordinates according to the rotation
// xy chages the coordinates according to the rotation
func (d *Device) xy(x, y int16) (int16, int16) {
switch d.rotation {
case drivers.Rotation0:
case NO_ROTATION:
return x, y
case drivers.Rotation90:
case ROTATION_90:
return d.width - y - 1, x
case drivers.Rotation180:
case ROTATION_180:
return d.width - x - 1, d.height - y - 1
case drivers.Rotation270:
case ROTATION_270:
return y, d.height - x - 1
}
return x, y
@@ -400,81 +338,296 @@ func (d *Device) Invert(invert bool) {
}
}
// SetLUT sets the look up tables for full or partial updates based on
// the speed and flicker-free mode.
// Based on code from https://github.com/antirez/uc8151_micropython
func (d *Device) SetLUT(speed Speed, flickerFree bool) error {
var lut LUTSet
// SetLUT sets the look up tables for full or partial updates
func (d *Device) SetLUT(speed Speed) {
switch speed {
case MEDIUM:
var lut = [44]uint8{
0x00, 0x16, 0x16, 0x0d, 0x00, 0x01,
0x00, 0x23, 0x23, 0x00, 0x00, 0x02,
0x00, 0x16, 0x16, 0x0d, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00,
}
d.SendCommand(LUT_VCOM)
for i := 0; i < 44; i++ {
d.SendData(lut[i])
}
lut = [44]uint8{
0x54, 0x16, 0x16, 0x0d, 0x00, 0x01,
0x60, 0x23, 0x23, 0x00, 0x00, 0x02,
0xa8, 0x16, 0x16, 0x0d, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
d.SendCommand(LUT_WW)
// do not send last two bytes
for i := 0; i < 42; i++ {
d.SendData(lut[i])
}
// Num. of frames for single direction change.
period := 64
p := uint8(period / (2 ^ (int(speed) - 1)))
if p < 1 {
p = 1
lut = [44]uint8{
0x54, 0x16, 0x16, 0x0d, 0x00, 0x01,
0x60, 0x23, 0x23, 0x00, 0x00, 0x02,
0xa8, 0x16, 0x16, 0x0d, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
d.SendCommand(LUT_BW)
// do not send last two bytes
for i := 0; i < 42; i++ {
d.SendData(lut[i])
}
lut = [44]uint8{
0xa8, 0x16, 0x16, 0x0d, 0x00, 0x01,
0x60, 0x23, 0x23, 0x00, 0x00, 0x02,
0x54, 0x16, 0x16, 0x0d, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
d.SendCommand(LUT_WB)
// do not send last two bytes
for i := 0; i < 42; i++ {
d.SendData(lut[i])
}
lut = [44]uint8{
0xa8, 0x16, 0x16, 0x0d, 0x00, 0x01,
0x60, 0x23, 0x23, 0x00, 0x00, 0x02,
0x54, 0x16, 0x16, 0x0d, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
d.SendCommand(LUT_BB)
// do not send last two bytes
for i := 0; i < 42; i++ {
d.SendData(lut[i])
}
break
case FAST:
var lut = [44]uint8{
0x00, 0x04, 0x04, 0x07, 0x00, 0x01,
0x00, 0x0c, 0x0c, 0x00, 0x00, 0x02,
0x00, 0x04, 0x04, 0x07, 0x00, 0x02,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00,
}
d.SendCommand(LUT_VCOM)
for i := 0; i < 44; i++ {
d.SendData(lut[i])
}
lut = [44]uint8{
0x54, 0x04, 0x04, 0x07, 0x00, 0x01,
0x60, 0x0c, 0x0c, 0x00, 0x00, 0x02,
0xa8, 0x04, 0x04, 0x07, 0x00, 0x02,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
d.SendCommand(LUT_WW)
// do not send last two bytes
for i := 0; i < 42; i++ {
d.SendData(lut[i])
}
lut = [44]uint8{
0x54, 0x04, 0x04, 0x07, 0x00, 0x01,
0x60, 0x0c, 0x0c, 0x00, 0x00, 0x02,
0xa8, 0x04, 0x04, 0x07, 0x00, 0x02,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
d.SendCommand(LUT_BW)
// do not send last two bytes
for i := 0; i < 42; i++ {
d.SendData(lut[i])
}
lut = [44]uint8{
0xa8, 0x04, 0x04, 0x07, 0x00, 0x01,
0x60, 0x0c, 0x0c, 0x00, 0x00, 0x02,
0x54, 0x04, 0x04, 0x07, 0x00, 0x02,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
d.SendCommand(LUT_WB)
// do not send last two bytes
for i := 0; i < 42; i++ {
d.SendData(lut[i])
}
lut = [44]uint8{
0xa8, 0x04, 0x04, 0x07, 0x00, 0x01,
0x60, 0x0c, 0x0c, 0x00, 0x00, 0x02,
0x54, 0x04, 0x04, 0x07, 0x00, 0x02,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
d.SendCommand(LUT_BB)
// do not send last two bytes
for i := 0; i < 42; i++ {
d.SendData(lut[i])
}
break
case TURBO:
var lut = [44]uint8{
0x00, 0x01, 0x01, 0x02, 0x00, 0x01,
0x00, 0x02, 0x02, 0x00, 0x00, 0x02,
0x00, 0x02, 0x02, 0x03, 0x00, 0x02,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00,
}
d.SendCommand(LUT_VCOM)
for i := 0; i < 44; i++ {
d.SendData(lut[i])
}
lut = [44]uint8{
0x54, 0x01, 0x01, 0x02, 0x00, 0x01,
0x60, 0x02, 0x02, 0x00, 0x00, 0x02,
0xa8, 0x02, 0x02, 0x03, 0x00, 0x02,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
d.SendCommand(LUT_WW)
// do not send last two bytes
for i := 0; i < 42; i++ {
d.SendData(lut[i])
}
lut = [44]uint8{
0x54, 0x01, 0x01, 0x02, 0x00, 0x01,
0x60, 0x02, 0x02, 0x00, 0x00, 0x02,
0xa8, 0x02, 0x02, 0x03, 0x00, 0x02,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
d.SendCommand(LUT_BW)
// do not send last two bytes
for i := 0; i < 42; i++ {
d.SendData(lut[i])
}
lut = [44]uint8{
0xa8, 0x01, 0x01, 0x02, 0x00, 0x01,
0x60, 0x02, 0x02, 0x00, 0x00, 0x02,
0x54, 0x02, 0x02, 0x03, 0x00, 0x02,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
d.SendCommand(LUT_WB)
// do not send last two bytes
for i := 0; i < 42; i++ {
d.SendData(lut[i])
}
lut = [44]uint8{
0xa8, 0x01, 0x01, 0x02, 0x00, 0x01,
0x60, 0x02, 0x02, 0x00, 0x00, 0x02,
0x54, 0x02, 0x02, 0x03, 0x00, 0x02,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
d.SendCommand(LUT_BB)
// do not send last two bytes
for i := 0; i < 42; i++ {
d.SendData(lut[i])
}
break
default:
var lut = [44]uint8{
0x00, 0x64, 0x64, 0x37, 0x00, 0x01,
0x00, 0x8c, 0x8c, 0x00, 0x00, 0x04,
0x00, 0x64, 0x64, 0x37, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00,
}
d.SendCommand(LUT_VCOM)
for i := 0; i < 44; i++ {
d.SendData(lut[i])
}
lut = [44]uint8{
0x54, 0x64, 0x64, 0x37, 0x00, 0x01,
0x60, 0x8c, 0x8c, 0x00, 0x00, 0x04,
0xa8, 0x64, 0x64, 0x37, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
d.SendCommand(LUT_WW)
// do not send last two bytes
for i := 0; i < 42; i++ {
d.SendData(lut[i])
}
lut = [44]uint8{
0x54, 0x64, 0x64, 0x37, 0x00, 0x01,
0x60, 0x8c, 0x8c, 0x00, 0x00, 0x04,
0xa8, 0x64, 0x64, 0x37, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
d.SendCommand(LUT_BW)
// do not send last two bytes
for i := 0; i < 42; i++ {
d.SendData(lut[i])
}
lut = [44]uint8{
0xa8, 0x64, 0x64, 0x37, 0x00, 0x01,
0x60, 0x8c, 0x8c, 0x00, 0x00, 0x04,
0x54, 0x64, 0x64, 0x37, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
d.SendCommand(LUT_WB)
// do not send last two bytes
for i := 0; i < 42; i++ {
d.SendData(lut[i])
}
lut = [44]uint8{
0xa8, 0x64, 0x64, 0x37, 0x00, 0x01,
0x60, 0x8c, 0x8c, 0x00, 0x00, 0x04,
0x54, 0x64, 0x64, 0x37, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
d.SendCommand(LUT_BB)
// do not send last two bytes
for i := 0; i < 42; i++ {
d.SendData(lut[i])
}
break
}
// Num. of frames for back-and-forth change.
hperiod := period % 2
hp := uint8(hperiod / (2 ^ (int(speed) - 1)))
if hp < 1 {
hp = 1
}
if speed < FAST && !flickerFree {
// For low speed everything is charge-neutral, even WB/BW.
// Phase 1: long go-inverted-color.
lut.VCOM.SetRow(0, 0x00, [4]uint8{p, 0x00, 0x00, 0x00}, 0x02)
lut.BW.SetRow(0, 0b01_000000, [4]uint8{p, 0x00, 0x00, 0x00}, 0x02)
lut.WB.SetRow(0, 0b10_000000, [4]uint8{p, 0x00, 0x00, 0x00}, 0x02)
// Phase 2: short ping/pong.
lut.VCOM.SetRow(1, 0x00, [4]uint8{hp, hp, 0x00, 0x00}, 0x02)
lut.BW.SetRow(1, 0b10_01_0000, [4]uint8{hp, hp, 0x00, 0x00}, 0x01)
lut.WB.SetRow(1, 0b01_10_0000, [4]uint8{hp, hp, 0x00, 0x00}, 0x01)
// Phase 3: long go-target-color.
lut.VCOM.SetRow(2, 0x00, [4]uint8{p, 0x00, 0x00, 0x00}, 0x02)
lut.BW.SetRow(2, 0b10_000000, [4]uint8{p, 0x00, 0x00, 0x00}, 0x02)
lut.WB.SetRow(2, 0b01_000000, [4]uint8{p, 0x00, 0x00, 0x00}, 0x02)
// For this speed, we use the same LUTs for WW/BB as well.
copy(lut.WW[:], lut.BW[:])
copy(lut.BB[:], lut.WB[:])
} else {
// Speed >= FAST
// For greater than 3 we use non charge-neutral LUTs for WB/BW
// since the inpulse is short and it gets reversed when the
// pixel changes color, so that's not a problem for the display,
// however we still need to use charge-neutral LUTs for WW/BB.
lut.VCOM.SetRow(0, 0x00, [4]uint8{p, p, p, p}, 0x01)
lut.BW.SetRow(0, 0b10_00_00_00, [4]uint8{p * 4, 0x00, 0x00, 0x00}, 0x01)
lut.WB.SetRow(0, 0b01_00_00_00, [4]uint8{p * 4, 0x00, 0x00, 0x00}, 0x01)
lut.WW.SetRow(0, 0b01_10_00_00, [4]uint8{p * 2, p * 2, 0x00, 0x00}, 0x01)
lut.BB.SetRow(0, 0b10_01_00_00, [4]uint8{p * 2, p * 2, 0x00, 0x00}, 0x01)
}
if flickerFree {
// If no flickering mode is enabled, we use an empty
// waveform BB and WW. The screen will need to be periodically fully refreshed.
lut.WW.Clear()
lut.BB.Clear()
}
d.SendCommand(LUT_VCOM)
d.SendData(append(lut.VCOM[:], []uint8{0, 0}...)...)
d.SendCommand(LUT_BW)
d.SendData(lut.BW[:]...)
d.SendCommand(LUT_WB)
d.SendData(lut.WB[:]...)
d.SendCommand(LUT_WW)
d.SendData(lut.WW[:]...)
d.SendCommand(LUT_BB)
d.SendData(lut.BB[:]...)
return nil
}
+1 -1
View File
@@ -2,4 +2,4 @@ package drivers
// Version returns a user-readable string showing the version of the drivers package for support purposes.
// Update this value before release of new version of software.
const Version = "0.28.0"
const Version = "0.26.0"
-287
View File
@@ -1,287 +0,0 @@
// Package epd2in66b implements a driver for the Waveshare 2.66inch E-Paper E-Ink Display Module (B)
// for Raspberry Pi Pico, 296×152, Red / Black / White
// Datasheet: https://files.waveshare.com/upload/e/ec/2.66inch-e-paper-b-specification.pdf
package epd2in66b
import (
"image/color"
"machine"
"time"
"tinygo.org/x/drivers"
)
const (
displayWidth = 152
displayHeight = 296
)
const Baudrate = 4_000_000 // 4 MHz
type Config struct {
ResetPin machine.Pin
DataPin machine.Pin
ChipSelectPin machine.Pin
BusyPin machine.Pin
}
type Device struct {
bus drivers.SPI
cs machine.Pin
dc machine.Pin
rst machine.Pin
busy machine.Pin
blackBuffer []byte
redBuffer []byte
}
// New allocates a new device.
// The bus is expected to be configured and ready for use.
func New(bus drivers.SPI) Device {
pixelCount := displayWidth * displayHeight
bufLen := pixelCount / 8
return Device{
bus: bus,
blackBuffer: make([]byte, bufLen),
redBuffer: make([]byte, bufLen),
}
}
// Configure configures the device and its pins.
func (d *Device) Configure(c Config) error {
d.cs = c.ChipSelectPin
d.dc = c.DataPin
d.rst = c.ResetPin
d.busy = c.BusyPin
d.cs.Configure(machine.PinConfig{Mode: machine.PinOutput})
d.dc.Configure(machine.PinConfig{Mode: machine.PinOutput})
d.rst.Configure(machine.PinConfig{Mode: machine.PinOutput})
d.busy.Configure(machine.PinConfig{Mode: machine.PinInput})
return nil
}
func (d *Device) Size() (x, y int16) {
return displayWidth, displayHeight
}
// SetPixel modifies the internal buffer in a single pixel.
// The display has 3 colors: red, black and white
//
// - white = RGBA(255,255,255, 1-255)
// - red = RGBA(1-255,0,0,1-255)
// - Anything else as black
func (d *Device) SetPixel(x int16, y int16, c color.RGBA) {
if x < 0 || x >= displayWidth || y < 0 || y >= displayHeight {
return
}
bytePos, bitPos := pos(x, y, displayWidth)
if c.R == 0xff && c.G == 0xff && c.B == 0xff && c.A > 0 { // white
set(d.blackBuffer, bytePos, bitPos)
unset(d.redBuffer, bytePos, bitPos)
} else if c.R != 0 && c.G == 0 && c.B == 0 && c.A > 0 { // red-ish
set(d.blackBuffer, bytePos, bitPos)
set(d.redBuffer, bytePos, bitPos)
} else { // black or other
unset(d.blackBuffer, bytePos, bitPos)
unset(d.redBuffer, bytePos, bitPos)
}
}
func set(buf []byte, bytePos, bitPos int) {
buf[bytePos] |= 0x1 << bitPos
}
func unset(buf []byte, bytePos, bitPos int) {
buf[bytePos] &^= 0x1 << bitPos
}
func pos(x, y, stride int16) (bytePos int, bitPos int) {
p := int(x) + int(y)*int(stride)
bytePos = p / 8
// reverse bit position as it is reversed on the device's buffer
bitPos = 7 - p%8
return bytePos, bitPos
}
func (d *Device) Display() error {
// Write RAM (Black White) / RAM 0x24
// 1 == white, 0 == black
if err := d.sendCommandByte(0x24); err != nil {
return err
}
if err := d.sendData(d.blackBuffer); err != nil {
return err
}
// Write RAM (RED) / RAM 0x26)
// 0 == blank, 1 == red
if err := d.sendCommandByte(0x26); err != nil {
return err
}
if err := d.sendData(d.redBuffer); err != nil {
return err
}
return d.turnOnDisplay()
}
func (d *Device) ClearBuffer() {
fill(d.redBuffer, 0x00)
fill(d.blackBuffer, 0xff)
}
func (d *Device) turnOnDisplay() error {
// also documented as 'Master Activation'
if err := d.sendCommandByte(0x20); err != nil {
return err
}
d.WaitUntilIdle()
return nil
}
func (d *Device) Reset() error {
d.hwReset()
d.WaitUntilIdle()
// soft reset & set defaults
if err := d.sendCommandByte(0x12); err != nil {
return err
}
d.WaitUntilIdle()
// data entry mode setting
if err := d.sendCommandSequence([]byte{0x11, 0x03}); err != nil {
return err
}
if err := d.setWindow(0, displayWidth-1, 0, displayHeight-1); err != nil {
return err
}
// display update control 1 - resolution setting
if err := d.sendCommandSequence([]byte{0x21, 0x00, 0x80}); err != nil {
return err
}
if err := d.setCursor(0, 0); err != nil {
return err
}
d.WaitUntilIdle()
return nil
}
func (d *Device) setCursor(x, y uint16) error {
// Set RAM X address counter
if err := d.sendCommandSequence([]byte{0x4e, byte(x & 0x1f)}); err != nil {
return err
}
// Set RAM Y address counter
yLo := byte(y)
yHi := byte(y>>8) & 0x1
if err := d.sendCommandSequence([]byte{0x4f, yLo, yHi}); err != nil {
return err
}
return nil
}
func (d *Device) hwReset() {
d.rst.High()
time.Sleep(50 * time.Millisecond)
d.rst.Low()
time.Sleep(2 * time.Millisecond)
d.rst.High()
time.Sleep(50 * time.Millisecond)
}
func (d *Device) setWindow(xstart, xend, ystart, yend int16) error {
// set RAM X-address start / end position
d1 := byte((xstart >> 3) & 0x1f)
d2 := byte((xend >> 3) & 0x1f)
if err := d.sendCommandSequence([]byte{0x44, d1, d2}); err != nil {
return err
}
// set RAM Y-address start / end position
ystartLo := byte(ystart)
ystartHi := byte(ystart>>8) & 0x1
yendLo := byte(yend)
yendHi := byte(yend>>8) & 0x1
return d.sendCommandSequence([]byte{0x45, ystartLo, ystartHi, yendLo, yendHi})
}
func (d *Device) WaitUntilIdle() {
// give it some time to get busy
time.Sleep(50 * time.Millisecond)
for d.busy.Get() { // high = busy
time.Sleep(10 * time.Millisecond)
}
// give it some extra time
time.Sleep(50 * time.Millisecond)
}
// sendCommandSequence sends the first byte in the buffer as a 'command' and all following bytes as data
func (d *Device) sendCommandSequence(seq []byte) error {
err := d.sendCommandByte(seq[0])
if err != nil {
return err
}
for i := 1; i < len(seq); i++ {
err = d.sendDataByte(seq[i])
if err != nil {
return err
}
}
return nil
}
func (d *Device) sendCommandByte(b byte) error {
d.dc.Low()
d.cs.Low()
_, err := d.bus.Transfer(b)
d.cs.High()
return err
}
func (d *Device) sendDataByte(b byte) error {
d.dc.High()
d.cs.Low()
_, err := d.bus.Transfer(b)
d.cs.High()
return err
}
func (d *Device) sendData(b []byte) error {
d.dc.High()
d.cs.Low()
err := d.bus.Tx(b, nil)
d.cs.High()
return err
}
// fill quickly fills a slice with a given value
func fill(s []byte, b byte) {
s[0] = b
for j := 1; j < len(s); j *= 2 {
copy(s[j:], s[:j])
}
}
-34
View File
@@ -1,34 +0,0 @@
//go:build pico
package epd2in66b
import (
"machine"
)
// DefaultConfig contains the default config for the https://www.waveshare.com/wiki/Pico-ePaper-2.66 module
var DefaultConfig = Config{
DataPin: machine.GP8,
ChipSelectPin: machine.GP9,
ResetPin: machine.GP12,
BusyPin: machine.GP13,
}
// NewPicoModule allocates a new device backed by the https://www.waveshare.com/wiki/Pico-ePaper-2.66 module
// This will also configure the SPI1 bus and configure the device with the DefaultConfig
func NewPicoModule() (Device, error) {
spi := machine.SPI1
if err := spi.Configure(machine.SPIConfig{
Frequency: Baudrate,
}); err != nil {
return Device{}, err
}
dev := New(spi)
if err := dev.Configure(DefaultConfig); err != nil {
return dev, err
}
return dev, nil
}
-92
View File
@@ -1,92 +0,0 @@
package epd2in66b
import (
_ "embed"
"fmt"
"image"
"image/color"
"image/draw"
"image/png"
"os"
"testing"
"time"
"tinygo.org/x/drivers"
"tinygo.org/x/tinyfont"
"tinygo.org/x/tinyfont/freemono"
)
type mockBus struct{}
func (m *mockBus) Tx(w, r []byte) error {
return nil
}
func (m *mockBus) Transfer(b byte) (byte, error) {
return 0, nil
}
func TestBufferDrawing(t *testing.T) {
dev := New(&mockBus{})
tinyfont.WriteLine(&dev, &freemono.Bold9pt7b, 10, 40, "Hello World!", color.RGBA{0xff, 0xff, 0xff, 0xff})
red := color.RGBA{0xff, 0, 0, 0xff}
black := color.RGBA{0xff, 0xff, 0xff, 0xff}
showRect(&dev, 10, 10, 10, 10, black)
showRect(&dev, 10, 20, 10, 10, red)
img := toImage(&dev)
writeImage(img)
}
func toImage(dev *Device) *image.RGBA {
red := color.RGBA{0xff, 0, 0, 0xff}
xMax, yMax := dev.Size()
r := image.Rect(0, 0, int(xMax), int(yMax))
container := image.NewRGBA(r)
draw.Draw(container, container.Bounds(), image.NewUniform(color.White), image.Point{}, draw.Over)
for x := 0; x < int(xMax); x++ {
for y := 0; y < int(yMax); y++ {
bytePos, bitPos := pos(int16(x), int16(y), displayWidth)
if isSet(dev.redBuffer, bytePos, bitPos) {
container.Set(x, y, red)
} else if isSet(dev.blackBuffer, bytePos, bitPos) {
container.Set(x, y, color.Black)
}
}
}
return container
}
func isSet(buf []byte, bytePos, bitPos int) bool {
return (buf[bytePos])&(0x1<<bitPos) != 0
}
func showRect(display drivers.Displayer, x int16, y int16, w int16, h int16, c color.RGBA) {
for i := x; i < x+w; i++ {
for j := y; j < y+h; j++ {
display.SetPixel(i, j, c)
}
}
}
func writeImage(img image.Image) string {
fn := fmt.Sprintf("%d.png", time.Now().Unix())
f, err := os.OpenFile(fn, os.O_RDWR|os.O_CREATE, 0o644)
if err != nil {
panic(err)
}
defer f.Close()
err = png.Encode(f, img)
if err != nil {
panic(err)
}
return fn
}
+106 -244
View File
@@ -33,6 +33,7 @@ var _debug debug = debugBasic
//var _debug debug = debugBasic | debugNetdev | debugCmd | debugDetail
var (
version = "0.0.1"
driverName = "Tinygo ESP32 Wifi network device driver (WiFiNINA)"
)
@@ -47,9 +48,6 @@ const (
statusConnectFailed connectionStatus = 4
statusConnectionLost connectionStatus = 5
statusDisconnected connectionStatus = 6
statusAPListening connectionStatus = 7
statusAPConnected connectionStatus = 8
statusAPFailed connectionStatus = 9
encTypeTKIP encryptionType = 2
encTypeCCMP encryptionType = 4
@@ -165,12 +163,10 @@ type encryptionType uint8
type sock uint8
type hwerr uint8
type Socket struct {
protocol int
clientConnected bool
laddr netip.AddrPort // Set in Bind()
raddr netip.AddrPort // Set in Connect()
sock // Device socket, as returned from w.getSocket()
type socket struct {
protocol int
ip netip.AddrPort
inuse bool
}
type Config struct {
@@ -217,13 +213,17 @@ type wifinina struct {
killWatchdog chan bool
fault error
sockets map[int]*Socket // keyed by sockfd
sockets map[sock]*socket // keyed by sock as returned by getSocket()
}
func newSocket(protocol int) *socket {
return &socket{protocol: protocol, inuse: true}
}
func New(cfg *Config) *wifinina {
w := wifinina{
cfg: cfg,
sockets: make(map[int]*Socket),
sockets: make(map[sock]*socket),
killWatchdog: make(chan bool),
cs: cfg.Cs,
ack: cfg.Ack,
@@ -302,61 +302,6 @@ func (w *wifinina) connectToAP() error {
return netlink.ErrConnectTimeout
}
func (w *wifinina) startAP() error {
timeout := w.params.ConnectTimeout
if timeout == 0 {
timeout = netlink.DefaultConnectTimeout
}
if len(w.params.Ssid) == 0 {
return netlink.ErrMissingSSID
}
if debugging(debugBasic) {
fmt.Printf("Starting Wifi AP as SSID '%s'...", w.params.Ssid)
}
start := time.Now()
// Start the connection process
switch {
case w.params.Passphrase != "":
w.setPassphraseForAP(w.params.Ssid, w.params.Passphrase)
default:
w.setNetworkForAP(w.params.Ssid)
}
// Check if we are listening
for {
status := w.getConnectionStatus()
switch status {
case statusAPListening:
if debugging(debugBasic) {
fmt.Printf("LISTENING\r\n")
}
if w.notifyCb != nil {
w.notifyCb(netlink.EventNetUp)
}
return nil
case statusAPFailed:
if debugging(debugBasic) {
fmt.Printf("FAILED (%s)\r\n", w.reason())
}
return netlink.ErrConnectFailed
}
if time.Since(start) > timeout {
break
}
time.Sleep(1 * time.Second)
}
if debugging(debugBasic) {
fmt.Printf("FAILED (timed out)\r\n")
}
return netlink.ErrConnectTimeout
}
func (w *wifinina) netDisconnect() {
w.disconnect()
}
@@ -368,7 +313,7 @@ func (w *wifinina) showDriver() {
if debugging(debugBasic) {
fmt.Printf("\r\n")
fmt.Printf("%s\r\n\r\n", driverName)
fmt.Printf("Driver version : %s\r\n", drivers.Version)
fmt.Printf("Driver version : %s\r\n", version)
}
w.driverShown = true
}
@@ -438,12 +383,7 @@ func (w *wifinina) showIP() {
}
func (w *wifinina) networkDown() bool {
switch w.getConnectionStatus() {
case statusConnected, statusAPListening, statusAPConnected:
return false
default:
return true
}
return w.getConnectionStatus() != statusConnected
}
func (w *wifinina) watchdog() {
@@ -481,28 +421,15 @@ func (w *wifinina) netConnect(reset bool) error {
}
w.showDevice()
retry:
for i := 0; w.params.Retries == 0 || i < w.params.Retries; i++ {
switch w.params.ConnectMode {
case netlink.ConnectModeAP:
if err := w.startAP(); err != nil {
switch err {
case netlink.ErrConnectTimeout, netlink.ErrConnectFailed:
continue
}
return err
if err := w.connectToAP(); err != nil {
switch err {
case netlink.ErrConnectTimeout, netlink.ErrConnectFailed:
continue
}
break retry
default:
if err := w.connectToAP(); err != nil {
switch err {
case netlink.ErrConnectTimeout, netlink.ErrConnectFailed:
continue
}
return err
}
break retry
return err
}
break
}
if w.networkDown() {
@@ -577,12 +504,6 @@ func (w *wifinina) GetHostByName(name string) (netip.Addr, error) {
fmt.Printf("[GetHostByName] name: %s\r\n", name)
}
// If it's already in dotted-decimal notation, return a copy
// per gethostbyname(3).
if ip, err := netip.ParseAddr(name); err == nil {
return ip, nil
}
w.mu.Lock()
defer w.mu.Unlock()
@@ -625,20 +546,6 @@ func (w *wifinina) Addr() (netip.Addr, error) {
return ip, nil
}
// newSockfd returns the next available sockfd, or -1 if none available
func (w *wifinina) newSockfd() int {
if len(w.sockets) >= maxNetworks {
return -1
}
// Search for the next available sockfd starting at 0
for sockfd := 0; ; sockfd++ {
if _, ok := w.sockets[sockfd]; !ok {
return sockfd
}
}
return -1
}
// See man socket(2) for standard Berkely sockets for Socket, Bind, etc.
// The driver strives to meet the function and semantics of socket(2).
@@ -666,49 +573,37 @@ func (w *wifinina) Socket(domain int, stype int, protocol int) (int, error) {
w.mu.Lock()
defer w.mu.Unlock()
sockfd := w.newSockfd()
if sockfd == -1 {
sock := w.getSocket()
if sock == noSocketAvail {
return -1, netdev.ErrNoMoreSockets
}
w.sockets[sockfd] = &Socket{
protocol: protocol,
sock: noSocketAvail,
}
socket := newSocket(protocol)
w.sockets[sock] = socket
if debugging(debugNetdev) {
fmt.Printf("[Socket] <-- sockfd %d\r\n", sockfd)
}
return sockfd, nil
return int(sock), nil
}
func (w *wifinina) Bind(sockfd int, ip netip.AddrPort) error {
if debugging(debugNetdev) {
fmt.Printf("[Bind] sockfd: %d, addr: %s:%d\r\n", sockfd, ip.Addr(), ip.Port())
fmt.Printf("[Bind] sockfd: %d, addr: %s\r\n", sockfd, ip)
}
w.mu.Lock()
defer w.mu.Unlock()
socket, ok := w.sockets[sockfd]
if !ok {
return netdev.ErrInvalidSocketFd
}
var sock = sock(sockfd)
var socket = w.sockets[sock]
switch socket.protocol {
case netdev.IPPROTO_TCP:
case netdev.IPPROTO_TLS:
case netdev.IPPROTO_UDP:
socket.sock = w.getSocket()
if socket.sock == noSocketAvail {
return netdev.ErrNoMoreSockets
}
w.startServer(socket.sock, ip.Port(), protoModeUDP)
w.startServer(sock, ip.Port(), protoModeUDP)
}
socket.laddr = ip
socket.ip = ip
return nil
}
@@ -733,40 +628,21 @@ func (w *wifinina) Connect(sockfd int, host string, ip netip.AddrPort) error {
w.mu.Lock()
defer w.mu.Unlock()
socket, ok := w.sockets[sockfd]
if !ok {
return netdev.ErrInvalidSocketFd
}
var sock = sock(sockfd)
var socket = w.sockets[sock]
// Start the connection
switch socket.protocol {
case netdev.IPPROTO_TCP:
socket.sock = w.getSocket()
if socket.sock == noSocketAvail {
return netdev.ErrNoMoreSockets
}
w.startClient(socket.sock, "", toUint32(ip.Addr().As4()), ip.Port(), protoModeTCP)
w.startClient(sock, "", toUint32(ip.Addr().As4()), ip.Port(), protoModeTCP)
case netdev.IPPROTO_TLS:
socket.sock = w.getSocket()
if socket.sock == noSocketAvail {
return netdev.ErrNoMoreSockets
}
w.startClient(socket.sock, host, 0, ip.Port(), protoModeTLS)
w.startClient(sock, host, 0, ip.Port(), protoModeTLS)
case netdev.IPPROTO_UDP:
if socket.sock == noSocketAvail {
return fmt.Errorf("Must Bind before Connecting")
}
// See start in sendUDP()
socket.raddr = ip
socket.clientConnected = true
w.startClient(sock, "", toUint32(ip.Addr().As4()), ip.Port(), protoModeUDP)
return nil
}
if w.getClientState(socket.sock) == tcpStateEstablished {
socket.clientConnected = true
if w.getClientState(sock) == tcpStateEstablished {
return nil
}
@@ -786,18 +662,12 @@ func (w *wifinina) Listen(sockfd int, backlog int) error {
w.mu.Lock()
defer w.mu.Unlock()
socket, ok := w.sockets[sockfd]
if !ok {
return netdev.ErrInvalidSocketFd
}
var sock = sock(sockfd)
var socket = w.sockets[sock]
switch socket.protocol {
case netdev.IPPROTO_TCP:
socket.sock = w.getSocket()
if socket.sock == noSocketAvail {
return netdev.ErrNoMoreSockets
}
w.startServer(socket.sock, socket.laddr.Port(), protoModeTCP)
w.startServer(sock, socket.ip.Port(), protoModeTCP)
case netdev.IPPROTO_UDP:
default:
return netdev.ErrProtocolNotSupported
@@ -806,29 +676,27 @@ func (w *wifinina) Listen(sockfd int, backlog int) error {
return nil
}
func (w *wifinina) Accept(sockfd int) (int, netip.AddrPort, error) {
func (w *wifinina) Accept(sockfd int, ip netip.AddrPort) (int, error) {
if debugging(debugNetdev) {
fmt.Printf("[Accept] sockfd: %d\r\n", sockfd)
fmt.Printf("[Accept] sockfd: %d, peer: %s\r\n", sockfd, ip)
}
w.mu.Lock()
defer w.mu.Unlock()
socket, ok := w.sockets[sockfd]
if !ok {
return -1, netip.AddrPort{}, netdev.ErrInvalidSocketFd
}
var client sock
var sock = sock(sockfd)
var socket = w.sockets[sock]
switch socket.protocol {
case netdev.IPPROTO_TCP:
default:
return -1, netip.AddrPort{}, netdev.ErrProtocolNotSupported
return -1, netdev.ErrProtocolNotSupported
}
skip:
for {
// Accept() will be sleeping most of the time, checking for
// Accept() will be sleeping most of the time, checking for a
// new clients every 1/10 sec.
w.mu.Unlock()
time.Sleep(100 * time.Millisecond)
@@ -836,46 +704,49 @@ skip:
// Check if we've faulted
if w.fault != nil {
return -1, netip.AddrPort{}, w.fault
return -1, w.fault
}
// TODO: BUG: Currently, a sock that is 100% busy will always be
// TODO: returned by w.accept(sock), starving other socks
// TODO: from begin serviced. Need to figure out how to
// TODO: service socks fairly (round-robin?) so no one sock
// TODO: can dominate.
// Check if a client has data
var client sock = w.accept(socket.sock)
client = w.accept(sock)
if client == noSocketAvail {
// None ready
continue
}
// If we already have a socket for the client, skip
for _, s := range w.sockets {
if s.sock == client {
continue skip
// If we've already seen this socket, we can reuse
// the socket and return it. But, only if the socket
// is closed. If it's not closed, we'll just come back
// later to reuse it.
clientSocket, ok := w.sockets[client]
if ok {
// Wait for client to Close
if clientSocket.inuse {
continue
}
// Reuse client socket
return int(client), nil
}
// Otherwise, create a new socket
clientfd := w.newSockfd()
if clientfd == -1 {
return -1, netip.AddrPort{}, netdev.ErrNoMoreSockets
}
w.sockets[clientfd] = &Socket{
protocol: netdev.IPPROTO_TCP,
sock: client,
clientConnected: true,
}
raddr := w.getRemoteData(client)
return clientfd, raddr, nil
// Create new socket for client and return fd
w.sockets[client] = newSocket(socket.protocol)
return int(client), nil
}
}
func (w *wifinina) sockDown(socket *Socket) bool {
func (w *wifinina) sockDown(sock sock) bool {
var socket = w.sockets[sock]
if socket.protocol == netdev.IPPROTO_UDP {
return false
}
return w.getClientState(socket.sock) != tcpStateEstablished
return w.getClientState(sock) != tcpStateEstablished
}
func (w *wifinina) sendTCP(sock sock, buf []byte, deadline time.Time) (int, error) {
@@ -903,7 +774,7 @@ func (w *wifinina) sendTCP(sock sock, buf []byte, deadline time.Time) (int, erro
}
// Check if socket went down
if w.getClientState(sock) != tcpStateEstablished {
if w.sockDown(sock) {
return -1, io.EOF
}
@@ -921,10 +792,7 @@ func (w *wifinina) sendTCP(sock sock, buf []byte, deadline time.Time) (int, erro
return -1, netdev.ErrTimeout
}
func (w *wifinina) sendUDP(sock sock, raddr netip.AddrPort, buf []byte, deadline time.Time) (int, error) {
// Start a client for each send
w.startClient(sock, "", toUint32(raddr.Addr().As4()), raddr.Port(), protoModeUDP)
func (w *wifinina) sendUDP(sock sock, buf []byte, deadline time.Time) (int, error) {
// Queue it
ok := w.insertDataBuf(sock, buf)
@@ -942,10 +810,8 @@ func (w *wifinina) sendUDP(sock sock, raddr netip.AddrPort, buf []byte, deadline
}
func (w *wifinina) sendChunk(sockfd int, buf []byte, deadline time.Time) (int, error) {
socket, ok := w.sockets[sockfd]
if !ok {
return -1, netdev.ErrInvalidSocketFd
}
var sock = sock(sockfd)
var socket = w.sockets[sock]
// Check if we've timed out
if !deadline.IsZero() {
@@ -956,9 +822,9 @@ func (w *wifinina) sendChunk(sockfd int, buf []byte, deadline time.Time) (int, e
switch socket.protocol {
case netdev.IPPROTO_TCP, netdev.IPPROTO_TLS:
return w.sendTCP(socket.sock, buf, deadline)
return w.sendTCP(sock, buf, deadline)
case netdev.IPPROTO_UDP:
return w.sendUDP(socket.sock, socket.raddr, buf, deadline)
return w.sendUDP(sock, buf, deadline)
}
return -1, netdev.ErrProtocolNotSupported
@@ -1003,10 +869,7 @@ func (w *wifinina) Recv(sockfd int, buf []byte, flags int,
w.mu.Lock()
defer w.mu.Unlock()
socket, ok := w.sockets[sockfd]
if !ok {
return -1, netdev.ErrInvalidSocketFd
}
var sock = sock(sockfd)
// Limit max read size to chunk large read requests
var max = len(buf)
@@ -1027,22 +890,22 @@ func (w *wifinina) Recv(sockfd int, buf []byte, flags int,
// doesn't return unless there is data, even a single byte, or
// on error such as timeout or EOF.
n := int(w.getDataBuf(socket.sock, buf[:max]))
n := int(w.getDataBuf(sock, buf[:max]))
if n > 0 {
if debugging(debugNetdev) {
fmt.Printf("[<--Recv] sockfd: %d, n: %d\r\n",
sockfd, n)
sock, n)
}
return n, nil
}
// Check if socket went down
if w.sockDown(socket) {
if w.sockDown(sock) {
// Get any last bytes
n = int(w.getDataBuf(socket.sock, buf[:max]))
n = int(w.getDataBuf(sock, buf[:max]))
if debugging(debugNetdev) {
fmt.Printf("[<--Recv] sockfd: %d, n: %d, EOF\r\n",
sockfd, n)
sock, n)
}
if n > 0 {
return n, io.EOF
@@ -1071,18 +934,34 @@ func (w *wifinina) Close(sockfd int) error {
w.mu.Lock()
defer w.mu.Unlock()
socket, ok := w.sockets[sockfd]
if !ok {
return netdev.ErrInvalidSocketFd
var sock = sock(sockfd)
var socket = w.sockets[sock]
if !socket.inuse {
return nil
}
if socket.clientConnected {
w.stopClient(socket.sock)
w.stopClient(sock)
if socket.protocol == netdev.IPPROTO_UDP {
socket.inuse = false
return nil
}
delete(w.sockets, sockfd)
start := time.Now()
for time.Since(start) < 5*time.Second {
return nil
if w.getClientState(sock) == tcpStateClosed {
socket.inuse = false
return nil
}
w.mu.Unlock()
time.Sleep(100 * time.Millisecond)
w.mu.Lock()
}
return netdev.ErrClosingSocket
}
func (w *wifinina) SetSockOpt(sockfd int, level int, opt int, value interface{}) error {
@@ -1244,23 +1123,6 @@ func (w *wifinina) accept(s sock) sock {
return newsock
}
func (w *wifinina) getRemoteData(s sock) netip.AddrPort {
if debugging(debugCmd) {
fmt.Printf(" [cmdGetRemoteData] sock: %d\r\n", s)
}
sl := make([]string, 2)
l := w.reqRspStr1(cmdGetRemoteData, uint8(s), sl)
if l != 2 {
w.faultf("getRemoteData wanted l=2, got l=%d", l)
return netip.AddrPort{}
}
ip, _ := netip.AddrFromSlice([]byte(sl[0])[:4])
port := binary.BigEndian.Uint16([]byte(sl[1]))
return netip.AddrPortFrom(ip, port)
}
// insertDataBuf adds data to the buffer used for sending UDP data
func (w *wifinina) insertDataBuf(sock sock, buf []byte) bool {