Compare commits

...

57 Commits

Author SHA1 Message Date
Ayke van Laethem 7a3a92ffdb Implement enough wrappers to start the WiFi task 2021-09-29 02:24:28 +02:00
Dmitriy Zakharkin 9ae6050feb added more stubs 2021-09-29 01:32:54 +02:00
Ayke van Laethem 5a956deb4b Implement _task_get_current_task 2021-09-28 00:53:24 +02:00
Ayke van Laethem b74b250db5 Stub out spinlocks 2021-09-27 16:57:13 +02:00
Ayke van Laethem ab4d01654b Implement memory allocation 2021-09-27 16:39:04 +02:00
Ayke van Laethem 1a32b5be12 Implement locking using FreeRTOS compatibility layer from TinyGo 2021-09-27 16:23:52 +02:00
Ayke van Laethem 0f0fdf894c Use tabs instead of spaces. 2021-09-27 14:24:00 +02:00
Dmitriy 348b7724a3 comment and print mutex value 2021-09-26 22:51:19 -04:00
Dmitriy d5ade3299f added all function for g_wifi_osi_funcs 2021-09-26 22:46:35 -04:00
Ayke van Laethem 5d914b5e34 WIP add more stub functions to figure out which functions are called 2021-09-27 02:51:43 +02:00
Ayke van Laethem f8dd441827 espnet: WIP support for on-chip WiFi on an ESP32C3
Work in progress. Does not work yet.

Some notes:

  - This requires some changes to TinyGo, look at the espnet branch.
  - The  next step is probably defining all the functions in
    g_wifi_osi_funcs (see espnet.c). Right now it hangs in
    esp_wifi_init_internal, probably a NULL pointer dereference.
  - This is only for the ESP32-C3. This will require some work to work
    on other chips from Espressif.
2021-09-24 18:52:09 +02:00
Yurii Soldak 3fe3fc64e4 wifinina: fix warnings 2021-09-19 13:05:38 +02:00
Yurii Soldak 0565a15b17 net: fix raddr typo 2021-09-19 13:05:38 +02:00
deadprogram fad9d12e0b examples/ili9341: correct go format
Signed-off-by: deadprogram <ron@hybridgroup.com>
2021-09-18 15:06:56 +02:00
sago35 f2e5278965 image: add support for image/jpeg and image/png (#303)
* Copy from go1.17 image package
* Remove unnecessary files
* Reduce memory usage
* Add examples/ili9341/slideshow
* image: add ./image/README.md
* image: change convert2bin to . /cmd
* Makefile: add ./cmd to NOTEST
2021-09-18 12:18:02 +02:00
Ayke van Laethem 259593e608 ws2812: add support for 168MHz (e.g. Adafruit Feather STM32F405) 2021-09-17 10:19:21 +02:00
Ayke van Laethem 7a2b1b8c90 ws2812: improve timings to be compatible with the WS2811
The WS2811 has a slightly slower data rate, but it is close enough that
a single library can support both LEDs at the same time. In my testing,
this package was already compatible with WS2811 LEDs before this change
when running at 4-5V, but when the voltage dropped to 3V or so the LEDs
started misbehaving. With these improved timings, the LEDs remained of
the correct color even at very low voltages (although blue was starting
to fade due to lack of voltage - that's not a software/protocol issue).
2021-09-17 10:08:46 +02:00
Ayke van Laethem cad03a7d4e ws2812: fix generated build tags 2021-09-17 09:42:28 +02:00
deadprogram 98f5396625 examples/sdcard: correct invalid build tag noticed by @aykevl
Signed-off-by: deadprogram <ron@hybridgroup.com>
2021-09-16 18:31:33 +02:00
deadprogram 3b16972ed8 all: use build directives for both Go1.17 and earlier versions
Signed-off-by: deadprogram <ron@hybridgroup.com>
2021-09-16 18:31:33 +02:00
deadprogram b35496dd1b make: correct fmt-check to look in all subdirectories
Signed-off-by: deadprogram <ron@hybridgroup.com>
2021-09-16 18:31:33 +02:00
Yurii Soldak ad6be40966 lsm6dox: minimal correct implementation 2021-09-14 12:10:59 +02:00
Ayke van Laethem b3af3f594e ws2812: generate assembly instead of handwriting it
Previously, a new implementation had to be written for each processor
speed. This is tedious and error-prone, therefore I've rewritten all of
the assembly code using a tool that will calculate exactly how many NOP
instructions are required.

I've also switched from build tags to a switch statement over all
processor speeds.

All in all, this means that:
  - Chips with a supported CPU performance will automatically be
    supported (no build tags necessary).
  - Adding a new chip is as easy as adding the MHz count to the
    generator script and to the switch statement.

Right now this is only for the Cortex-M, but it's certainly feasible to
extend this to other architectures such as the AVR.
2021-09-07 18:00:55 +02:00
sago35 9a3bfd4826 wifinina: add comments 2021-09-06 11:06:59 +02:00
sago35 1ba9ba3143 wifinina: add examples/wifinina/http-get 2021-09-06 11:06:59 +02:00
Yurii Soldak feb1c63656 Instructions on how to flash nina-fw on nano rp2040 connect w/o Arduino IDE 2021-09-05 11:57:07 +02:00
Yurii Soldak 0691ed5205 wifinina: nina-fw update docs 2021-09-05 11:57:07 +02:00
deadprogram e70beede1c ws2812: update examples to use go:build tags required by go 1.17
Signed-off-by: deadprogram <ron@hybridgroup.com>
2021-09-03 11:11:40 +02:00
sago35 92673d996d dht: improve build tag in lowfreq.go 2021-09-02 11:28:22 +02:00
sago35 374e2dfe1d ili9341: change to use drivers.SPI interface 2021-09-01 19:41:27 +02:00
sago35 b35aab6d61 Add support for case-insensitive header names 2021-08-24 10:45:28 +02:00
sago35 358501fc47 Add support for req.URL.RawQuery 2021-08-24 10:45:28 +02:00
sago35 a0119ffc6f rtl8720dn: add examples/rtl8720dn/tcpclient 2021-08-09 21:11:47 +02:00
sago35 58106b5942 Add examples/rtl8720dn/udpstation 2021-08-05 22:18:24 +02:00
sago35 2537c69b0f rtl8720dn: add support for http.Get(), http.Post() and cookie 2021-08-05 08:10:52 +02:00
sago35 633f19c4ac rtl8720dn: add examples/rtl8720dn/mqtt* 2021-07-31 11:02:45 +02:00
sago35 6a4cd9d8e7 rtl8720dn: add examples/rtl8720dn/webserver 2021-07-24 09:44:17 +02:00
sago35 8a4a0ff017 rtl8720dn: add info when debug mode is enabled 2021-07-24 09:28:46 +02:00
deadprogram 1cbf1a6f3d release: v0.17.1
Signed-off-by: deadprogram <ron@hybridgroup.com>
2021-07-01 20:38:55 +02:00
deadprogram 001bdb43ba release: v0.17.0
Signed-off-by: deadprogram <ron@hybridgroup.com>
2021-07-01 20:20:03 +02:00
deadprogram f7ae6fac6d modules: switch to use tinygo-org version of tinyfs package
Signed-off-by: deadprogram <ron@hybridgroup.com>
2021-07-01 20:00:52 +02:00
sago35 a575b2a4ed Update comments 2021-06-27 16:33:32 +02:00
sago35 0bc3702dd0 rtl8720dn: add support for rtl8720dn 2021-06-27 16:33:32 +02:00
sago35 b53a8a17e7 ili9341: add standard SPI driver 2021-06-27 08:08:27 +02:00
sago35 5e117c162c ws2812: rename the pin to ws2812 2021-06-26 14:04:13 +02:00
sago35 24dfd8795a sdcard: add write functionality to examples/sdcard/tinyfs 2021-06-23 18:34:26 +02:00
sago35 a748451cab Merge pull request #275 from tinygo-org/ws2812-add-nrf52833
ws2812: add tag for nrf52833
2021-06-23 20:33:53 +09:00
deadprogram a811483fd4 ws2812: add tag for nrf52833
Signed-off-by: deadprogram <ron@hybridgroup.com>
2021-06-20 11:11:08 +02:00
Daniel Bridges 7b565e8cb0 Disable interrupts before sending ws2812 data 2021-06-17 09:24:49 +02:00
sago35 5645eb3f91 ws2812: add support for qtpy and atsame5x 2021-06-01 21:09:08 +02:00
sago35 6b914f7af7 sdcard: add support for fatfs 2021-05-31 17:26:21 +02:00
Ayke van Laethem 5a02fe068b apa102: use 4-byte buffer to improve speed
Instead of sending the APA102 data byte by byte, it's more efficient to
send multiple bytes at once (especially when the SPI peripheral uses
DMA). This requires the apa102.Device object to be a pointer receiver to
avoid excessive heap allocations.

This commit also just happens to work around a hardware bug on the
nrf52832:
https://infocenter.nordicsemi.com/index.jsp?topic=%2Ferrata_nRF52832_Rev2%2FERR%2FnRF52832%2FRev2%2Flatest%2Fanomaly_832_58.html&anchor=anomaly_832_58
2021-05-30 22:39:50 +02:00
Ayke van Laethem 5ecefde991 bmi160: avoid heap allocations
The nrf52 series chips use DMA for writing and reading SPI data, which
means that the transmit and receive buffer slices escape. This can
easily be solved by pre-allocating the buffer in the BMI160 driver and
reusing this buffer every time.
2021-05-30 22:11:27 +02:00
Yurii Soldak 9adbde99a2 wifinina: avoid fmt package 2021-05-28 18:09:29 +02:00
Yurii Soldak a09ec31e7a Fix RSSI command for WiFiNINA
+ Print current SSID
+ Wait for correct time before printing it out
+ Cleanup
2021-05-27 08:00:13 +02:00
sago35 d07ad40373 sdcard: add support for spi sdcard driver 2021-05-23 11:22:42 +02:00
sago35 aa17ffd1cb uart: use machine.Serial as the default output 2021-05-14 13:26:59 +02:00
191 changed files with 42233 additions and 531 deletions
+3
View File
@@ -0,0 +1,3 @@
[submodule "espnet/esp-idf"]
path = espnet/esp-idf
url = https://github.com/espressif/esp-idf.git
+25
View File
@@ -1,3 +1,28 @@
0.17.1
---
- To correct an error in the release process. Same as 0.17.0.
0.17.0
---
- **new devices**
- rtl8720dn: add support for rtl8720dn
- sdcard: add support for spi sdcard driver, along with fatfs
- **enhancements**
- apa102: use 4-byte buffer to improve speed
- bmi160: avoid heap allocations
- ili9341: add standard SPI driver
- wifinina
- avoid fmt package
- Fix RSSI command for WiFiNINA + Print current SSID + Wait for correct time before printing it out + Cleanup
- ws2812
- rename the pin to ws2812
- add tag for nrf52833
- Disable interrupts before sending ws2812 data
- add support for qtpy and atsame5x
- **core**
- modules: switch to use tinygo-org version of tinyfs package
- all: use machine.Serial as the default output
0.16.0
---
- **new devices**
+14 -2
View File
@@ -2,7 +2,7 @@
clean:
@rm -rf build
FMT_PATHS = ./*.go ./examples/**/*.go
FMT_PATHS = ./
fmt-check:
@unformatted=$$(gofmt -l $(FMT_PATHS)); [ -z "$$unformatted" ] && exit 0; echo "Unformatted:"; for fn in $$unformatted; do echo " $$fn"; done; exit 1
@@ -75,6 +75,8 @@ smoke-test:
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=xiao ./examples/ili9341/scroll
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=pyportal ./examples/ili9341/slideshow
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=circuitplay-express ./examples/lis3dh/main.go
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/lsm303agr/main.go
@@ -193,12 +195,22 @@ endif
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=nucleo-l432kc ./examples/aht20/main.go
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=feather-m4 ./examples/sdcard/console/
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=feather-m4 ./examples/sdcard/tinyfs/
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=wioterminal ./examples/rtl8720dn/webclient/
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=wioterminal ./examples/rtl8720dn/webserver/
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=wioterminal ./examples/rtl8720dn/mqttsub/
@md5sum ./build/test.hex
DRIVERS = $(wildcard */)
NOTESTS = build examples flash semihosting pcd8544 shiftregister st7789 microphone mcp3008 gps microbitmatrix \
hcsr04 ssd1331 ws2812 thermistor apa102 easystepper ssd1351 ili9341 wifinina shifter hub75 \
hd44780 buzzer ssd1306 espat l9110x st7735 bmi160 l293x dht keypad4x4 max72xx p1am tone tm1637 \
pcf8563 mcp2515 servo
pcf8563 mcp2515 servo sdcard rtl8720dn image cmd
TESTS = $(filter-out $(addsuffix /%,$(NOTESTS)),$(DRIVERS))
unit-test:
+3 -1
View File
@@ -52,7 +52,7 @@ func main() {
## Currently supported devices
The following 65 devices are supported.
The following 67 devices are supported.
| Device Name | Interface Type |
|----------|-------------|
@@ -100,12 +100,14 @@ The following 65 devices are supported.
| [PCD8544 display](http://eia.udg.edu/~forest/PCD8544_1.pdf) | SPI |
| [PCF8563 real time clock](https://www.nxp.com/docs/en/data-sheet/PCF8563.pdf) | I2C |
| [Resistive Touchscreen (4-wire)](http://ww1.microchip.com/downloads/en/Appnotes/doc8091.pdf) | GPIO |
| [RTL8720DN 2.4G/5G Dual Bands Wireless and BLE5.0](https://www.seeedstudio.com/Realtek8720DN-2-4G-5G-Dual-Bands-Wireless-and-BLE5-0-Combo-Module-p-4442.html) | UART |
| [Semihosting](https://wiki.segger.com/Semihosting) | Debug |
| [Servo](https://learn.sparkfun.com/tutorials/hobby-servo-tutorial/all) | PWM |
| [Shift register (PISO)](https://en.wikipedia.org/wiki/Shift_register#Parallel-in_serial-out_\(PISO\)) | GPIO |
| [Shift registers (SIPO)](https://en.wikipedia.org/wiki/Shift_register#Serial-in_parallel-out_(SIPO)) | GPIO |
| [SHT3x Digital Humidity Sensor](https://www.sensirion.com/fileadmin/user_upload/customers/sensirion/Dokumente/0_Datasheets/Humidity/Sensirion_Humidity_Sensors_SHT3x_Datasheet_digital.pdf) | I2C |
| [SPI NOR Flash Memory](https://en.wikipedia.org/wiki/Flash_memory#NOR_flash) | SPI/QSPI |
| [SPI SDCARD/MMC](https://en.wikipedia.org/wiki/SD_card) | SPI |
| [SSD1306 OLED display](https://cdn-shop.adafruit.com/datasheets/SSD1306.pdf) | I2C / SPI |
| [SSD1331 TFT color display](https://www.crystalfontz.com/controllers/SolomonSystech/SSD1331/381/) | SPI |
| [SSD1351 OLED display](https://download.mikroe.com/documents/datasheets/ssd1351-revision-1.3.pdf) | SPI |
+19 -17
View File
@@ -27,44 +27,46 @@ var startFrame = []byte{0x00, 0x00, 0x00, 0x00}
type Device struct {
bus drivers.SPI
Order int
buf [4]byte
}
// New returns a new APA102 driver. Pass in a fully configured SPI bus.
func New(b drivers.SPI) Device {
return Device{bus: b, Order: BGR}
func New(b drivers.SPI) *Device {
return &Device{bus: b, Order: BGR}
}
// NewSoftwareSPI returns a new APA102 driver that will use a software based
// implementation of the SPI protocol.
func NewSoftwareSPI(sckPin, sdoPin machine.Pin, delay uint32) Device {
func NewSoftwareSPI(sckPin, sdoPin machine.Pin, delay uint32) *Device {
return New(&bbSPI{SCK: sckPin, SDO: sdoPin, Delay: delay})
}
// WriteColors writes the given RGBA color slice out using the APA102 protocol.
// The A value (Alpha channel) is used for brightness, set to 0xff (255) for maximum.
func (d Device) WriteColors(cs []color.RGBA) (n int, err error) {
func (d *Device) WriteColors(cs []color.RGBA) (n int, err error) {
d.startFrame()
// write data
for _, c := range cs {
// brightness is scaled to 5 bit value
d.bus.Transfer(0xe0 | (c.A >> 3))
d.buf[0] = 0xe0 | (c.A >> 3)
// set the colors
switch d.Order {
case BRG:
d.bus.Transfer(c.B)
d.bus.Transfer(c.R)
d.bus.Transfer(c.G)
d.buf[1] = c.B
d.buf[2] = c.R
d.buf[3] = c.G
case GRB:
d.bus.Transfer(c.G)
d.bus.Transfer(c.R)
d.bus.Transfer(c.B)
d.buf[1] = c.G
d.buf[2] = c.R
d.buf[3] = c.B
case BGR:
d.bus.Transfer(c.B)
d.bus.Transfer(c.G)
d.bus.Transfer(c.R)
d.buf[1] = c.B
d.buf[2] = c.G
d.buf[3] = c.R
}
d.bus.Tx(d.buf[:], nil)
}
d.endFrame(len(cs))
@@ -73,7 +75,7 @@ func (d Device) WriteColors(cs []color.RGBA) (n int, err error) {
}
// Write the raw bytes using the APA102 protocol.
func (d Device) Write(buf []byte) (n int, err error) {
func (d *Device) Write(buf []byte) (n int, err error) {
d.startFrame()
d.bus.Tx(buf, nil)
d.endFrame(len(buf) / 4)
@@ -82,14 +84,14 @@ func (d Device) Write(buf []byte) (n int, err error) {
}
// startFrame sends the start bytes for a strand of LEDs.
func (d Device) startFrame() {
func (d *Device) startFrame() {
d.bus.Tx(startFrame, nil)
}
// endFrame sends the end frame marker with one extra bit per LED so
// long strands of LEDs receive the necessary termination for updates.
// See https://cpldcpu.wordpress.com/2014/11/30/understanding-the-apa102-superled/
func (d Device) endFrame(count int) {
func (d *Device) endFrame(count int) {
for i := 0; i < count/16; i++ {
d.bus.Transfer(0xff)
}
+24 -5
View File
@@ -13,6 +13,8 @@ type DeviceSPI struct {
// Chip select pin
CSB machine.Pin
buf [7]byte
// SPI bus (requires chip select to be usable).
Bus drivers.SPI
}
@@ -80,7 +82,10 @@ func (d *DeviceSPI) Reset() error {
// ReadTemperature returns the temperature in celsius milli degrees (°C/1000).
func (d *DeviceSPI) ReadTemperature() (temperature int32, err error) {
data := []byte{0x80 | reg_TEMPERATURE_0, 0, 0}
data := d.buf[:3]
data[0] = 0x80 | reg_TEMPERATURE_0
data[1] = 0
data[2] = 0
d.CSB.Low()
err = d.Bus.Tx(data, data)
d.CSB.High()
@@ -113,7 +118,11 @@ func (d *DeviceSPI) ReadTemperature() (temperature int32, err error) {
// and the sensor is not moving the returned value will be around 1000000 or
// -1000000.
func (d *DeviceSPI) ReadAcceleration() (x int32, y int32, z int32, err error) {
data := []byte{0x80 | reg_ACC_XL, 0, 0, 0, 0, 0, 0}
data := d.buf[:7]
data[0] = 0x80 | reg_ACC_XL
for i := 1; i < len(data); i++ {
data[i] = 0
}
d.CSB.Low()
err = d.Bus.Tx(data, data)
d.CSB.High()
@@ -139,7 +148,11 @@ func (d *DeviceSPI) ReadAcceleration() (x int32, y int32, z int32, err error) {
// rotation along one axis and while doing so integrate all values over time,
// you would get a value close to 360000000.
func (d *DeviceSPI) ReadRotation() (x int32, y int32, z int32, err error) {
data := []byte{0x80 | reg_GYR_XL, 0, 0, 0, 0, 0, 0}
data := d.buf[:7]
data[0] = 0x80 | reg_GYR_XL
for i := 1; i < len(data); i++ {
data[i] = 0
}
d.CSB.Low()
err = d.Bus.Tx(data, data)
d.CSB.High()
@@ -185,7 +198,9 @@ func (d *DeviceSPI) readRegister(address uint8) uint8 {
// I don't know why but it appears necessary to sleep for a bit here.
time.Sleep(time.Millisecond)
data := []byte{0x80 | address, 0}
data := d.buf[:2]
data[0] = 0x80 | address
data[1] = 0
d.CSB.Low()
d.Bus.Tx(data, data)
d.CSB.High()
@@ -198,7 +213,11 @@ func (d *DeviceSPI) writeRegister(address, data uint8) {
// I don't know why but it appears necessary to sleep for a bit here.
time.Sleep(time.Millisecond)
buf := d.buf[:2]
buf[0] = address
buf[1] = data
d.CSB.Low()
d.Bus.Tx([]byte{address, data}, []byte{0, 0})
d.Bus.Tx(buf, buf)
d.CSB.High()
}
+49
View File
@@ -0,0 +1,49 @@
package main
import (
"fmt"
"io/ioutil"
"log"
"os"
"strings"
)
// See ../../image/README.md for the usage.
func main() {
err := run(os.Args)
if err != nil {
log.Fatal(err)
}
}
func run(args []string) error {
if len(args) < 2 {
return fmt.Errorf("usage: %s FILE")
}
b, err := ioutil.ReadFile(args[1])
if err != nil {
return err
}
fmt.Printf("const %s = \"\" +\n", strings.Replace(args[1], ".", "_", -1))
i := 0
max := 32
for i = 0; i < len(b); i++ {
bb := b[i]
if (i % max) == 0 {
fmt.Printf(" \"")
}
fmt.Printf("\\x%02X", bb)
if (i%max) == max-1 && i != len(b)-1 {
fmt.Printf("\" + \n")
}
}
if (i % max) < max-1 {
fmt.Printf("\"\n")
}
return nil
}
+2 -1
View File
@@ -1,4 +1,5 @@
// +build mimxrt1062 stm32f405 atsamd51 stm32f103xx k210 stm32f407
//go:build mimxrt1062 || stm32f405 || atsamd51 || stm32f103xx || k210 || stm32f407
// +build mimxrt1062 stm32f405 atsamd51 stm32f103xx k210 stm32f407
package dht // import "tinygo.org/x/drivers/dht"
+2 -1
View File
@@ -1,4 +1,5 @@
// +build arduino atmega1284p nrf52840 digispark nrf52 arduino_nano nrf51 atsamd21 fe310 arduino_nano33 circuitplay_express arduino_mega2560
//go:build !mimxrt1062 && !stm32f405 && !atsamd51 && !stm32f103xx && !k210 && !stm32f407
// +build !mimxrt1062,!stm32f405,!atsamd51,!stm32f103xx,!k210,!stm32f407
package dht // import "tinygo.org/x/drivers/dht"
+49
View File
@@ -0,0 +1,49 @@
package espnet
/*
#cgo CFLAGS: -DCONFIG_IDF_TARGET_ESP32C3
#cgo CFLAGS: -Iinclude
#cgo CFLAGS: -Iesp-idf/components/esp_common/include
#cgo CFLAGS: -Iesp-idf/components/esp_event/include
#cgo CFLAGS: -Iesp-idf/components/esp_netif/include
#cgo CFLAGS: -Iesp-idf/components/esp_wifi/include
#cgo LDFLAGS: -Lesp-idf/components/esp_wifi/lib/esp32c3 -lnet80211 -lpp -lphy -lmesh -lcore
#cgo LDFLAGS: -Tesp-idf/components/esp_rom/esp32c3/ld/esp32c3.rom.ld
#include "esp_private/wifi.h"
#include "esp_wifi_types.h"
#include "espnet.h"
*/
import "C"
import _ "compat/freertos"
type ESPWiFi struct {
}
var WiFi = &ESPWiFi{}
type Config struct {
}
var internalConfig = C.wifi_init_config_t{
osi_funcs: &C.g_wifi_osi_funcs,
wpa_crypto_funcs: C.g_wifi_default_wpa_crypto_funcs,
static_rx_buf_num: 10,
static_tx_buf_num: 10,
mgmt_sbuf_num: 6,
sta_disconnected_pm: true,
magic: C.WIFI_INIT_CONFIG_MAGIC,
}
func (wifi ESPWiFi) Configure(config Config) error {
C.esp_wifi_internal_set_log_level(5)
return makeError(C.esp_wifi_init_internal(&internalConfig))
}
func (wifi ESPWiFi) AccessPointMAC() ([6]byte, error) {
var mac [6]byte
errCode := C.esp_wifi_get_mac(C.ESP_IF_WIFI_AP, &mac[0])
return mac, makeError(errCode)
}
+103
View File
@@ -0,0 +1,103 @@
package espnet
// #include <esp_err.h>
// #include <esp_wifi.h>
import "C"
// Wrapper for C.esp_err_t. Don't convert a C.esp_err_t to an Error type,
// instead use makeError to handle ESP_OK.
type Error C.esp_err_t
// makeError converts a C.esp_err_t into an error or nil depending on whether
// errCode indicates an error or not.
func makeError(errCode C.esp_err_t) error {
if errCode == C.ESP_OK {
return nil
}
return Error(errCode)
}
func (e Error) Error() string {
switch {
case e < C.ESP_ERR_WIFI_BASE:
// esp-idf/components/esp_common/include/esp_err.h
switch e {
case C.ESP_OK:
return "OK" // not an error
case C.ESP_FAIL:
return "ESP FAIL"
case C.ESP_ERR_NO_MEM:
return "Out of memory"
case C.ESP_ERR_INVALID_ARG:
return "Invalid argument"
case C.ESP_ERR_INVALID_STATE:
return "Invalid state"
case C.ESP_ERR_INVALID_SIZE:
return "Invalid size"
case C.ESP_ERR_NOT_FOUND:
return "Requested resource not found"
case C.ESP_ERR_NOT_SUPPORTED:
return "Operation or feature not supported"
case C.ESP_ERR_TIMEOUT:
return "Operation timed out"
case C.ESP_ERR_INVALID_RESPONSE:
return "Received response was invalid"
case C.ESP_ERR_INVALID_CRC:
return "CRC or checksum was invalid"
case C.ESP_ERR_INVALID_VERSION:
return "Version was invalid"
case C.ESP_ERR_INVALID_MAC:
return "MAC address was invalid"
default:
return "Unknown error"
}
case e >= C.ESP_ERR_WIFI_BASE && e < C.ESP_ERR_MESH_BASE:
// esp-idf/components/esp_wifi/include/esp_wifi.h
switch e {
case C.ESP_ERR_WIFI_NOT_INIT:
return "WiFi driver was not installed by esp_wifi_init"
case C.ESP_ERR_WIFI_NOT_STARTED:
return "WiFi driver was not started by esp_wifi_start"
case C.ESP_ERR_WIFI_NOT_STOPPED:
return "WiFi driver was not stopped by esp_wifi_stop"
case C.ESP_ERR_WIFI_IF:
return "WiFi interface error"
case C.ESP_ERR_WIFI_MODE:
return "WiFi mode error"
case C.ESP_ERR_WIFI_STATE:
return "WiFi internal state error"
case C.ESP_ERR_WIFI_CONN:
return "WiFi internal control block of station or soft-AP error"
case C.ESP_ERR_WIFI_NVS:
return "WiFi internal NVS module error"
case C.ESP_ERR_WIFI_MAC:
return "MAC address is invalid"
case C.ESP_ERR_WIFI_SSID:
return " SSID is invalid"
case C.ESP_ERR_WIFI_PASSWORD:
return "Password is invalid"
case C.ESP_ERR_WIFI_TIMEOUT:
return "Timeout error"
case C.ESP_ERR_WIFI_WAKE_FAIL:
return "WiFi is in sleep state(RF closed) and wakeup fail"
case C.ESP_ERR_WIFI_WOULD_BLOCK:
return "The caller would block"
case C.ESP_ERR_WIFI_NOT_CONNECT:
return "Station still in disconnect status"
case C.ESP_ERR_WIFI_POST:
return "Failed to post the event to WiFi task"
case C.ESP_ERR_WIFI_INIT_STATE:
return "Invalid WiFi state when init/deinit is called"
case C.ESP_ERR_WIFI_STOP_STATE:
return "Returned when WiFi is stopping"
case C.ESP_ERR_WIFI_NOT_ASSOC:
return "The WiFi connection is not associated"
case C.ESP_ERR_WIFI_TX_DISALLOW:
return "The WiFi TX is disallowed"
default:
return "Other WiFi error"
}
default:
return "Other error"
}
}
+1
Submodule espnet/esp-idf added at c9646ff0be
+829
View File
@@ -0,0 +1,829 @@
#include <stdint.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "espnet.h"
#include "esp_wifi.h"
#include "esp_private/wifi.h"
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#include "freertos/task.h"
// Stub functions, to know which functions need to be implemented for OS
// functionality.
static bool _env_is_chip(void) {
printf("called: _env_is_chip\n");
return false;
}
static void _set_intr(int32_t cpu_no, uint32_t intr_source, uint32_t intr_num, int32_t intr_prio) {
printf("called: _set_intr\n");
}
static void _clear_intr(uint32_t intr_source, uint32_t intr_num) {
printf("called: _clear_intr\n");
}
static void _set_isr(int32_t n, void *f, void *arg) {
printf("called: _set_isr\n");
}
static void _ints_on(uint32_t mask) {
printf("called: _ints_on\n");
}
static void _ints_off(uint32_t mask) {
printf("called: _ints_off\n");
}
static bool _is_from_isr(void) {
printf("called: _is_from_isr\n");
return false;
}
// Having conflict between when include
// #include "freertos/portmacro.h"
typedef struct {
/* owner field values:
* 0 - Uninitialized (invalid)
* portMUX_FREE_VAL - Mux is free, can be locked by either CPU
* CORE_ID_REGVAL_PRO / CORE_ID_REGVAL_APP - Mux is locked to the particular core
*
*
* Any value other than portMUX_FREE_VAL, CORE_ID_REGVAL_PRO, CORE_ID_REGVAL_APP indicates corruption
*/
uint32_t owner;
/* count field:
* If mux is unlocked, count should be zero.
* If mux is locked, count is non-zero & represents the number of recursive locks on the mux.
*/
uint32_t count;
} portMUX_TYPE;
#define portMUX_FREE_VAL SPINLOCK_FREE
#define SPINLOCK_FREE 0xB33FFFFF
#define portMUX_INITIALIZER_UNLOCKED { \
.owner = portMUX_FREE_VAL, \
.count = 0, \
}
static void * _spin_lock_create(void) {
portMUX_TYPE tmp = portMUX_INITIALIZER_UNLOCKED;
void *mux = malloc(sizeof(portMUX_TYPE));
if (mux) {
memcpy(mux,&tmp,sizeof(portMUX_TYPE));
return mux;
}
return NULL;
}
static void _spin_lock_delete(void *lock) {
free(lock);
}
static uint32_t _wifi_int_disable(void *wifi_int_mux) {
printf("called: _wifi_int_disable\n");
return 0;
}
static void _wifi_int_restore(void *wifi_int_mux, uint32_t tmp) {
printf("called: _wifi_int_restore\n");
}
static void _task_yield_from_isr(void) {
printf("called: _task_yield_from_isr\n");
}
static void *_semphr_create(uint32_t max, uint32_t init) {
return (void *)xSemaphoreCreateCounting(max, init);
}
static void _semphr_delete(void *semphr) {
vSemaphoreDelete(semphr);
}
static int32_t _semphr_take(void *semphr, uint32_t block_time_tick) {
if (block_time_tick == OSI_FUNCS_TIME_BLOCKING) {
return (int32_t)xSemaphoreTake(semphr, portMAX_DELAY);
} else {
return (int32_t)xSemaphoreTake(semphr, block_time_tick);
}
}
static int32_t _semphr_give(void *semphr) {
return (int32_t)xSemaphoreGive(semphr);
}
static void *_wifi_thread_semphr_get(void) {
static SemaphoreHandle_t sem = NULL;
if (!sem) {
sem = xSemaphoreCreateCounting(1, 0);
}
return (void*)sem;
}
static void *_mutex_create(void) {
printf("called: _mutex_create\n");
return NULL;
}
static void *_recursive_mutex_create(void) {
return xSemaphoreCreateRecursiveMutex();
}
static void _mutex_delete(void *mutex) {
return vSemaphoreDelete(mutex);
}
static int32_t _mutex_lock(void *mutex) {
return (int32_t)xSemaphoreTakeRecursive(mutex, portMAX_DELAY);
}
static int32_t _mutex_unlock(void *mutex) {
return (int32_t)xSemaphoreGiveRecursive(mutex);
}
static void * _queue_create(uint32_t queue_len, uint32_t item_size) {
printf("called: _queue_create\n");
return NULL;
}
static void _queue_delete(void *queue) {
printf("called: _queue_delete\n");
}
static int32_t _queue_send(void *queue, void *item, uint32_t block_time_tick) {
if (block_time_tick == OSI_FUNCS_TIME_BLOCKING) {
return (int32_t)xQueueSend(queue, item, portMAX_DELAY);
} else {
return (int32_t)xQueueSend(queue, item, block_time_tick);
}
}
static int32_t _queue_send_from_isr(void *queue, void *item, void *hptw) {
printf("called: _queue_send_from_isr\n");
return 0;
}
static int32_t _queue_send_to_back(void *queue, void *item, uint32_t block_time_tick) {
printf("called: _queue_send_to_back\n");
return 0;
}
static int32_t _queue_send_to_front(void *queue, void *item, uint32_t block_time_tick) {
printf("called: _queue_send_to_front\n");
return 0;
}
static int32_t _queue_recv(void *queue, void *item, uint32_t block_time_tick) {
if (block_time_tick == OSI_FUNCS_TIME_BLOCKING) {
return (int32_t)xQueueReceive(queue, item, portMAX_DELAY);
} else {
return (int32_t)xQueueReceive(queue, item, block_time_tick);
}
}
static void * _event_group_create(void) {
printf("called: _event_group_create\n");
return NULL;
}
static void _event_group_delete(void *event) {
printf("called: _event_group_delete\n");
}
static uint32_t _event_group_set_bits(void *event, uint32_t bits) {
printf("called: _event_group_set_bits\n");
return 0;
}
static uint32_t _event_group_clear_bits(void *event, uint32_t bits) {
printf("called: _event_group_clear_bits\n");
return 0;
}
static uint32_t _event_group_wait_bits(void *event, uint32_t bits_to_wait_for, int clear_on_exit, int wait_for_all_bits, uint32_t block_time_tick) {
printf("called: _event_group_wait_bits\n");
return 0;
}
#define P(x) printf("called: "#x"\n");
static int32_t _task_create_pinned_to_core(void *task_func, const char *name, uint32_t stack_depth, void *param, uint32_t prio, void *task_handle, uint32_t core_id) {
// Note: using xTaskCreate instead of xTaskCreatePinnedToCore.
return (uint32_t)xTaskCreate(task_func, name, stack_depth, param, prio, task_handle);
}
static int32_t _task_create(void *task_func, const char *name, uint32_t stack_depth, void *param, uint32_t prio, void *task_handle) {
P(_task_create)
return 0;
}
static void _task_delete(void *task_handle) {
P(_task_delete)
}
static int32_t _task_ms_to_tick(uint32_t ms) {
return (int32_t)(ms / portTICK_PERIOD_MS);
}
static int32_t _task_get_max_priority() {
return configMAX_PRIORITIES;
}
static int32_t _event_post(const char* event_base, int32_t event_id, void* event_data, size_t event_data_size, uint32_t ticks_to_wait) {
P(_event_post)
return 0;
}
static uint32_t _get_free_heap_size(void) {
P(_get_free_heap_size)
return 0;
}
static uint32_t _rand(void) {
P(_rand)
return 0;
}
static void _dport_access_stall_other_cpu_start_wrap(void) {
P(_dport_access_stall_other_cpu_start_wrap)
}
static void _dport_access_stall_other_cpu_end_wrap(void) {
P(_dport_access_stall_other_cpu_end_wrap)
}
static void _wifi_apb80m_request(void) {
P(_wifi_apb80m_request)
}
static void _wifi_apb80m_release(void) {
P(_wifi_apb80m_release)
}
static void _phy_disable(void) {
P(_phy_disable)
}
static void _phy_enable(void) {
P(_phy_enable)
}
static int _phy_update_country_info(const char* country) {
P(_phy_update_country_info)
return 0;
}
static int _read_mac(uint8_t* mac, uint32_t type) {
P(_read_mac)
return 0;
}
static void _timer_arm(void *timer, uint32_t tmout, bool repeat) {
P(_timer_arm)
}
static void _timer_disarm(void *timer) {
P(_timer_disarm)
}
static void _timer_done(void *ptimer) {
P(_timer_done)
}
static void _timer_setfn(void *ptimer, void *pfunction, void *parg) {
P(_timer_setfn)
}
static void _timer_arm_us(void *ptimer, uint32_t us, bool repeat) {
P(_timer_arm_us)
}
static void _wifi_reset_mac(void) {
P(_wifi_reset_mac)
}
static void _wifi_clock_enable(void) {
P(_wifi_clock_enable)
}
static void _wifi_clock_disable(void) {
P(_wifi_clock_disable)
}
static void _wifi_rtc_enable_iso(void) {
P(_wifi_rtc_enable_iso)
}
static void _wifi_rtc_disable_iso(void) {
P(_wifi_rtc_disable_iso)
}
static int64_t _esp_timer_get_time(void) {
P(_esp_timer_get_time)
return 0;
}
static int _nvs_set_i8(uint32_t handle, const char* key, int8_t value) {
P(_nvs_set_i8)
return 0;
}
static int _nvs_get_i8(uint32_t handle, const char* key, int8_t* out_value) {
P(_nvs_get_i8)
return 0;
}
static int _nvs_set_u8(uint32_t handle, const char* key, uint8_t value) {
P(_nvs_set_u8)
return 0;
}
static int _nvs_get_u8(uint32_t handle, const char* key, uint8_t* out_value) {
P(_nvs_get_u8)
return 0;
}
static int _nvs_set_u16(uint32_t handle, const char* key, uint16_t value) {
P(_nvs_set_u16)
return 0;
}
static int _nvs_get_u16(uint32_t handle, const char* key, uint16_t* out_value) {
P(_nvs_get_u16)
return 0;
}
static int _nvs_open(const char* name, uint32_t open_mode, uint32_t *out_handle) {
P(_nvs_open)
return 0;
}
static void _nvs_close(uint32_t handle) {
P(_nvs_close)
}
static int _nvs_commit(uint32_t handle) {
P(_nvs_commit)
return 0;
}
static int _nvs_set_blob(uint32_t handle, const char* key, const void* value, size_t length) {
P(_nvs_set_blob)
return 0;
}
static int _nvs_get_blob(uint32_t handle, const char* key, void* out_value, size_t* length) {
P(_nvs_get_blob)
return 0;
}
static int _nvs_erase_key(uint32_t handle, const char* key) {
P(_nvs_erase_key)
return 0;
}
static int _get_random(uint8_t *buf, size_t len) {
P(_get_random)
return 0;
}
static int _get_time(void *t) {
P(_get_time)
return 0;
}
static unsigned long _random(void) {
P(_random)
return 0;
}
// #if CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32C3
// uint32_t (* _slowclk_cal_get(void)
// #endif
static void _log_write(uint32_t level, const char* tag, const char* format, ...) {
va_list argList;
printf("[%s] ", tag);
va_start(argList, format);
vprintf(format, argList);
va_end(argList);
printf("\n");
}
static void _log_writev(uint32_t level, const char* tag, const char* format, va_list args) {
printf("[%s] ", tag);
vprintf(format, args);
printf("\n");
}
static uint32_t _log_timestamp(void) {
P(_log_timestamp)
return 0;
}
static void* _malloc_internal(size_t size) {
printf("called: _malloc_internal(%d)\n", size);
return malloc(size);
}
static void* _realloc_internal(void *ptr, size_t size) {
printf("called: _realloc_internal(%p,%d)\n", ptr, size);
return NULL;
}
static void* _calloc_internal(size_t n, size_t size) {
printf("called: _calloc_internal(%d,%d)\n", n, size);
return malloc(n * size);
}
static void* _zalloc_internal(size_t size) {
printf("called: _zalloc_internal(%d)\n", size);
return NULL;
}
static void* _wifi_malloc(size_t size) {
return malloc(size);
}
static void* _wifi_realloc(void *ptr, size_t size) {
printf("called: _wifi_realloc(%d)\n", size);
return NULL;
}
static void* _wifi_calloc(size_t n, size_t size) {
return calloc(n, size);
}
static void* _wifi_zalloc(size_t size) {
return calloc(1, size);
}
static void* _wifi_create_queue(int queue_len, int item_size) {
wifi_static_queue_t *queue = (wifi_static_queue_t*)malloc(sizeof(wifi_static_queue_t));
queue->handle = xQueueCreate( queue_len, item_size);
return queue;
}
static void _wifi_delete_queue(void * queue) {
vQueueDelete(queue);
}
static int _coex_init(void) {
P(_coex_init)
return 0;
}
static void _coex_deinit(void) {
P(_coex_deinit)
}
static int _coex_enable(void) {
P(_coex_enable)
return 0;
}
static void _coex_disable(void) {
P(_coex_disable)
}
static uint32_t _coex_status_get(void) {
P(_coex_status_get)
return 0;
}
static void _coex_condition_set(uint32_t type, bool dissatisfy) {
P(_coex_condition_set)
}
static int _coex_wifi_request(uint32_t event, uint32_t latency, uint32_t duration) {
P(_coex_wifi_request)
return 0;
}
static int _coex_wifi_release(uint32_t event) {
P(_coex_wifi_release)
return 0;
}
static int _coex_wifi_channel_set(uint8_t primary, uint8_t secondary) {
P(_coex_wifi_channel_set)
return 0;
}
static int _coex_event_duration_get(uint32_t event, uint32_t *duration) {
P(_coex_event_duration_get)
return 0;
}
static int _coex_pti_get(uint32_t event, uint8_t *pti) {
P(_coex_pti_get)
return 0;
}
static void _coex_schm_status_bit_clear(uint32_t type, uint32_t status) {
P(_coex_schm_status_bit_clear)
}
static void _coex_schm_status_bit_set(uint32_t type, uint32_t status) {
P(_coex_schm_status_bit_set)
}
static int _coex_schm_interval_set(uint32_t interval) {
P(_coex_schm_interval_set)
return 0;
}
static uint32_t _coex_schm_interval_get(void) {
P(_coex_schm_interval_get)
return 0;
}
static uint8_t _coex_schm_curr_period_get(void) {
P(_coex_schm_curr_period_get)
return 0;
}
static void* _coex_schm_curr_phase_get(void) {
P(_coex_schm_curr_phase_get)
return NULL;
}
static int _coex_schm_curr_phase_idx_set(int idx) {
P(_coex_schm_curr_phase_idx_set)
return 0;
}
static int _coex_schm_curr_phase_idx_get(void) {
P(_coex_schm_curr_phase_idx_get)
return 0;
}
uint32_t _slowclk_cal_get(void) {
return 0;
}
// OS adapter functions.
// See: esp-idf/components/esp_wifi/include/esp_private/wifi_os_adapter.h
wifi_osi_funcs_t g_wifi_osi_funcs = {
._version = ESP_WIFI_OS_ADAPTER_VERSION,
._env_is_chip = _env_is_chip,
._set_intr = _set_intr,
._clear_intr = _clear_intr,
._set_isr = _set_isr,
._ints_on = _ints_on,
._ints_off = _ints_off,
._is_from_isr = _is_from_isr,
._spin_lock_create = _spin_lock_create,
._spin_lock_delete = _spin_lock_delete,
._wifi_int_disable = _wifi_int_disable,
._wifi_int_restore = _wifi_int_restore,
._task_yield_from_isr = _task_yield_from_isr,
._semphr_create = _semphr_create,
._semphr_delete = _semphr_delete,
._semphr_take = _semphr_take,
._semphr_give = _semphr_give,
._wifi_thread_semphr_get = _wifi_thread_semphr_get,
._mutex_create = _mutex_create,
._recursive_mutex_create = _recursive_mutex_create,
._mutex_delete = _mutex_delete,
._mutex_lock = _mutex_lock,
._mutex_unlock = _mutex_unlock,
._queue_create = _queue_create,
._queue_delete = _queue_delete,
._queue_send = _queue_send,
._queue_send_from_isr = _queue_send_from_isr,
._queue_send_to_back = _queue_send_to_back,
._queue_send_to_front = _queue_send_to_front,
._queue_recv = _queue_recv,
._queue_msg_waiting = (uint32_t(*)(void *))uxQueueMessagesWaiting,
._event_group_create = _event_group_create,
._event_group_delete = _event_group_delete,
._event_group_set_bits = _event_group_set_bits,
._event_group_clear_bits = _event_group_clear_bits,
._event_group_wait_bits = _event_group_wait_bits,
._task_create_pinned_to_core = _task_create_pinned_to_core,
._task_create = _task_create,
._task_delete = _task_delete,
._task_delay = vTaskDelay,
._task_ms_to_tick = _task_ms_to_tick,
._task_get_current_task = (void *(*)(void))xTaskGetCurrentTaskHandle,
._task_get_max_priority = _task_get_max_priority,
._malloc = malloc,
._free = free,
._event_post = _event_post,
._get_free_heap_size = _get_free_heap_size,
._rand = _rand,
._dport_access_stall_other_cpu_start_wrap = _dport_access_stall_other_cpu_start_wrap,
._dport_access_stall_other_cpu_end_wrap = _dport_access_stall_other_cpu_end_wrap,
._wifi_apb80m_request = _wifi_apb80m_request,
._wifi_apb80m_release = _wifi_apb80m_release,
._phy_disable = _phy_disable,
._phy_enable = _phy_enable,
._phy_update_country_info = _phy_update_country_info,
._read_mac = _read_mac,
._timer_arm = _timer_arm,
._timer_disarm = _timer_disarm,
._timer_done = _timer_done,
._timer_setfn = _timer_setfn,
._timer_arm_us = _timer_arm_us,
._wifi_reset_mac = _wifi_reset_mac,
._wifi_clock_enable = _wifi_clock_enable,
._wifi_clock_disable = _wifi_clock_disable,
._wifi_rtc_enable_iso = _wifi_rtc_enable_iso,
._wifi_rtc_disable_iso = _wifi_rtc_disable_iso,
._esp_timer_get_time = _esp_timer_get_time,
._nvs_set_i8 = _nvs_set_i8,
._nvs_get_i8 = _nvs_get_i8,
._nvs_set_u8 = _nvs_set_u8,
._nvs_get_u8 = _nvs_get_u8,
._nvs_set_u16 = _nvs_set_u16,
._nvs_get_u16 = _nvs_get_u16,
._nvs_open = _nvs_open,
._nvs_close = _nvs_close,
._nvs_commit = _nvs_commit,
._nvs_set_blob = _nvs_set_blob,
._nvs_get_blob = _nvs_get_blob,
._nvs_erase_key = _nvs_erase_key,
._get_random = _get_random,
._get_time = _get_time,
._random = _random,
#if CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32C3
._slowclk_cal_get = _slowclk_cal_get,
#endif
._log_write = _log_write,
._log_writev = _log_writev,
._log_timestamp = _log_timestamp,
._malloc_internal = _malloc_internal,
._realloc_internal = _realloc_internal,
._calloc_internal = _calloc_internal,
._zalloc_internal = _zalloc_internal,
._wifi_malloc = _wifi_malloc,
._wifi_realloc = _wifi_realloc,
._wifi_calloc = _wifi_calloc,
._wifi_zalloc = _wifi_zalloc,
._wifi_create_queue = _wifi_create_queue,
._wifi_delete_queue = _wifi_delete_queue,
._coex_init = _coex_init,
._coex_deinit = _coex_deinit,
._coex_enable = _coex_enable,
._coex_disable = _coex_disable,
._coex_status_get = _coex_status_get,
._coex_condition_set = _coex_condition_set,
._coex_wifi_request = _coex_wifi_request,
._coex_wifi_release = _coex_wifi_release,
._coex_wifi_channel_set = _coex_wifi_channel_set,
._coex_event_duration_get = _coex_event_duration_get,
._coex_pti_get = _coex_pti_get,
._coex_schm_status_bit_clear = _coex_schm_status_bit_clear,
._coex_schm_status_bit_set = _coex_schm_status_bit_set,
._coex_schm_interval_set = _coex_schm_interval_set,
._coex_schm_interval_get = _coex_schm_interval_get,
._coex_schm_curr_period_get = _coex_schm_curr_period_get,
._coex_schm_curr_phase_get = _coex_schm_curr_phase_get,
._coex_schm_curr_phase_idx_set = _coex_schm_curr_phase_idx_set,
._coex_schm_curr_phase_idx_get = _coex_schm_curr_phase_idx_get,
._magic = ESP_WIFI_OS_ADAPTER_MAGIC,
};
static int esp_aes_wrap(const unsigned char *kek, int n, const unsigned char *plain, unsigned char *cipher) {
P(aes_wrap)
return -1;
}
static int esp_aes_unwrap(const unsigned char *kek, int n, const unsigned char *cipher, unsigned char *plain) {
P(aes_unwrap)
return -1;
}
static int hmac_sha256_vector(const unsigned char *key, int key_len, int num_elem,
const unsigned char *addr[], const int *len, unsigned char *mac) {
return -1;
}
static int sha256_prf(const unsigned char *key, int key_len, const char *label,
const unsigned char *data, int data_len, unsigned char *buf, int buf_len) {
P(sha256_prf)
return -1;
}
static int hmac_md5(const unsigned char *key, unsigned int key_len, const unsigned char *data,
unsigned int data_len, unsigned char *mac) {
P(hmac_md5)
return -1;
}
static int hamc_md5_vector(const unsigned char *key, unsigned int key_len, unsigned int num_elem,
const unsigned char *addr[], const unsigned int *len, unsigned char *mac) {
P(hamc_md5_vector)
return -1;
}
static int hmac_sha1(const unsigned char *key, unsigned int key_len, const unsigned char *data,
unsigned int data_len, unsigned char *mac) {
P(hmac_sha1)
return -1;
}
static int hmac_sha1_vector(const unsigned char *key, unsigned int key_len, unsigned int num_elem,
const unsigned char *addr[], const unsigned int *len, unsigned char *mac) {
P(hmac_sha1_vector)
return -1;
}
static int sha1_prf(const unsigned char *key, unsigned int key_len, const char *label,
const unsigned char *data, unsigned int data_len, unsigned char *buf, unsigned int buf_len) {
P(sha1_prf)
return -1;
}
static int sha1_vector(unsigned int num_elem, const unsigned char *addr[], const unsigned int *len,
unsigned char *mac) {
P(sha1_vector)
return -1;
}
static int pbkdf2_sha1(const char *passphrase, const char *ssid, unsigned int ssid_len,
int iterations, unsigned char *buf, unsigned int buflen) {
P(pbkdf2_sha1)
return -1;
}
static int rc4_skip(const unsigned char *key, unsigned int keylen, unsigned int skip,
unsigned char *data, unsigned int data_len) {
P(rc4_skip)
return -1;
}
static int md5_vector(unsigned int num_elem, const unsigned char *addr[], const unsigned int *len,
unsigned char *mac) {
P(md5_vector)
return -1;
}
static void aes_encrypt(void *ctx, const unsigned char *plain, unsigned char *crypt) {
P(aes_encrypt)
}
static void * aes_encrypt_init(const unsigned char *key, unsigned int len) {
P(aes_encrypt_init)
return NULL;
}
static void aes_encrypt_deinit(void *ctx) {
P(aes_encrypt_deinit)
}
static void aes_decrypt(void *ctx, const unsigned char *crypt, unsigned char *plain) {
P(aes_decrypt)
}
static void * aes_decrypt_init(const unsigned char *key, unsigned int len) {
P(aes_decrypt_init)
return NULL;
}
static void aes_decrypt_deinit(void *ctx) {
P(aes_decrypt_deinit)
}
static int aes_128_decrypt(const unsigned char *key, const unsigned char *iv, unsigned char *data, int data_len) {
P(aes_128_decrypt)
return -1;
}
static int omac1_aes_128(const uint8_t *key, const uint8_t *data, size_t data_len,
uint8_t *mic) {
P(omac1_aes_128)
return -1;
}
static uint8_t * ccmp_decrypt(const uint8_t *tk, const uint8_t *ieee80211_hdr,
const uint8_t *data, size_t data_len,
size_t *decrypted_len, bool espnow_pkt) {
P(ccmp_decrypt)
return NULL;
}
static uint8_t * ccmp_encrypt(const uint8_t *tk, uint8_t *frame, size_t len, size_t hdrlen,
uint8_t *pn, int keyid, size_t *encrypted_len) {
P(ccmp_encrypt)
return NULL;
}
static int hmac_md5_vector(const unsigned char *key, unsigned int key_len, unsigned int num_elem,
const unsigned char *addr[], const unsigned int *len, unsigned char *mac) {
P(hmac_md5_vector)
return -1;
}
static void esp_aes_encrypt(void *ctx, const unsigned char *plain, unsigned char *crypt) {
P(esp_aes_encrypt)
}
static void esp_aes_decrypt(void *ctx, const unsigned char *crypt, unsigned char *plain) {
P(esp_aes_decrypt)
}
static int aes_128_cbc_encrypt(const unsigned char *key, const unsigned char *iv, unsigned char *data, int data_len) {
P(aes_128_cbc_encrypt)
return -1;
}
static int aes_128_cbc_decrypt(const unsigned char *key, const unsigned char *iv, unsigned char *data, int data_len) {
P(aes_128_cbc_decrypt)
return -1;
}
const wpa_crypto_funcs_t g_wifi_default_wpa_crypto_funcs = {
.size = sizeof(wpa_crypto_funcs_t),
.version = ESP_WIFI_CRYPTO_VERSION,
.aes_wrap = (esp_aes_wrap_t)esp_aes_wrap,
.aes_unwrap = (esp_aes_unwrap_t)esp_aes_unwrap,
.hmac_sha256_vector = (esp_hmac_sha256_vector_t)hmac_sha256_vector,
.sha256_prf = (esp_sha256_prf_t)sha256_prf,
.hmac_md5 = (esp_hmac_md5_t)hmac_md5,
.hamc_md5_vector = (esp_hmac_md5_vector_t)hmac_md5_vector,
.hmac_sha1 = (esp_hmac_sha1_t)hmac_sha1,
.hmac_sha1_vector = (esp_hmac_sha1_vector_t)hmac_sha1_vector,
.sha1_prf = (esp_sha1_prf_t)sha1_prf,
.sha1_vector = (esp_sha1_vector_t)sha1_vector,
.pbkdf2_sha1 = (esp_pbkdf2_sha1_t)pbkdf2_sha1,
.rc4_skip = (esp_rc4_skip_t)rc4_skip,
.md5_vector = (esp_md5_vector_t)md5_vector,
.aes_encrypt = (esp_aes_encrypt_t)esp_aes_encrypt,
.aes_encrypt_init = (esp_aes_encrypt_init_t)aes_encrypt_init,
.aes_encrypt_deinit = (esp_aes_encrypt_deinit_t)aes_encrypt_deinit,
.aes_decrypt = (esp_aes_decrypt_t)esp_aes_decrypt,
.aes_decrypt_init = (esp_aes_decrypt_init_t)aes_decrypt_init,
.aes_decrypt_deinit = (esp_aes_decrypt_deinit_t)aes_decrypt_deinit,
.aes_128_encrypt = (esp_aes_128_encrypt_t)aes_128_cbc_encrypt,
.aes_128_decrypt = (esp_aes_128_decrypt_t)aes_128_cbc_decrypt,
.omac1_aes_128 = (esp_omac1_aes_128_t)omac1_aes_128,
.ccmp_decrypt = (esp_ccmp_decrypt_t)ccmp_decrypt,
.ccmp_encrypt = (esp_ccmp_encrypt_t)ccmp_encrypt
};
// This is a string constant that is used all over ESP-IDF and is also used by
// libnet80211.a. The main purpose is to be a fixed pointer that can be compared
// against etc.
const char *WIFI_EVENT = "WIFI_EVENT";
// Required by libphy.a
int phy_printf(const char *format, ...) {
va_list args;
va_start(args, format);
printf("phy: ");
int res = vprintf(format, args);
va_end(args);
return res;
}
// Required by libpp.a
int pp_printf(const char *format, ...) {
va_list args;
va_start(args, format);
printf("pp: ");
int res = vprintf(format, args);
va_end(args);
return res;
}
// Required by libnet80211.a
int net80211_printf(const char *format, ...) {
va_list args;
va_start(args, format);
printf("net80211: ");
int res = vprintf(format, args);
va_end(args);
return res;
}
// Source: esp-idf/components/wpa_supplicant/src/utils/common.c
static int hex2num(char c)
{
if (c >= '0' && c <= '9')
return c - '0';
if (c >= 'a' && c <= 'f')
return c - 'a' + 10;
if (c >= 'A' && c <= 'F')
return c - 'A' + 10;
return -1;
}
// Source: esp-idf/components/wpa_supplicant/src/utils/common.c
int hex2byte(const char *hex)
{
int a, b;
a = hex2num(*hex++);
if (a < 0)
return -1;
b = hex2num(*hex++);
if (b < 0)
return -1;
return (a << 4) | b;
}
// Source: esp-idf/components/wpa_supplicant/src/utils/common.c
/**
* hexstr2bin - Convert ASCII hex string into binary data
* @hex: ASCII hex string (e.g., "01ab")
* @buf: Buffer for the binary data
* @len: Length of the text to convert in bytes (of buf); hex will be double
* this size
* Returns: 0 on success, -1 on failure (invalid hex string)
*/
int hexstr2bin(const char *hex, uint8_t *buf, size_t len)
{
size_t i;
int a;
const char *ipos = hex;
uint8_t *opos = buf;
for (i = 0; i < len; i++) {
a = hex2byte(ipos);
if (a < 0)
return -1;
*opos++ = a;
ipos += 2;
}
return 0;
}
+5
View File
@@ -0,0 +1,5 @@
#include <stdbool.h>
#include "esp_private/wifi_os_adapter.h"
extern wifi_osi_funcs_t g_wifi_osi_funcs;
View File
+1 -1
View File
@@ -13,7 +13,7 @@ import (
)
var (
apa apa102.Device
apa *apa102.Device
pwm = machine.TCC0
leds = make([]color.RGBA, 1)
+1 -1
View File
@@ -30,7 +30,7 @@ var (
tx = machine.PA22
rx = machine.PA23
console = machine.UART0
console = machine.Serial
adaptor *espat.Device
)
+1 -1
View File
@@ -36,7 +36,7 @@ var (
tx = machine.PA22
rx = machine.PA23
console = machine.UART0
console = machine.Serial
adaptor *espat.Device
topic = "tinygo"
+1 -1
View File
@@ -37,7 +37,7 @@ var (
tx = machine.PA22
rx = machine.PA23
console = machine.UART0
console = machine.Serial
adaptor *espat.Device
cl mqtt.Client
+20
View File
@@ -0,0 +1,20 @@
package main
import "tinygo.org/x/drivers/espnet"
func main() {
err := espnet.WiFi.Configure(espnet.Config{})
if err != nil {
println("failed to configure:", err.Error())
}
mac, err := espnet.WiFi.AccessPointMAC()
if err != nil {
println("failed to read MAC address:", err.Error())
return
}
print("MAC address:")
for _, b := range mac {
print(" ", b)
}
println()
}
+1 -1
View File
@@ -20,7 +20,7 @@ var (
input [consoleBufLen]byte
store [storageBufLen]byte
console = machine.UART0
console = machine.Serial
dev *flash.Device
+1 -1
View File
@@ -10,7 +10,7 @@ import (
func main() {
println("GPS UART Example")
machine.UART1.Configure(machine.UARTConfig{BaudRate: 9600})
ublox := gps.NewUART(&machine.UART1)
ublox := gps.NewUART(machine.UART1)
parser := gps.NewParser()
var fix gps.Fix
for {
+1
View File
@@ -1,3 +1,4 @@
//go:build atsamd21
// +build atsamd21
package main
+1
View File
@@ -1,3 +1,4 @@
//go:build pyportal
// +build pyportal
package main
+1
View File
@@ -1,3 +1,4 @@
//go:build wioterminal
// +build wioterminal
package main
+9 -6
View File
@@ -25,7 +25,7 @@ const (
)
var (
frameBuffer = [(graphics.BALLHEIGHT + 8) * (graphics.BALLWIDTH + 8)]uint16{}
frameBuffer = [(graphics.BALLHEIGHT + 8) * (graphics.BALLWIDTH + 8) * 2]uint8{}
startTime int64
frame int64
@@ -179,7 +179,8 @@ func main() {
c = BGCOLOR
}
}
frameBuffer[y*int(width)+x] = c
frameBuffer[(y*int(width)+x)*2] = byte(c >> 8)
frameBuffer[(y*int(width)+x)*2+1] = byte(c)
bx1++ // Increment bitmap position counters (X axis)
bgx1++
}
@@ -190,7 +191,7 @@ func main() {
bgy++
}
display.DrawRGBBitmap(minx, miny, frameBuffer[:width*height], width, height)
display.DrawRGBBitmap8(minx, miny, frameBuffer[:width*height*2], width, height)
// Show approximate frame rate
frame++
@@ -215,11 +216,13 @@ func DrawBackground() {
b = graphics.Background[j*byteWidth+k/8]
}
if b&0x80 == 0 {
frameBuffer[k] = BGCOLOR
frameBuffer[2*k] = byte(BGCOLOR >> 8)
frameBuffer[2*k+1] = byte(BGCOLOR & 0xFF)
} else {
frameBuffer[k] = GRIDCOLOR
frameBuffer[2*k] = byte(GRIDCOLOR >> 8)
frameBuffer[2*k+1] = byte(GRIDCOLOR & 0xFF)
}
}
display.DrawRGBBitmap(0, j, frameBuffer[0:w], w, 1)
display.DrawRGBBitmap8(0, j, frameBuffer[0:w*2], w, 1)
}
}
@@ -1,3 +1,4 @@
//go:build pyportal
// +build pyportal
package main
@@ -1,3 +1,4 @@
//go:build wioterminal
// +build wioterminal
package main
+1
View File
@@ -1,3 +1,4 @@
//go:build atsamd21
// +build atsamd21
package main
+1
View File
@@ -1,3 +1,4 @@
//go:build pyportal
// +build pyportal
package main
+1
View File
@@ -1,3 +1,4 @@
//go:build wioterminal
// +build wioterminal
package main
+18
View File
@@ -0,0 +1,18 @@
# examples/ili9341/slideshow
![](./slideshow.jpg)
This example uses the image package for TinyGo to display png and jpeg images.
## How to create an image
The following program will output an image binary like the one in [images.go](./images.go).
```
go run ./examples/ili9341/slideshow/convert2bin ./path/to/png_or_jpg.png
```
## Notes
Displaying a 320x240 png or jpeg often requires more than 50KB of memory.
The examples include samd21 settings, but if you run them as is, you will get a memory size error.
+30
View File
@@ -0,0 +1,30 @@
//go:build atsamd21
// +build atsamd21
package main
import (
"machine"
"tinygo.org/x/drivers/ili9341"
)
var (
display = ili9341.NewSPI(
machine.SPI0,
machine.D0,
machine.D1,
machine.D2,
)
backlight = machine.D3
)
func init() {
machine.SPI0.Configure(machine.SPIConfig{
SCK: machine.SPI0_SCK_PIN,
SDO: machine.SPI0_SDO_PIN,
SDI: machine.SPI0_SDI_PIN,
Frequency: 24000000,
})
}
File diff suppressed because it is too large Load Diff
+94
View File
@@ -0,0 +1,94 @@
package main
import (
"fmt"
"image/color"
"machine"
"strings"
"time"
"tinygo.org/x/drivers/ili9341"
"tinygo.org/x/drivers/image/jpeg"
"tinygo.org/x/drivers/image/png"
)
var (
black = color.RGBA{0, 0, 0, 255}
white = color.RGBA{255, 255, 255, 255}
red = color.RGBA{255, 0, 0, 255}
blue = color.RGBA{0, 0, 255, 255}
green = color.RGBA{0, 255, 0, 255}
)
func main() {
err := run()
for err != nil {
errorMessage(err)
}
}
func run() error {
backlight.Configure(machine.PinConfig{machine.PinOutput})
display.Configure(ili9341.Config{})
width, height := display.Size()
if width < 320 || height < 240 {
display.SetRotation(ili9341.Rotation270)
}
display.FillScreen(black)
backlight.High()
for {
err := drawJpeg(display)
if err != nil {
return err
}
time.Sleep(time.Second)
err = drawPng(display)
if err != nil {
return err
}
time.Sleep(time.Second)
}
return nil
}
// Define the buffer required for the callback. In most cases, this setting
// should be sufficient. For jpeg, the callback will always be called every
// 3*8*8*4 pix. png will be called every line, i.e. every width pix.
var buffer [3 * 8 * 8 * 4]uint16
func drawPng(display *ili9341.Device) error {
p := strings.NewReader(pngImage)
png.SetCallback(buffer[:], func(data []uint16, x, y, w, h, width, height int16) {
err := display.DrawRGBBitmap(x, y, data[:w*h], w, h)
if err != nil {
errorMessage(fmt.Errorf("error drawPng: %s", err))
}
})
return png.Decode(p)
}
func drawJpeg(display *ili9341.Device) error {
p := strings.NewReader(jpegImage)
jpeg.SetCallback(buffer[:], func(data []uint16, x, y, w, h, width, height int16) {
err := display.DrawRGBBitmap(x, y, data[:w*h], w, h)
if err != nil {
errorMessage(fmt.Errorf("error drawJpeg: %s", err))
}
})
return jpeg.Decode(p)
}
func errorMessage(err error) {
for {
fmt.Printf("%s\r\n", err.Error())
time.Sleep(5 * time.Second)
}
}
+23
View File
@@ -0,0 +1,23 @@
//go:build pyportal
// +build pyportal
package main
import (
"machine"
"tinygo.org/x/drivers/ili9341"
)
var (
display = ili9341.NewParallel(
machine.LCD_DATA0,
machine.TFT_WR,
machine.TFT_DC,
machine.TFT_CS,
machine.TFT_RESET,
machine.TFT_RD,
)
backlight = machine.TFT_BACKLIGHT
)
Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

+30
View File
@@ -0,0 +1,30 @@
//go:build wioterminal
// +build wioterminal
package main
import (
"machine"
"tinygo.org/x/drivers/ili9341"
)
var (
display = ili9341.NewSPI(
machine.SPI3,
machine.LCD_DC,
machine.LCD_SS_PIN,
machine.LCD_RESET,
)
backlight = machine.LCD_BACKLIGHT
)
func init() {
machine.SPI3.Configure(machine.SPIConfig{
SCK: machine.LCD_SCK_PIN,
SDO: machine.LCD_SDO_PIN,
SDI: machine.LCD_SDI_PIN,
Frequency: 40000000,
})
}
+108
View File
@@ -0,0 +1,108 @@
// Connects to an LSM6DSOX I2C a 6 axis Inertial Measurement Unit (IMU)
package main
import (
"fmt"
"machine"
"time"
"tinygo.org/x/drivers/lsm6dsox"
)
const (
PLOTTER = true
SHOW_ACCELERATION = false
SHOW_ROTATION = true
SHOW_TEMPERATURE = false
// calibration is naive, good enough for a demo: do not shake a second after flashing
GYRO_CALIBRATION = true
)
var cal = [...]float32{0, 0, 0}
func main() {
machine.I2C0.Configure(machine.I2CConfig{})
device := lsm6dsox.New(machine.I2C0)
device.Configure(lsm6dsox.Configuration{
AccelRange: lsm6dsox.ACCEL_2G,
AccelSampleRate: lsm6dsox.ACCEL_SR_104,
GyroRange: lsm6dsox.GYRO_250DPS,
GyroSampleRate: lsm6dsox.GYRO_SR_104,
})
for {
if !device.Connected() {
println("LSM6DSOX not connected")
time.Sleep(time.Second)
continue
}
// heuristic: after successful calibration the value can't be 0
if GYRO_CALIBRATION && cal[0] == 0 {
calibrateGyro(device)
}
ax, ay, az := device.ReadAcceleration()
gx, gy, gz := device.ReadRotation()
t, _ := device.ReadTemperature()
if PLOTTER {
printPlotter(ax, ay, az, gx, gy, gz, t)
time.Sleep(time.Millisecond * 100)
} else {
printMonitor(ax, ay, az, gx, gy, gz, t)
time.Sleep(time.Millisecond * 1000)
}
}
}
func calibrateGyro(device *lsm6dsox.Device) {
for i := 0; i < 100; i++ {
gx, gy, gz := device.ReadRotation()
cal[0] += float32(gx) / 1000000
cal[1] += float32(gy) / 1000000
cal[2] += float32(gz) / 1000000
time.Sleep(time.Millisecond * 10)
}
cal[0] /= 100
cal[1] /= 100
cal[2] /= 100
}
// Arduino IDE's Serial Plotter
func printPlotter(ax, ay, az, gx, gy, gz, t int32) {
if SHOW_ACCELERATION {
fmt.Printf("AX:%f, AY:%f, AZ:%f,", axis(ax, 0), axis(ay, 0), axis(az, 0))
}
if SHOW_ROTATION {
fmt.Printf("GX:%f, GY:%f, GZ:%f,", axis(gx, cal[0]), axis(gy, cal[1]), axis(gz, cal[2]))
}
if SHOW_TEMPERATURE {
fmt.Printf("T:%f", float32(t)/1000)
}
println()
}
// Any Serial Monitor
func printMonitor(ax, ay, az, gx, gy, gz, t int32) {
if SHOW_ACCELERATION {
fmt.Printf("Acceleration: %f, %f, %f\r\n", axis(ax, 0), axis(ay, 0), axis(az, 0))
}
if SHOW_ROTATION {
fmt.Printf("Rotation: %f, %f, %f\r\n", axis(gx, cal[0]), axis(gy, cal[1]), axis(gz, cal[2]))
}
if SHOW_TEMPERATURE {
fmt.Printf("Temperature C: %f\r\n", float32(t)/1000)
}
println()
}
func axis(raw int32, cal float32) float32 {
return float32(raw)/1000000 - cal
}
+131
View File
@@ -0,0 +1,131 @@
// This is a sensor station that uses a RTL8720DN running on the device UART2.
// It creates an MQTT connection that publishes a message every second
// to an MQTT broker.
//
// In other words:
// Your computer <--> USB-CDC <--> MCU <--> UART2 <--> RTL8720DN <--> Internet <--> MQTT broker.
//
// You must install the Paho MQTT package to build this program:
//
// go get -u github.com/eclipse/paho.mqtt.golang
//
// You can check that mqttpub is running successfully with the following command.
//
// mosquitto_sub -h test.mosquitto.org -t tinygo
//
package main
import (
"fmt"
"math/rand"
"time"
"tinygo.org/x/drivers/net"
"tinygo.org/x/drivers/net/mqtt"
"tinygo.org/x/drivers/rtl8720dn"
)
// You can override the setting with the init() in another source code.
// func init() {
// ssid = "your-ssid"
// password = "your-password"
// debug = true
// server = "tinygo.org"
// }
var (
ssid string
password string
server string = "tcp://test.mosquitto.org:1883"
debug = false
)
var buf [0x400]byte
var lastRequestTime time.Time
var conn net.Conn
var adaptor *rtl8720dn.RTL8720DN
func main() {
err := run()
for err != nil {
fmt.Printf("error: %s\r\n", err.Error())
time.Sleep(5 * time.Second)
}
}
var (
topic = "tinygo"
)
func run() error {
rtl, err := setupRTL8720DN()
if err != nil {
return err
}
net.UseDriver(rtl)
err = rtl.ConnectToAP(ssid, password)
if err != nil {
return err
}
ip, subnet, gateway, err := rtl.GetIP()
if err != nil {
return err
}
fmt.Printf("IP Address : %s\r\n", ip)
fmt.Printf("Mask : %s\r\n", subnet)
fmt.Printf("Gateway : %s\r\n", gateway)
rand.Seed(time.Now().UnixNano())
opts := mqtt.NewClientOptions()
opts.AddBroker(server).SetClientID("tinygo-client-" + randomString(10))
println("Connectng to MQTT...")
cl := mqtt.NewClient(opts)
if token := cl.Connect(); token.Wait() && token.Error() != nil {
failMessage(token.Error().Error())
}
for i := 0; ; i++ {
println("Publishing MQTT message...")
data := []byte(fmt.Sprintf(`{"e":[{"n":"hello %d","v":101}]}`, i))
token := cl.Publish(topic, 0, false, data)
token.Wait()
if err := token.Error(); err != nil {
return err
}
time.Sleep(100 * time.Millisecond)
}
// Right now this code is never reached. Need a way to trigger it...
println("Disconnecting MQTT...")
cl.Disconnect(100)
println("Done.")
return nil
}
// Returns an int >= min, < max
func randomInt(min, max int) int {
return min + rand.Intn(max-min)
}
// Generate a random string of A-Z chars with len = l
func randomString(len int) string {
bytes := make([]byte, len)
for i := 0; i < len; i++ {
bytes[i] = byte(randomInt(65, 90))
}
return string(bytes)
}
func failMessage(msg string) {
for {
println(msg)
time.Sleep(1 * time.Second)
}
}
@@ -0,0 +1,74 @@
//go:build wioterminal
// +build wioterminal
package main
import (
"device/sam"
"machine"
"runtime/interrupt"
"time"
"tinygo.org/x/drivers/rtl8720dn"
)
var (
uart UARTx
)
func handleInterrupt(interrupt.Interrupt) {
// should reset IRQ
uart.Receive(byte((uart.Bus.DATA.Get() & 0xFF)))
uart.Bus.INTFLAG.SetBits(sam.SERCOM_USART_INT_INTFLAG_RXC)
}
func setupRTL8720DN() (*rtl8720dn.RTL8720DN, error) {
machine.RTL8720D_CHIP_PU.Configure(machine.PinConfig{Mode: machine.PinOutput})
machine.RTL8720D_CHIP_PU.Low()
time.Sleep(100 * time.Millisecond)
machine.RTL8720D_CHIP_PU.High()
time.Sleep(1000 * time.Millisecond)
if debug {
waitSerial()
}
uart = UARTx{
UART: &machine.UART{
Buffer: machine.NewRingBuffer(),
Bus: sam.SERCOM0_USART_INT,
SERCOM: 0,
},
}
uart.Interrupt = interrupt.New(sam.IRQ_SERCOM0_2, handleInterrupt)
uart.Configure(machine.UARTConfig{TX: machine.PB24, RX: machine.PC24, BaudRate: 614400})
rtl := rtl8720dn.New(uart)
rtl.Debug(debug)
_, err := rtl.Rpc_tcpip_adapter_init()
if err != nil {
return nil, err
}
return rtl, nil
}
// Wait for user to open serial console
func waitSerial() {
for !machine.Serial.DTR() {
time.Sleep(100 * time.Millisecond)
}
}
type UARTx struct {
*machine.UART
}
func (u UARTx) Read(p []byte) (n int, err error) {
if u.Buffered() == 0 {
time.Sleep(1 * time.Millisecond)
return 0, nil
}
return u.UART.Read(p)
}
+154
View File
@@ -0,0 +1,154 @@
// This is a sensor station that uses a RTL8720DN running on the device UART2.
// It creates an MQTT connection that publishes a message every second
// to an MQTT broker.
//
// In other words:
// Your computer <--> USB-CDC <--> MCU <--> UART2 <--> RTL8720DN <--> Internet <--> MQTT broker.
//
// You must also install the Paho MQTT package to build this program:
//
// go get -u github.com/eclipse/paho.mqtt.golang
//
// You can check that mqttpub/mqttsub is running successfully with the following command.
//
// mosquitto_sub -h test.mosquitto.org -t tinygo/tx
// mosquitto_pub -h test.mosquitto.org -t tinygo/rx -m "hello world"
//
package main
import (
"fmt"
"math/rand"
"time"
"tinygo.org/x/drivers/net"
"tinygo.org/x/drivers/net/mqtt"
"tinygo.org/x/drivers/rtl8720dn"
)
// You can override the setting with the init() in another source code.
// func init() {
// ssid = "your-ssid"
// password = "your-password"
// debug = true
// server = "tinygo.org"
// }
var (
ssid string
password string
server string = "tcp://test.mosquitto.org:1883"
debug = false
)
var buf [0x400]byte
var lastRequestTime time.Time
var conn net.Conn
var adaptor *rtl8720dn.RTL8720DN
func main() {
err := run()
for err != nil {
fmt.Printf("error: %s\r\n", err.Error())
time.Sleep(5 * time.Second)
}
}
// change these to connect to a different UART or pins for the ESP8266/ESP32
var (
cl mqtt.Client
topicTx = "tinygo/tx"
topicRx = "tinygo/rx"
)
func subHandler(client mqtt.Client, msg mqtt.Message) {
fmt.Printf("[%s] ", msg.Topic())
fmt.Printf("%s\r\n", msg.Payload())
}
func run() error {
rtl, err := setupRTL8720DN()
if err != nil {
return err
}
net.UseDriver(rtl)
err = rtl.ConnectToAP(ssid, password)
if err != nil {
return err
}
ip, subnet, gateway, err := rtl.GetIP()
if err != nil {
return err
}
fmt.Printf("IP Address : %s\r\n", ip)
fmt.Printf("Mask : %s\r\n", subnet)
fmt.Printf("Gateway : %s\r\n", gateway)
rand.Seed(time.Now().UnixNano())
opts := mqtt.NewClientOptions()
opts.AddBroker(server).SetClientID("tinygo-client-" + randomString(10))
println("Connecting to MQTT broker at", server)
cl = mqtt.NewClient(opts)
if token := cl.Connect(); token.Wait() && token.Error() != nil {
failMessage(token.Error().Error())
}
// subscribe
token := cl.Subscribe(topicRx, 0, subHandler)
token.Wait()
if token.Error() != nil {
failMessage(token.Error().Error())
}
go publishing()
select {}
// Right now this code is never reached. Need a way to trigger it...
println("Disconnecting MQTT...")
cl.Disconnect(100)
println("Done.")
return nil
}
func publishing() {
for i := 0; ; i++ {
println("Publishing MQTT message...")
data := []byte(fmt.Sprintf(`{"e":[{"n":"hello %d","v":101}]}`, i))
token := cl.Publish(topicTx, 0, false, data)
token.Wait()
if token.Error() != nil {
println(token.Error().Error())
}
time.Sleep(20 * 100 * time.Millisecond)
}
}
// Returns an int >= min, < max
func randomInt(min, max int) int {
return min + rand.Intn(max-min)
}
// Generate a random string of A-Z chars with len = l
func randomString(len int) string {
bytes := make([]byte, len)
for i := 0; i < len; i++ {
bytes[i] = byte(randomInt(65, 90))
}
return string(bytes)
}
func failMessage(msg string) {
for {
println(msg)
time.Sleep(1 * time.Second)
}
}
+74
View File
@@ -0,0 +1,74 @@
//go:build wioterminal
// +build wioterminal
package main
import (
"device/sam"
"machine"
"runtime/interrupt"
"time"
"tinygo.org/x/drivers/rtl8720dn"
)
var (
uart UARTx
)
func handleInterrupt(interrupt.Interrupt) {
// should reset IRQ
uart.Receive(byte((uart.Bus.DATA.Get() & 0xFF)))
uart.Bus.INTFLAG.SetBits(sam.SERCOM_USART_INT_INTFLAG_RXC)
}
func setupRTL8720DN() (*rtl8720dn.RTL8720DN, error) {
machine.RTL8720D_CHIP_PU.Configure(machine.PinConfig{Mode: machine.PinOutput})
machine.RTL8720D_CHIP_PU.Low()
time.Sleep(100 * time.Millisecond)
machine.RTL8720D_CHIP_PU.High()
time.Sleep(1000 * time.Millisecond)
if debug {
waitSerial()
}
uart = UARTx{
UART: &machine.UART{
Buffer: machine.NewRingBuffer(),
Bus: sam.SERCOM0_USART_INT,
SERCOM: 0,
},
}
uart.Interrupt = interrupt.New(sam.IRQ_SERCOM0_2, handleInterrupt)
uart.Configure(machine.UARTConfig{TX: machine.PB24, RX: machine.PC24, BaudRate: 614400})
rtl := rtl8720dn.New(uart)
rtl.Debug(debug)
_, err := rtl.Rpc_tcpip_adapter_init()
if err != nil {
return nil, err
}
return rtl, nil
}
// Wait for user to open serial console
func waitSerial() {
for !machine.Serial.DTR() {
time.Sleep(100 * time.Millisecond)
}
}
type UARTx struct {
*machine.UART
}
func (u UARTx) Read(p []byte) (n int, err error) {
if u.Buffered() == 0 {
time.Sleep(1 * time.Millisecond)
return 0, nil
}
return u.UART.Read(p)
}
+144
View File
@@ -0,0 +1,144 @@
// This is an example of using the rtl8720dn driver to implement a NTP client.
// It creates a UDP connection to request the current time and parse the
// response from a NTP server.
package main
import (
"errors"
"fmt"
"runtime"
"time"
"tinygo.org/x/drivers/net"
)
// IP address of the server aka "hub". Replace with your own info.
// You can override the setting with the init() in another source code.
// func init() {
// ssid = "your-ssid"
// password = "your-password"
// ntpHost = "129.6.15.29"
// debug = true
// }
var (
ssid string
password string
ntpHost = "129.6.15.29"
debug = false
)
const NTP_PACKET_SIZE = 48
var b = make([]byte, NTP_PACKET_SIZE)
func main() {
err := run()
for err != nil {
fmt.Printf("error: %s\r\n", err.Error())
time.Sleep(5 * time.Second)
}
}
func run() error {
rtl, err := setupRTL8720DN()
if err != nil {
return err
}
net.UseDriver(rtl)
err = rtl.ConnectToAP(ssid, password)
if err != nil {
return err
}
ip, subnet, gateway, err := rtl.GetIP()
if err != nil {
return err
}
fmt.Printf("IP Address : %s\r\n", ip)
fmt.Printf("Mask : %s\r\n", subnet)
fmt.Printf("Gateway : %s\r\n", gateway)
// now make UDP connection
hip := net.ParseIP(ntpHost)
raddr := &net.UDPAddr{IP: hip, Port: 123}
laddr := &net.UDPAddr{Port: 2390}
conn, err := net.DialUDP("udp", laddr, raddr)
if err != nil {
return err
}
for {
// send data
println("Requesting NTP time...")
t, err := getCurrentTime(conn)
if err != nil {
message("Error getting current time: %v", err)
} else {
message("NTP time: %v", t)
}
runtime.AdjustTimeOffset(-1 * int64(time.Since(t)))
for i := 0; i < 10; i++ {
message("Current time: %v", time.Now())
time.Sleep(1 * time.Second)
}
}
}
func getCurrentTime(conn *net.UDPSerialConn) (time.Time, error) {
if err := sendNTPpacket(conn); err != nil {
return time.Time{}, err
}
clearBuffer()
for now := time.Now(); time.Since(now) < time.Second; {
time.Sleep(5 * time.Millisecond)
if n, err := conn.Read(b); err != nil {
return time.Time{}, fmt.Errorf("error reading UDP packet: %w", err)
} else if n == 0 {
continue // no packet received yet
} else if n != NTP_PACKET_SIZE {
if n != NTP_PACKET_SIZE {
return time.Time{}, fmt.Errorf("expected NTP packet size of %d: %d", NTP_PACKET_SIZE, n)
}
}
return parseNTPpacket(), nil
}
return time.Time{}, errors.New("no packet received after 1 second")
}
func sendNTPpacket(conn *net.UDPSerialConn) error {
clearBuffer()
b[0] = 0b11100011 // LI, Version, Mode
b[1] = 0 // Stratum, or type of clock
b[2] = 6 // Polling Interval
b[3] = 0xEC // Peer Clock Precision
// 8 bytes of zero for Root Delay & Root Dispersion
b[12] = 49
b[13] = 0x4E
b[14] = 49
b[15] = 52
if _, err := conn.Write(b); err != nil {
return err
}
return nil
}
func parseNTPpacket() time.Time {
// the timestamp starts at byte 40 of the received packet and is four bytes,
// this is NTP time (seconds since Jan 1 1900):
t := uint32(b[40])<<24 | uint32(b[41])<<16 | uint32(b[42])<<8 | uint32(b[43])
const seventyYears = 2208988800
return time.Unix(int64(t-seventyYears), 0)
}
func clearBuffer() {
for i := range b {
b[i] = 0
}
}
func message(format string, args ...interface{}) {
println(fmt.Sprintf(format, args...), "\r")
}
@@ -0,0 +1,72 @@
//go:build wioterminal
// +build wioterminal
package main
import (
"device/sam"
"machine"
"runtime/interrupt"
"time"
"tinygo.org/x/drivers/rtl8720dn"
)
var (
uart UARTx
)
func handleInterrupt(interrupt.Interrupt) {
// should reset IRQ
uart.Receive(byte((uart.Bus.DATA.Get() & 0xFF)))
uart.Bus.INTFLAG.SetBits(sam.SERCOM_USART_INT_INTFLAG_RXC)
}
func setupRTL8720DN() (*rtl8720dn.RTL8720DN, error) {
machine.RTL8720D_CHIP_PU.Configure(machine.PinConfig{Mode: machine.PinOutput})
machine.RTL8720D_CHIP_PU.Low()
time.Sleep(100 * time.Millisecond)
machine.RTL8720D_CHIP_PU.High()
time.Sleep(1000 * time.Millisecond)
waitSerial()
uart = UARTx{
UART: &machine.UART{
Buffer: machine.NewRingBuffer(),
Bus: sam.SERCOM0_USART_INT,
SERCOM: 0,
},
}
uart.Interrupt = interrupt.New(sam.IRQ_SERCOM0_2, handleInterrupt)
uart.Configure(machine.UARTConfig{TX: machine.PB24, RX: machine.PC24, BaudRate: 614400})
rtl := rtl8720dn.New(uart)
rtl.Debug(debug)
_, err := rtl.Rpc_tcpip_adapter_init()
if err != nil {
return nil, err
}
return rtl, nil
}
// Wait for user to open serial console
func waitSerial() {
for !machine.Serial.DTR() {
time.Sleep(100 * time.Millisecond)
}
}
type UARTx struct {
*machine.UART
}
func (u UARTx) Read(p []byte) (n int, err error) {
if u.Buffered() == 0 {
time.Sleep(1 * time.Millisecond)
return 0, nil
}
return u.UART.Read(p)
}
+121
View File
@@ -0,0 +1,121 @@
// This example opens a TCP connection using a device with RTL8720DN firmware
// and sends some data, for the purpose of testing speed and connectivity.
//
// You can open a server to accept connections from this program using:
//
// nc -w 5 -lk 8080
//
package main
import (
"bytes"
"fmt"
"time"
"tinygo.org/x/drivers/net"
)
// You can override the setting with the init() in another source code.
// func init() {
// ssid = "sghome-gw"
// password = "3af25537b4524"
// serverIP = "192.168.1.119"
// debug = true
// }
var (
ssid string
password string
serverIP = ""
debug = false
)
var buf = &bytes.Buffer{}
func main() {
err := run()
for err != nil {
fmt.Printf("error: %s\r\n", err.Error())
time.Sleep(5 * time.Second)
}
}
func run() error {
rtl, err := setupRTL8720DN()
if err != nil {
return err
}
net.UseDriver(rtl)
err = rtl.ConnectToAP(ssid, password)
if err != nil {
return err
}
ip, subnet, gateway, err := rtl.GetIP()
if err != nil {
return err
}
fmt.Printf("IP Address : %s\r\n", ip)
fmt.Printf("Mask : %s\r\n", subnet)
fmt.Printf("Gateway : %s\r\n", gateway)
for {
sendBatch()
time.Sleep(500 * time.Millisecond)
}
println("Done.")
return nil
}
func sendBatch() {
// make TCP connection
ip := net.ParseIP(serverIP)
raddr := &net.TCPAddr{IP: ip, Port: 8080}
laddr := &net.TCPAddr{Port: 8080}
message("---------------\r\nDialing TCP connection")
conn, err := net.DialTCP("tcp", laddr, raddr)
for ; err != nil; conn, err = net.DialTCP("tcp", laddr, raddr) {
message(err.Error())
time.Sleep(5 * time.Second)
}
n := 0
w := 0
start := time.Now()
// send data
message("Sending data")
for i := 0; i < 1000; i++ {
buf.Reset()
fmt.Fprint(buf,
"\r---------------------------- i == ", i, " ----------------------------"+
"\r---------------------------- i == ", i, " ----------------------------")
if w, err = conn.Write(buf.Bytes()); err != nil {
println("error:", err.Error(), "\r")
continue
}
n += w
}
buf.Reset()
ms := time.Now().Sub(start).Milliseconds()
fmt.Fprint(buf, "\nWrote ", n, " bytes in ", ms, " ms\r\n")
message(buf.String())
if _, err := conn.Write(buf.Bytes()); err != nil {
println("error:", err.Error(), "\r")
}
// Right now this code is never reached. Need a way to trigger it...
println("Disconnecting TCP...")
conn.Close()
}
func message(msg string) {
println(msg, "\r")
}
@@ -0,0 +1,72 @@
//go:build wioterminal
// +build wioterminal
package main
import (
"device/sam"
"machine"
"runtime/interrupt"
"time"
"tinygo.org/x/drivers/rtl8720dn"
)
var (
uart UARTx
)
func handleInterrupt(interrupt.Interrupt) {
// should reset IRQ
uart.Receive(byte((uart.Bus.DATA.Get() & 0xFF)))
uart.Bus.INTFLAG.SetBits(sam.SERCOM_USART_INT_INTFLAG_RXC)
}
func setupRTL8720DN() (*rtl8720dn.RTL8720DN, error) {
machine.RTL8720D_CHIP_PU.Configure(machine.PinConfig{Mode: machine.PinOutput})
machine.RTL8720D_CHIP_PU.Low()
time.Sleep(100 * time.Millisecond)
machine.RTL8720D_CHIP_PU.High()
time.Sleep(1000 * time.Millisecond)
waitSerial()
uart = UARTx{
UART: &machine.UART{
Buffer: machine.NewRingBuffer(),
Bus: sam.SERCOM0_USART_INT,
SERCOM: 0,
},
}
uart.Interrupt = interrupt.New(sam.IRQ_SERCOM0_2, handleInterrupt)
uart.Configure(machine.UARTConfig{TX: machine.PB24, RX: machine.PC24, BaudRate: 614400})
rtl := rtl8720dn.New(uart)
rtl.Debug(debug)
_, err := rtl.Rpc_tcpip_adapter_init()
if err != nil {
return nil, err
}
return rtl, nil
}
// Wait for user to open serial console
func waitSerial() {
for !machine.Serial.DTR() {
time.Sleep(100 * time.Millisecond)
}
}
type UARTx struct {
*machine.UART
}
func (u UARTx) Read(p []byte) (n int, err error) {
if u.Buffered() == 0 {
time.Sleep(1 * time.Millisecond)
return 0, nil
}
return u.UART.Read(p)
}
+137
View File
@@ -0,0 +1,137 @@
package main
import (
"bufio"
"fmt"
"strings"
"time"
"tinygo.org/x/drivers/net"
"tinygo.org/x/drivers/net/http"
"tinygo.org/x/drivers/rtl8720dn"
)
// You can override the setting with the init() in another source code.
// func init() {
// ssid = "your-ssid"
// password = "your-password"
// debug = true
// url = "https://www.example.com"
// test_root_ca = "..."
// }
var (
ssid string
password string
url string = "https://www.example.com"
debug = false
)
// Set the test_root_ca created by the following command
// $ openssl s_client -showcerts -verify 5 -connect www.example.com:443 < /dev/null
var test_root_ca = `-----BEGIN CERTIFICATE-----
MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD
QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT
MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j
b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG
9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB
CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97
nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt
43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P
T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4
gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO
BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR
TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw
DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr
hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg
06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF
PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls
YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk
CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4=
-----END CERTIFICATE-----
`
var buf [0x1000]byte
var lastRequestTime time.Time
var conn net.Conn
var adaptor *rtl8720dn.RTL8720DN
func main() {
err := run()
for err != nil {
fmt.Printf("error: %s\r\n", err.Error())
time.Sleep(5 * time.Second)
}
}
func run() error {
rtl, err := setupRTL8720DN()
if err != nil {
return err
}
rtl.SetRootCA(&test_root_ca)
net.UseDriver(rtl)
http.SetBuf(buf[:])
err = rtl.ConnectToAP(ssid, password)
if err != nil {
return err
}
ip, subnet, gateway, err := rtl.GetIP()
if err != nil {
return err
}
fmt.Printf("IP Address : %s\r\n", ip)
fmt.Printf("Mask : %s\r\n", subnet)
fmt.Printf("Gateway : %s\r\n", gateway)
// You can send and receive cookies in the following way
// import "tinygo.org/x/drivers/net/http/cookiejar"
// jar, err := cookiejar.New(nil)
// if err != nil {
// return err
// }
// client := &http.Client{Jar: jar}
// http.DefaultClient = client
cnt := 0
for {
// Various examples are as follows
//
// -- Get
// resp, err := http.Get(url)
//
// -- Post
// body := `cnt=12`
// resp, err = http.Post(url, "application/x-www-form-urlencoded", strings.NewReader(body))
//
// -- Post with JSON
// body := `{"msg": "hello"}`
// resp, err := http.Post(url, "application/json", strings.NewReader(body))
resp, err := http.Get(url)
if err != nil {
return err
}
fmt.Printf("%s %s\r\n", resp.Proto, resp.Status)
for k, v := range resp.Header {
fmt.Printf("%s: %s\r\n", k, strings.Join(v, " "))
}
fmt.Printf("\r\n")
scanner := bufio.NewScanner(resp.Body)
for scanner.Scan() {
fmt.Printf("%s\r\n", scanner.Text())
}
resp.Body.Close()
cnt++
fmt.Printf("-------- %d --------\r\n", cnt)
time.Sleep(10 * time.Second)
}
}
@@ -0,0 +1,72 @@
//go:build wioterminal
// +build wioterminal
package main
import (
"device/sam"
"machine"
"runtime/interrupt"
"time"
"tinygo.org/x/drivers/rtl8720dn"
)
var (
uart UARTx
)
func handleInterrupt(interrupt.Interrupt) {
// should reset IRQ
uart.Receive(byte((uart.Bus.DATA.Get() & 0xFF)))
uart.Bus.INTFLAG.SetBits(sam.SERCOM_USART_INT_INTFLAG_RXC)
}
func setupRTL8720DN() (*rtl8720dn.RTL8720DN, error) {
machine.RTL8720D_CHIP_PU.Configure(machine.PinConfig{Mode: machine.PinOutput})
machine.RTL8720D_CHIP_PU.Low()
time.Sleep(100 * time.Millisecond)
machine.RTL8720D_CHIP_PU.High()
time.Sleep(1000 * time.Millisecond)
waitSerial()
uart = UARTx{
UART: &machine.UART{
Buffer: machine.NewRingBuffer(),
Bus: sam.SERCOM0_USART_INT,
SERCOM: 0,
},
}
uart.Interrupt = interrupt.New(sam.IRQ_SERCOM0_2, handleInterrupt)
uart.Configure(machine.UARTConfig{TX: machine.PB24, RX: machine.PC24, BaudRate: 614400})
rtl := rtl8720dn.New(uart)
rtl.Debug(debug)
_, err := rtl.Rpc_tcpip_adapter_init()
if err != nil {
return nil, err
}
return rtl, nil
}
// Wait for user to open serial console
func waitSerial() {
for !machine.Serial.DTR() {
time.Sleep(100 * time.Millisecond)
}
}
type UARTx struct {
*machine.UART
}
func (u UARTx) Read(p []byte) (n int, err error) {
if u.Buffered() == 0 {
time.Sleep(1 * time.Millisecond)
return 0, nil
}
return u.UART.Read(p)
}
+89
View File
@@ -0,0 +1,89 @@
package main
import (
"fmt"
"strconv"
"time"
"tinygo.org/x/drivers/net"
"tinygo.org/x/drivers/net/http"
)
// IP address of the server aka "hub". Replace with your own info.
// You can override the setting with the init() in another source code.
// func init() {
// ssid = "your-ssid"
// password = "your-password"
// hubIP = "192.168.1.118"
// debug = true
// }
var (
ssid string
password string
hubIP = ""
debug = false
)
var buf [0x400]byte
func main() {
err := run()
for err != nil {
fmt.Printf("error: %s\r\n", err.Error())
time.Sleep(5 * time.Second)
}
}
func run() error {
rtl, err := setupRTL8720DN()
if err != nil {
return err
}
net.UseDriver(rtl)
http.SetBuf(buf[:])
err = rtl.ConnectToAP(ssid, password)
if err != nil {
return err
}
ip, subnet, gateway, err := rtl.GetIP()
if err != nil {
return err
}
fmt.Printf("IP Address : %s\r\n", ip)
fmt.Printf("Mask : %s\r\n", subnet)
fmt.Printf("Gateway : %s\r\n", gateway)
// now make UDP connection
hip := net.ParseIP(hubIP)
raddr := &net.UDPAddr{IP: hip, Port: 2222}
laddr := &net.UDPAddr{Port: 2222}
println("Dialing UDP connection...")
conn, err := net.DialUDP("udp", laddr, raddr)
if err != nil {
return err
}
for {
// send data
println("Sending data...")
for i := 0; i < 25; i++ {
conn.Write([]byte("hello " + strconv.Itoa(i) + "\r\n"))
}
time.Sleep(1000 * time.Millisecond)
}
// Right now this code is never reached. Need a way to trigger it...
println("Disconnecting UDP...")
conn.Close()
println("Done.")
return nil
}
func message(msg string) {
println(msg, "\r")
}
@@ -0,0 +1,72 @@
//go:build wioterminal
// +build wioterminal
package main
import (
"device/sam"
"machine"
"runtime/interrupt"
"time"
"tinygo.org/x/drivers/rtl8720dn"
)
var (
uart UARTx
)
func handleInterrupt(interrupt.Interrupt) {
// should reset IRQ
uart.Receive(byte((uart.Bus.DATA.Get() & 0xFF)))
uart.Bus.INTFLAG.SetBits(sam.SERCOM_USART_INT_INTFLAG_RXC)
}
func setupRTL8720DN() (*rtl8720dn.RTL8720DN, error) {
machine.RTL8720D_CHIP_PU.Configure(machine.PinConfig{Mode: machine.PinOutput})
machine.RTL8720D_CHIP_PU.Low()
time.Sleep(100 * time.Millisecond)
machine.RTL8720D_CHIP_PU.High()
time.Sleep(1000 * time.Millisecond)
waitSerial()
uart = UARTx{
UART: &machine.UART{
Buffer: machine.NewRingBuffer(),
Bus: sam.SERCOM0_USART_INT,
SERCOM: 0,
},
}
uart.Interrupt = interrupt.New(sam.IRQ_SERCOM0_2, handleInterrupt)
uart.Configure(machine.UARTConfig{TX: machine.PB24, RX: machine.PC24, BaudRate: 614400})
rtl := rtl8720dn.New(uart)
rtl.Debug(debug)
_, err := rtl.Rpc_tcpip_adapter_init()
if err != nil {
return nil, err
}
return rtl, nil
}
// Wait for user to open serial console
func waitSerial() {
for !machine.Serial.DTR() {
time.Sleep(100 * time.Millisecond)
}
}
type UARTx struct {
*machine.UART
}
func (u UARTx) Read(p []byte) (n int, err error) {
if u.Buffered() == 0 {
time.Sleep(1 * time.Millisecond)
return 0, nil
}
return u.UART.Read(p)
}
@@ -0,0 +1,136 @@
package main
import (
"bufio"
"fmt"
"image/color"
"strings"
"time"
"tinygo.org/x/drivers/net"
"tinygo.org/x/drivers/net/http"
"tinygo.org/x/tinyfont/proggy"
"tinygo.org/x/tinyterm"
)
// You can override the setting with the init() in another source code.
// If debug is enabled, a serial connection is required.
// func init() {
// ssid = "your-ssid"
// password = "your-password"
// debug = false // true
// server = "tinygo.org"
// }
var (
ssid string
password string
url = "http://tinygo.org/"
debug = false
)
var (
terminal = tinyterm.NewTerminal(display)
black = color.RGBA{0, 0, 0, 255}
white = color.RGBA{255, 255, 255, 255}
red = color.RGBA{255, 0, 0, 255}
blue = color.RGBA{0, 0, 255, 255}
green = color.RGBA{0, 255, 0, 255}
font = &proggy.TinySZ8pt7b
)
var buf [0x400]byte
func main() {
display.FillScreen(black)
backlight.High()
terminal.Configure(&tinyterm.Config{
Font: font,
FontHeight: 10,
FontOffset: 6,
})
err := run()
for err != nil {
fmt.Fprintf(terminal, "error: %s\r\n", err.Error())
time.Sleep(5 * time.Second)
}
}
func run() error {
fmt.Fprintf(terminal, "setupRTL8720DN()\r\n")
if debug {
fmt.Fprintf(terminal, "Running in debug mode.\r\n")
fmt.Fprintf(terminal, "A serial connection is required to continue execution.\r\n")
}
rtl, err := setupRTL8720DN()
if err != nil {
return err
}
net.UseDriver(rtl)
http.SetBuf(buf[:])
fmt.Fprintf(terminal, "ConnectToAP()\r\n")
err = rtl.ConnectToAP(ssid, password)
if err != nil {
return err
}
fmt.Fprintf(terminal, "connected\r\n\r\n")
ip, subnet, gateway, err := rtl.GetIP()
if err != nil {
return err
}
fmt.Fprintf(terminal, "IP Address : %s\r\n", ip)
fmt.Fprintf(terminal, "Mask : %s\r\n", subnet)
fmt.Fprintf(terminal, "Gateway : %s\r\n", gateway)
// You can send and receive cookies in the following way
// import "tinygo.org/x/drivers/net/http/cookiejar"
// jar, err := cookiejar.New(nil)
// if err != nil {
// return err
// }
// client := &http.Client{Jar: jar}
// http.DefaultClient = client
cnt := 0
for {
// Various examples are as follows
//
// -- Get
// resp, err := http.Get(url)
//
// -- Post
// body := `cnt=12`
// resp, err = http.Post(url, "application/x-www-form-urlencoded", strings.NewReader(body))
//
// -- Post with JSON
// body := `{"msg": "hello"}`
// resp, err := http.Post(url, "application/json", strings.NewReader(body))
resp, err := http.Get(url)
if err != nil {
return err
}
fmt.Fprintf(terminal, "%s %s\r\n", resp.Proto, resp.Status)
for k, v := range resp.Header {
fmt.Fprintf(terminal, "%s: %s\r\n", k, strings.Join(v, " "))
}
fmt.Printf("\r\n")
scanner := bufio.NewScanner(resp.Body)
for scanner.Scan() {
fmt.Fprintf(terminal, "%s\r\n", scanner.Text())
}
resp.Body.Close()
cnt++
fmt.Fprintf(terminal, "-------- %d --------\r\n", cnt)
time.Sleep(10 * time.Second)
}
}
@@ -0,0 +1,98 @@
//go:build wioterminal
// +build wioterminal
package main
import (
"device/sam"
"machine"
"runtime/interrupt"
"time"
"tinygo.org/x/drivers/ili9341"
"tinygo.org/x/drivers/rtl8720dn"
)
var (
uart UARTx
)
var (
display = ili9341.NewSPI(
machine.SPI3,
machine.LCD_DC,
machine.LCD_SS_PIN,
machine.LCD_RESET,
)
backlight = machine.LCD_BACKLIGHT
)
func init() {
machine.SPI3.Configure(machine.SPIConfig{
SCK: machine.LCD_SCK_PIN,
SDO: machine.LCD_SDO_PIN,
SDI: machine.LCD_SDI_PIN,
Frequency: 40000000,
})
display.Configure(ili9341.Config{})
backlight.Configure(machine.PinConfig{machine.PinOutput})
}
func handleInterrupt(interrupt.Interrupt) {
// should reset IRQ
uart.Receive(byte((uart.Bus.DATA.Get() & 0xFF)))
uart.Bus.INTFLAG.SetBits(sam.SERCOM_USART_INT_INTFLAG_RXC)
}
func setupRTL8720DN() (*rtl8720dn.RTL8720DN, error) {
machine.RTL8720D_CHIP_PU.Configure(machine.PinConfig{Mode: machine.PinOutput})
machine.RTL8720D_CHIP_PU.Low()
time.Sleep(100 * time.Millisecond)
machine.RTL8720D_CHIP_PU.High()
time.Sleep(1000 * time.Millisecond)
if debug {
waitSerial()
}
uart = UARTx{
UART: &machine.UART{
Buffer: machine.NewRingBuffer(),
Bus: sam.SERCOM0_USART_INT,
SERCOM: 0,
},
}
uart.Interrupt = interrupt.New(sam.IRQ_SERCOM0_2, handleInterrupt)
uart.Configure(machine.UARTConfig{TX: machine.PB24, RX: machine.PC24, BaudRate: 614400})
rtl := rtl8720dn.New(uart)
rtl.Debug(debug)
_, err := rtl.Rpc_tcpip_adapter_init()
if err != nil {
return nil, err
}
return rtl, nil
}
// Wait for user to open serial console
func waitSerial() {
for !machine.Serial.DTR() {
time.Sleep(100 * time.Millisecond)
}
}
type UARTx struct {
*machine.UART
}
func (u UARTx) Read(p []byte) (n int, err error) {
if u.Buffered() == 0 {
time.Sleep(1 * time.Millisecond)
return 0, nil
}
return u.UART.Read(p)
}
+104
View File
@@ -0,0 +1,104 @@
package main
import (
"bufio"
"fmt"
"strings"
"time"
"tinygo.org/x/drivers/net"
"tinygo.org/x/drivers/net/http"
)
// You can override the setting with the init() in another source code.
// func init() {
// ssid = "your-ssid"
// password = "your-password"
// url = "http://tinygo.org/"
// debug = true
// }
var (
ssid string
password string
url = "http://tinygo.org/"
debug = false
)
var buf [0x400]byte
func main() {
err := run()
for err != nil {
fmt.Printf("error: %s\r\n", err.Error())
time.Sleep(5 * time.Second)
}
}
func run() error {
rtl, err := setupRTL8720DN()
if err != nil {
return err
}
net.UseDriver(rtl)
http.SetBuf(buf[:])
err = rtl.ConnectToAP(ssid, password)
if err != nil {
return err
}
ip, subnet, gateway, err := rtl.GetIP()
if err != nil {
return err
}
fmt.Printf("IP Address : %s\r\n", ip)
fmt.Printf("Mask : %s\r\n", subnet)
fmt.Printf("Gateway : %s\r\n", gateway)
// You can send and receive cookies in the following way
// import "tinygo.org/x/drivers/net/http/cookiejar"
// jar, err := cookiejar.New(nil)
// if err != nil {
// return err
// }
// client := &http.Client{Jar: jar}
// http.DefaultClient = client
cnt := 0
for {
// Various examples are as follows
//
// -- Get
// resp, err := http.Get(url)
//
// -- Post
// body := `cnt=12`
// resp, err = http.Post(url, "application/x-www-form-urlencoded", strings.NewReader(body))
//
// -- Post with JSON
// body := `{"msg": "hello"}`
// resp, err := http.Post(url, "application/json", strings.NewReader(body))
resp, err := http.Get(url)
if err != nil {
return err
}
fmt.Printf("%s %s\r\n", resp.Proto, resp.Status)
for k, v := range resp.Header {
fmt.Printf("%s: %s\r\n", k, strings.Join(v, " "))
}
fmt.Printf("\r\n")
scanner := bufio.NewScanner(resp.Body)
for scanner.Scan() {
fmt.Printf("%s\r\n", scanner.Text())
}
resp.Body.Close()
cnt++
fmt.Printf("-------- %d --------\r\n", cnt)
time.Sleep(10 * time.Second)
}
}
@@ -0,0 +1,72 @@
//go:build wioterminal
// +build wioterminal
package main
import (
"device/sam"
"machine"
"runtime/interrupt"
"time"
"tinygo.org/x/drivers/rtl8720dn"
)
var (
uart UARTx
)
func handleInterrupt(interrupt.Interrupt) {
// should reset IRQ
uart.Receive(byte((uart.Bus.DATA.Get() & 0xFF)))
uart.Bus.INTFLAG.SetBits(sam.SERCOM_USART_INT_INTFLAG_RXC)
}
func setupRTL8720DN() (*rtl8720dn.RTL8720DN, error) {
machine.RTL8720D_CHIP_PU.Configure(machine.PinConfig{Mode: machine.PinOutput})
machine.RTL8720D_CHIP_PU.Low()
time.Sleep(100 * time.Millisecond)
machine.RTL8720D_CHIP_PU.High()
time.Sleep(1000 * time.Millisecond)
waitSerial()
uart = UARTx{
UART: &machine.UART{
Buffer: machine.NewRingBuffer(),
Bus: sam.SERCOM0_USART_INT,
SERCOM: 0,
},
}
uart.Interrupt = interrupt.New(sam.IRQ_SERCOM0_2, handleInterrupt)
uart.Configure(machine.UARTConfig{TX: machine.PB24, RX: machine.PC24, BaudRate: 614400})
rtl := rtl8720dn.New(uart)
rtl.Debug(debug)
_, err := rtl.Rpc_tcpip_adapter_init()
if err != nil {
return nil, err
}
return rtl, nil
}
// Wait for user to open serial console
func waitSerial() {
for !machine.Serial.DTR() {
time.Sleep(100 * time.Millisecond)
}
}
type UARTx struct {
*machine.UART
}
func (u UARTx) Read(p []byte) (n int, err error) {
if u.Buffered() == 0 {
time.Sleep(1 * time.Millisecond)
return 0, nil
}
return u.UART.Read(p)
}
+192
View File
@@ -0,0 +1,192 @@
package main
import (
"fmt"
"machine"
"strconv"
"time"
"tinygo.org/x/drivers/net/http"
)
// You can override the setting with the init() in another source code.
// func init() {
// ssid = "your-ssid"
// password = "your-password"
// debug = true
// }
var (
ssid string
password string
debug = false
)
var led = machine.LED
var backlight = machine.LCD_BACKLIGHT
func main() {
led.Configure(machine.PinConfig{Mode: machine.PinOutput})
backlight.Configure(machine.PinConfig{Mode: machine.PinOutput})
err := run()
for err != nil {
fmt.Printf("error: %s\r\n", err.Error())
time.Sleep(5 * time.Second)
}
}
func run() error {
rtl, err := setupRTL8720DN()
if err != nil {
return err
}
http.UseDriver(rtl)
err = rtl.ConnectToAP(ssid, password)
if err != nil {
return err
}
ip, subnet, gateway, err := rtl.GetIP()
if err != nil {
return err
}
fmt.Printf("IP Address : %s\r\n", ip)
fmt.Printf("Mask : %s\r\n", subnet)
fmt.Printf("Gateway : %s\r\n", gateway)
http.HandleFunc("/", root)
http.HandleFunc("/hello", hello)
http.HandleFunc("/cnt", cnt)
http.HandleFunc("/6", sixlines)
http.HandleFunc("/off", LED_OFF)
http.HandleFunc("/on", LED_ON)
if err := http.ListenAndServe(":80", nil); err != nil {
message(err.Error())
}
return nil
}
func root(w http.ResponseWriter, r *http.Request) {
access := 1
cookie, err := r.Cookie("access")
if err != nil {
if err == http.ErrNoCookie {
cookie = &http.Cookie{
Name: "access",
Value: "1",
}
} else {
http.Error(w, fmt.Sprintf("%s", err.Error()), http.StatusBadRequest)
return
}
} else {
v, err := strconv.ParseInt(cookie.Value, 10, 0)
if err != nil {
http.Error(w, fmt.Sprintf("invalid cookie.Value : %s", cookie.Value), http.StatusBadRequest)
return
}
cookie.Value = fmt.Sprintf("%d", v+1)
access = int(v) + 1
}
http.SetCookie(w, cookie)
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, `
<html>
<head>
<title>TinyGo HTTP Server</title>
<script language="javascript" type="text/javascript">
var counter = 0
function ledOn() { fetch("/on").then(response => response.text()).then(text => { led.innerHTML = "<p>on</p>"; }); }
function ledOff() { fetch("/off").then(response => response.text()).then(text => { led.innerHTML = "<p>off</p>"; }); }
function fetchCnt() { fetch("/cnt").then(response => response.json()).then(json => { counter = json.cnt; cnt.innerHTML = counter; }); }
function incrCnt() { counter = counter + 1; fetch("/cnt?cnt=" + counter, { method: 'POST' }).then(response => response.json()).then(json => { counter = json.cnt; cnt.innerHTML = counter; }); }
function setCnt() { fetch("/cnt", {
method: "POST",
body: "cnt=" + document.getElementsByName("cnt")[0].value,
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
}).then(response => response.json()).then(json => { counter = json.cnt; cnt.innerHTML = counter; }); return false; }
function onLoad() { fetchCnt(); }
</script>
</head>
<body onLoad="onLoad()">
<h5>TinyGo HTTP Server</h5>
<p>
access: %d
</p>
<a href="/hello">/hello</a><br>
<a href="/6">/6</a><br>
<p>
LED<br>
<a href="javascript:ledOn();">/on</a><br>
<a href="javascript:ledOff();">/off</a><br>
</p>
<p>
<a href="/cnt">/cnt</a><br>
cnt: <span id="cnt"></span><br>
<a href="javascript:incrCnt()">incrCnt()</a><br>
<form id="form1" style="display: inline" onSubmit="return setCnt()">
<input type="text" name="cnt">
<input type="button" value="set cnt", onClick="setCnt()">
</form>
</p>
</body>
</html>
`, access)
}
func sixlines(w http.ResponseWriter, r *http.Request) {
// https://fukuno.jig.jp/3267
fmt.Fprint(w, `<body onload='onkeydown=e=>K=parseInt(e.key[5]||6,28)/3-8;Z=X=[B=A=12];Y=_=>`+
`{for(C=[q=c=i=4];f=i--*K;c-=!Z[h+(K+6?p+K:C[i]=p*A-(p/9|0)*145)])p=B[i];for(c?0:K+6?h+=K:B=C;`+
`i=K=q--;f+=Z[A+p])X[p=h+B[q]]=1;h+=A;if(f|B)for(Z=X,X=[l=228],B=[[-7,-20,6,h=17,-9,3,3][t=++t%7]-4,0,1,t-6?-A:2];l--;)`+
`for(l%A?l-=l%A*!Z[l]:(P++,c=l+=A);--c>A;)Z[c]=Z[c-A];for(S="";i<240;S+=X[i]|(X[i]=Z[i]|=++i%A<2|i>228)?i%A?"■":"■<br>":" ");`+
`D.innerHTML=S+P;setTimeout(Y,i-P)};Y(h=K=t=P=0)'id=D>`)
}
func LED_ON(w http.ResponseWriter, r *http.Request) {
led.High()
backlight.High()
w.Header().Set(`Content-Type`, `text/plain; charset=UTF-8`)
fmt.Fprintf(w, "led.High()")
}
func LED_OFF(w http.ResponseWriter, r *http.Request) {
led.Low()
backlight.Low()
w.Header().Set(`Content-Type`, `text/plain; charset=UTF-8`)
fmt.Fprintf(w, "led.Low()")
}
func hello(w http.ResponseWriter, r *http.Request) {
w.Header().Set(`Content-Type`, `text/plain; charset=UTF-8`)
fmt.Fprintf(w, "hello")
}
var counter int
func cnt(w http.ResponseWriter, r *http.Request) {
r.ParseForm()
if r.Method == "POST" {
c := r.Form.Get("cnt")
if c != "" {
i64, _ := strconv.ParseInt(c, 0, 0)
counter = int(i64)
}
}
w.Header().Set(`Content-Type`, `application/json`)
fmt.Fprintf(w, `{"cnt": %d}`, counter)
}
func message(msg string) {
println(msg, "\r")
}
@@ -0,0 +1,74 @@
//go:build wioterminal
// +build wioterminal
package main
import (
"device/sam"
"machine"
"runtime/interrupt"
"time"
"tinygo.org/x/drivers/rtl8720dn"
)
var (
uart UARTx
)
func handleInterrupt(interrupt.Interrupt) {
// should reset IRQ
uart.Receive(byte((uart.Bus.DATA.Get() & 0xFF)))
uart.Bus.INTFLAG.SetBits(sam.SERCOM_USART_INT_INTFLAG_RXC)
}
func setupRTL8720DN() (*rtl8720dn.RTL8720DN, error) {
machine.RTL8720D_CHIP_PU.Configure(machine.PinConfig{Mode: machine.PinOutput})
machine.RTL8720D_CHIP_PU.Low()
time.Sleep(100 * time.Millisecond)
machine.RTL8720D_CHIP_PU.High()
time.Sleep(1000 * time.Millisecond)
if debug {
waitSerial()
}
uart = UARTx{
UART: &machine.UART{
Buffer: machine.NewRingBuffer(),
Bus: sam.SERCOM0_USART_INT,
SERCOM: 0,
},
}
uart.Interrupt = interrupt.New(sam.IRQ_SERCOM0_2, handleInterrupt)
uart.Configure(machine.UARTConfig{TX: machine.PB24, RX: machine.PC24, BaudRate: 614400})
rtl := rtl8720dn.New(uart)
rtl.Debug(debug)
_, err := rtl.Rpc_tcpip_adapter_init()
if err != nil {
return nil, err
}
return rtl, nil
}
// Wait for user to open serial console
func waitSerial() {
for !machine.Serial.DTR() {
time.Sleep(100 * time.Millisecond)
}
}
type UARTx struct {
*machine.UART
}
func (u UARTx) Read(p []byte) (n int, err error) {
if u.Buffered() == 0 {
time.Sleep(1 * time.Millisecond)
return 0, nil
}
return u.UART.Read(p)
}
+363
View File
@@ -0,0 +1,363 @@
package main
import (
"fmt"
"io"
"machine"
"os"
"strconv"
"strings"
"time"
"tinygo.org/x/drivers/sdcard"
)
const consoleBufLen = 64
const storageBufLen = 1024
var (
debug = false
input [consoleBufLen]byte
store [storageBufLen]byte
console = machine.Serial
dev *sdcard.Device
commands map[string]cmdfunc = map[string]cmdfunc{
"": cmdfunc(noop),
"help": cmdfunc(help),
"dbg": cmdfunc(dbg),
"erase": cmdfunc(erase),
"lsblk": cmdfunc(lsblk),
"write": cmdfunc(write),
"xxd": cmdfunc(xxd),
}
his history
)
type history struct {
buf [32]string
wp int
idx int
}
func (h *history) Add(cmd string) {
if len(cmd) == 0 {
h.idx = h.wp
return
}
if h.wp == len(h.buf)-1 {
for i := 1; i < len(h.buf); i++ {
h.buf[i-1] = h.buf[i]
}
h.wp--
}
h.buf[h.wp] = cmd
h.wp++
h.idx = h.wp
}
func (h *history) PeekPrev() string {
if h.idx > 0 {
h.idx--
}
return h.buf[h.idx]
}
func (h *history) PeekNext() string {
if h.idx < h.wp {
h.idx++
}
return h.buf[h.idx]
}
type cmdfunc func(argv []string)
const (
StateInput = iota
StateEscape
StateEscBrc
StateCSI
)
func RunFor(device *sdcard.Device) {
dev = device
prompt()
var state = StateInput
for i := 0; ; {
if console.Buffered() > 0 {
data, _ := console.ReadByte()
if debug {
fmt.Printf("\rdata: %x, his.idx: %d\r\n\r", data, his.idx)
prompt()
console.Write(input[:i])
}
switch state {
case StateInput:
switch data {
case 0x8:
fallthrough
case 0x7f: // this is probably wrong... works on my machine tho :)
// backspace
if i > 0 {
i -= 1
console.Write([]byte{0x8, 0x20, 0x8})
}
case 13:
// return key
console.Write([]byte("\r\n"))
runCommand(string(input[:i]))
his.Add(string(input[:i]))
prompt()
i = 0
continue
case 27:
// escape
state = StateEscape
default:
// anything else, just echo the character if it is printable
if strconv.IsPrint(rune(data)) {
if i < (consoleBufLen - 1) {
console.WriteByte(data)
input[i] = data
i++
}
}
}
case StateEscape:
switch data {
case 0x5b:
state = StateEscBrc
default:
state = StateInput
}
case StateEscBrc:
switch data {
case 0x41:
// up
println()
prompt()
cmd := his.PeekPrev()
i = len(cmd)
copy(input[:i], []byte(cmd))
console.Write(input[:i])
state = StateInput
case 0x42:
//down
println()
prompt()
cmd := his.PeekNext()
i = len(cmd)
copy(input[:i], []byte(cmd))
console.Write(input[:i])
state = StateInput
default:
// TODO: handle escape sequences
state = StateInput
}
default:
// TODO: handle escape sequences
state = StateInput
}
} else {
time.Sleep(1 * time.Millisecond)
}
}
}
func runCommand(line string) {
argv := strings.SplitN(strings.TrimSpace(line), " ", -1)
cmd := argv[0]
cmdfn, ok := commands[cmd]
if !ok {
println("unknown command: " + line)
return
}
cmdfn(argv)
}
func noop(argv []string) {}
func help(argv []string) {
fmt.Printf("help\r\n")
fmt.Printf("dbg\r\n")
fmt.Printf("erase\r\n")
fmt.Printf("lsblk\r\n")
fmt.Printf("write <hex offset> <bytes>\r\n")
fmt.Printf("xxd <start address> <length>\r\n")
}
func dbg(argv []string) {
if debug {
debug = false
println("Console debbuging off")
} else {
debug = true
println("Console debbuging on")
}
}
func lsblk(argv []string) {
csd := dev.CSD
sectors, err := csd.Sectors()
if err != nil {
fmt.Printf("%s\r\n", err.Error())
return
}
cid := dev.CID
fmt.Printf(
"\r\n-------------------------------------\r\n"+
" Device Information: \r\n"+
"-------------------------------------\r\n"+
" JEDEC ID: %v\r\n"+
" Serial: %v\r\n"+
" Status 1: %02x\r\n"+
" Status 2: %02x\r\n"+
" \r\n"+
" Max clock speed (MHz): %d\r\n"+
" Has Sector Protection: %t\r\n"+
" Supports Fast Reads: %t\r\n"+
" Supports QSPI Reads: %t\r\n"+
" Supports QSPI Write: %t\r\n"+
" Write Status Split: %t\r\n"+
" Single Status Byte: %t\r\n"+
"-Sectors: %d\r\n"+
"-Bytes (Sectors * 512) %d\r\n"+
"-ManufacturerID %02X\r\n"+
"-OEMApplicationID %04X\r\n"+
"-ProductName %s\r\n"+
"-ProductVersion %s\r\n"+
"-ProductSerialNumber %08X\r\n"+
"-ManufacturingYear %02X\r\n"+
"-ManufacturingMonth %02X\r\n"+
"-Always1 %d\r\n"+
"-CRC %02X\r\n"+
"-------------------------------------\r\n\r\n",
"attrs.JedecID", // attrs.JedecID,
cid.ProductSerialNumber, // serialNumber1,
0, // status1,
0, // status2,
csd.TRAN_SPEED, // attrs.MaxClockSpeedMHz,
false, // attrs.HasSectorProtection,
false, // attrs.SupportsFastRead,
false, // attrs.SupportsQSPI,
false, // attrs.SupportsQSPIWrites,
false, // attrs.WriteStatusSplit,
false, // attrs.SingleStatusByte,
sectors,
csd.Size(),
cid.ManufacturerID,
cid.OEMApplicationID,
cid.ProductName,
cid.ProductVersion,
cid.ProductSerialNumber,
cid.ManufacturingYear,
cid.ManufacturingMonth,
cid.Always1,
cid.CRC,
)
}
func erase(argv []string) {
fmt.Printf("erase - not impl\r\n")
}
var writeBuf [256]byte
func write(argv []string) {
if len(argv) < 3 {
println("usage: write <hex offset> <bytes>")
return
}
var err error
var addr uint64 = 0x0
if addr, err = strconv.ParseUint(argv[1], 16, 32); err != nil {
println("Invalid address: " + err.Error() + "\r\n")
return
}
buf := writeBuf[:0]
for i := 0; i < len(argv[2]); i += 2 {
var b uint64
if b, err = strconv.ParseUint(argv[2][i:i+2], 16, 8); err != nil {
println("Invalid bytes: " + err.Error() + "\r\n")
return
}
buf = append(buf, byte(b))
}
if _, err = dev.WriteAt(buf, int64(addr)); err != nil {
println("Write error: " + err.Error() + "\r\n")
}
}
func xxd(argv []string) {
var err error
var addr uint64 = 0x0
var size int = 64
switch len(argv) {
case 3:
if size, err = strconv.Atoi(argv[2]); err != nil {
println("Invalid size argument: " + err.Error() + "\r\n")
return
}
if size > storageBufLen || size < 1 {
fmt.Printf("Size of hexdump must be greater than 0 and less than %d\r\n", storageBufLen)
return
}
fallthrough
case 2:
if addr, err = strconv.ParseUint(argv[1], 16, 32); err != nil {
println("Invalid address: " + err.Error() + "\r\n")
return
}
fallthrough
case 1:
// no args supplied, so nothing to do here, just use the defaults
default:
println("usage: xxd <hex address, ex: 0xA0> <size of hexdump in bytes>\r\n")
return
}
buf := store[0:size]
_, err = dev.ReadAt(buf, int64(addr))
if err != nil {
fmt.Printf("xxd err : %s\r\n", err.Error())
}
xxdfprint(os.Stdout, uint32(addr), buf)
}
func xxdfprint(w io.Writer, offset uint32, b []byte) {
var l int
var buf16 = make([]byte, 16)
for i, c := 0, len(b); i < c; i += 16 {
l = i + 16
if l >= c {
l = c
}
fmt.Fprintf(w, "%08x: % x ", offset+uint32(i), b[i:l])
for j, n := 0, l-i; j < 16; j++ {
if j >= n || !strconv.IsPrint(rune(b[i+j])) || b[i+j] >= 0x80 {
buf16[j] = '.'
} else {
buf16[j] = b[i+j]
}
}
console.Write(buf16)
println()
}
}
func prompt() {
print("==> ")
}
+18
View File
@@ -0,0 +1,18 @@
//go:build feather_m4 || feather_m4_can || feather_nrf52840
// +build feather_m4 feather_m4_can feather_nrf52840
package main
import (
"machine"
)
func init() {
spi = machine.SPI0
sckPin = machine.SPI0_SCK_PIN
sdoPin = machine.SPI0_SDO_PIN
sdiPin = machine.SPI0_SDI_PIN
csPin = machine.D10
ledPin = machine.LED
}
@@ -0,0 +1,18 @@
//go:build grandcentral_m4
// +build grandcentral_m4
package main
import (
"machine"
)
func init() {
spi = machine.SPI1
sckPin = machine.SDCARD_SCK_PIN
sdoPin = machine.SDCARD_SDO_PIN
sdiPin = machine.SDCARD_SDI_PIN
csPin = machine.SDCARD_CS_PIN
ledPin = machine.LED
}
+18
View File
@@ -0,0 +1,18 @@
//go:build atsamd21 && !p1am_100
// +build atsamd21,!p1am_100
package main
import (
"machine"
)
func init() {
spi = machine.SPI0
sckPin = machine.SPI0_SCK_PIN
sdoPin = machine.SPI0_SDO_PIN
sdiPin = machine.SPI0_SDI_PIN
csPin = machine.D2
ledPin = machine.LED
}
+51
View File
@@ -0,0 +1,51 @@
package main
import (
"fmt"
"machine"
"time"
"tinygo.org/x/drivers/sdcard"
)
var (
spi machine.SPI
sckPin machine.Pin
sdoPin machine.Pin
sdiPin machine.Pin
csPin machine.Pin
ledPin machine.Pin
)
func main() {
waitSerial()
fmt.Printf("sdcard console\r\n")
led := ledPin
led.Configure(machine.PinConfig{Mode: machine.PinOutput})
sd := sdcard.New(spi, sckPin, sdoPin, sdiPin, csPin)
err := sd.Configure()
if err != nil {
fmt.Printf("%s\r\n", err.Error())
for {
time.Sleep(time.Hour)
}
}
go RunFor(&sd)
for {
led.High()
time.Sleep(200 * time.Millisecond)
led.Low()
time.Sleep(200 * time.Millisecond)
}
}
// Wait for user to open serial console
func waitSerial() {
for !machine.Serial.DTR() {
time.Sleep(100 * time.Millisecond)
}
}
+18
View File
@@ -0,0 +1,18 @@
//go:build p1am_100
// +build p1am_100
package main
import (
"machine"
)
func init() {
spi = machine.SDCARD_SPI
sckPin = machine.SDCARD_SCK_PIN
sdoPin = machine.SDCARD_SDO_PIN
sdiPin = machine.SDCARD_SDI_PIN
csPin = machine.SDCARD_SS_PIN
ledPin = machine.LED
}
+18
View File
@@ -0,0 +1,18 @@
//go:build pygamer
// +build pygamer
package main
import (
"machine"
)
func init() {
spi = machine.SPI0
sckPin = machine.SPI0_SCK_PIN
sdoPin = machine.SPI0_SDO_PIN
sdiPin = machine.SPI0_SDI_PIN
csPin = machine.D4
ledPin = machine.LED
}
+18
View File
@@ -0,0 +1,18 @@
//go:build pyportal
// +build pyportal
package main
import (
"machine"
)
func init() {
spi = machine.SPI0
sckPin = machine.SPI0_SCK_PIN
sdoPin = machine.SPI0_SDO_PIN
sdiPin = machine.SPI0_SDI_PIN
csPin = machine.D32 // SD_CS
ledPin = machine.LED
}
+18
View File
@@ -0,0 +1,18 @@
//go:build wioterminal
// +build wioterminal
package main
import (
"machine"
)
func init() {
spi = machine.SPI2
sckPin = machine.SCK2
sdoPin = machine.SDO2
sdiPin = machine.SDI2
csPin = machine.SS2
ledPin = machine.LED
}
+543
View File
@@ -0,0 +1,543 @@
//go:build tinygo
// +build tinygo
package console
import (
"fmt"
"io"
"machine"
"os"
"strconv"
"strings"
"time"
"tinygo.org/x/tinyfs"
)
const consoleBufLen = 64
const startBlock = 0
const blockCount = 0
var (
debug = false
input [consoleBufLen]byte
console = machine.Serial
readyLED = machine.LED
flashdev tinyfs.BlockDevice
fs tinyfs.Filesystem
currdir = "/"
commands = map[string]cmdfunc{
"": noop,
"dbg": dbg,
"help": help,
"lsblk": lsblk,
"mount": mount,
"umount": umount,
"format": format,
"xxd": xxd,
"ls": ls,
"samples": samples,
"mkdir": mkdir,
"cat": cat,
"create": create,
"write": write,
"rm": rm,
}
)
type cmdfunc func(argv []string)
const (
StateInput = iota
StateEscape
StateEscBrc
StateCSI
)
func RunFor(dev tinyfs.BlockDevice, filesys tinyfs.Filesystem) {
flashdev = dev
fs = filesys
readyLED.Configure(machine.PinConfig{Mode: machine.PinOutput})
readyLED.High()
readyLED.Low()
println("SPI Configured. Reading flash info")
/*
lfsConfig := flashlfs.NewConfig()
if blockCount == 0 {
lfsConfig.BlockCount = (flashdev.Attrs().TotalSize / lfsConfig.BlockSize) - startBlock
} else {
lfsConfig.BlockCount = blockCount
}
println("Start block:", startBlock)
println("Block count:", lfsConfig.BlockCount)
blockdev = flashlfs.NewBlockDevice(flashdev, startBlock, lfsConfig.BlockSize)
*/
prompt()
var state = StateInput
for i := 0; ; {
if console.Buffered() > 0 {
data, _ := console.ReadByte()
if debug {
fmt.Printf("\rdata: %x\r\n\r", data)
prompt()
console.Write(input[:i])
}
switch state {
case StateInput:
switch data {
case 0x8:
fallthrough
case 0x7f: // this is probably wrong... works on my machine tho :)
// backspace
if i > 0 {
i -= 1
console.Write([]byte{0x8, 0x20, 0x8})
}
case 13:
// return key
if console.Buffered() > 0 {
data, _ := console.ReadByte()
if data != 10 {
println("\r\nunexpected: \r", int(data))
}
}
console.Write([]byte("\r\n"))
runCommand(string(input[:i]))
prompt()
i = 0
continue
case 27:
// escape
state = StateEscape
default:
// anything else, just echo the character if it is printable
if strconv.IsPrint(rune(data)) {
if i < (consoleBufLen - 1) {
console.WriteByte(data)
input[i] = data
i++
}
}
}
case StateEscape:
switch data {
case 0x5b:
state = StateEscBrc
default:
state = StateInput
}
default:
// TODO: handle escape sequences
state = StateInput
}
}
}
}
func runCommand(line string) {
argv := strings.SplitN(strings.TrimSpace(line), " ", -1)
cmd := argv[0]
cmdfn, ok := commands[cmd]
if !ok {
println("unknown command: " + line)
return
}
cmdfn(argv)
}
func noop(argv []string) {}
func help(argv []string) {
fmt.Printf("help\r\n")
fmt.Printf(" show help\r\n")
fmt.Printf("dbg\r\n")
fmt.Printf(" toggle debug mode\r\n")
fmt.Printf("xxd <hex address, ex: 0xA0> <size of hexdump in bytes>\r\n")
fmt.Printf(" hexdump the specified address\r\n")
fmt.Printf("ls <target file>\r\n")
fmt.Printf(" list information\r\n")
fmt.Printf("samples\r\n")
fmt.Printf(" write some files in the root directory\r\n")
fmt.Printf("mkdir <target dir>\r\n")
fmt.Printf(" create directory\r\n")
fmt.Printf("cat <target file>\r\n")
fmt.Printf(" print the contents of file\r\n")
fmt.Printf("create <target file>\r\n")
fmt.Printf(" create file\r\n")
fmt.Printf("write <target file>\r\n")
fmt.Printf(" write to file (press CTRL-D to exit)\r\n")
fmt.Printf("rm\r\n")
}
func dbg(argv []string) {
if debug {
debug = false
println("Console debugging off")
} else {
debug = true
println("Console debugging on")
}
}
func lsblk(argv []string) {
fmt.Printf("lsblk : not implement\r\n")
}
func mount(argv []string) {
if err := fs.Mount(); err != nil {
println("Could not mount LittleFS filesystem: " + err.Error() + "\r\n")
} else {
println("Successfully mounted LittleFS filesystem.\r\n")
}
}
func format(argv []string) {
if err := fs.Format(); err != nil {
println("Could not format LittleFS filesystem: " + err.Error() + "\r\n")
} else {
println("Successfully formatted LittleFS filesystem.\r\n")
}
}
func umount(argv []string) {
if err := fs.Unmount(); err != nil {
println("Could not unmount LittleFS filesystem: " + err.Error() + "\r\n")
} else {
println("Successfully unmounted LittleFS filesystem.\r\n")
}
}
/*
var err error
if fatfs == nil {
fatfs, err = fat.New(fatdisk)
if err != nil {
fatfs = nil
println("could not load FAT filesystem: " + err.Error() + "\r\n")
}
fmt.Printf("loaded fs\r\n")
}
if rootdir == nil {
rootdir, err = fatfs.RootDir()
if err != nil {
rootdir = nil
println("could not load rootdir: " + err.Error() + "\r\n")
}
fmt.Printf("loaded rootdir\r\n")
}
if currdir == nil {
currdir = rootdir
}
*/
func ls(argv []string) {
path := "/"
if len(argv) > 1 {
path = strings.TrimSpace(argv[1])
}
dir, err := fs.Open(path)
if err != nil {
fmt.Printf("Could not open directory %s: %v\r\n", path, err)
return
}
defer dir.Close()
infos, err := dir.Readdir(0)
_ = infos
if err != nil {
fmt.Printf("Could not read directory %s: %v\r\n", path, err)
return
}
for _, info := range infos {
s := "-rwxrwxrwx"
if info.IsDir() {
s = "drwxrwxrwx"
}
fmt.Printf("%s %5d %s\r\n", s, info.Size(), info.Name())
}
}
func mkdir(argv []string) {
tgt := ""
if len(argv) == 2 {
tgt = strings.TrimSpace(argv[1])
}
if debug {
println("Trying mkdir to " + tgt)
}
if tgt == "" {
println("Usage: mkdir <target dir>")
return
}
err := fs.Mkdir(tgt, 0777)
if err != nil {
println("Could not mkdir " + tgt + ": " + err.Error())
}
}
func rm(argv []string) {
tgt := ""
if len(argv) == 2 {
tgt = strings.TrimSpace(argv[1])
}
if debug {
println("Trying rm to " + tgt)
}
if tgt == "" {
println("Usage: rm <target dir>")
return
}
err := fs.Remove(tgt)
if err != nil {
println("Could not rm " + tgt + ": " + err.Error())
}
}
func samples(argv []string) {
buf := make([]byte, 90)
for i := 0; i < 5; i++ {
name := fmt.Sprintf("file%d.txt", i)
if bytes, err := createSampleFile(name, buf); err != nil {
fmt.Printf("%s\r\n", err)
return
} else {
fmt.Printf("wrote %d bytes to %s\r\n", bytes, name)
}
}
}
func create(argv []string) {
tgt := ""
if len(argv) == 2 {
tgt = strings.TrimSpace(argv[1])
}
if debug {
println("Trying create to " + tgt)
}
buf := make([]byte, 90)
if bytes, err := createSampleFile(tgt, buf); err != nil {
fmt.Printf("%s\r\n", err)
return
} else {
fmt.Printf("wrote %d bytes to %s\r\n", bytes, tgt)
}
}
func write(argv []string) {
tgt := ""
if len(argv) == 2 {
tgt = strings.TrimSpace(argv[1])
}
if debug {
println("Trying receive to " + tgt)
}
buf := make([]byte, 1)
f, err := fs.OpenFile(tgt, os.O_CREATE|os.O_WRONLY|os.O_TRUNC)
if err != nil {
fmt.Printf("error opening %s: %s\r\n", tgt, err.Error())
return
}
defer f.Close()
var n int
for {
if console.Buffered() > 0 {
data, _ := console.ReadByte()
switch data {
case 0x04:
fmt.Printf("wrote %d bytes to %s\r\n", n, tgt)
return
default:
// anything else, just echo the character if it is printable
if strconv.IsPrint(rune(data)) {
console.WriteByte(data)
}
buf[0] = data
if _, err := f.Write(buf); err != nil {
fmt.Printf("\nerror writing: %s\r\n", err)
return
}
n++
}
}
}
}
func createSampleFile(name string, buf []byte) (int, error) {
for j := uint8(0); j < uint8(len(buf)); j++ {
buf[j] = 0x20 + j
}
f, err := fs.OpenFile(name, os.O_CREATE|os.O_WRONLY|os.O_TRUNC)
if err != nil {
return 0, fmt.Errorf("error opening %s: %s", name, err.Error())
}
defer f.Close()
bytes, err := f.Write(buf)
if err != nil {
return 0, fmt.Errorf("error writing %s: %s", name, err.Error())
}
return bytes, nil
}
/*
func cd(argv []string) {
if fatfs == nil || rootdir == nil {
mnt(nil)
}
if len(argv) == 1 {
currdir = rootdir
return
}
tgt := ""
if len(argv) == 2 {
tgt = strings.TrimSpace(argv[1])
}
if debug {
println("Trying to cd to " + tgt)
}
if tgt == "" {
println("Usage: cd <target dir>")
return
}
if debug {
println("Getting entry")
}
entry := currdir.Entry(tgt)
if entry == nil {
println("File not found: " + tgt)
return
}
if !entry.IsDir() {
println("Not a directory: " + tgt)
return
}
if debug {
println("Getting dir")
}
cd, err := entry.Dir()
if err != nil {
println("Could not cd to " + tgt + ": " + err.Error())
}
currdir = cd
}
*/
func cat(argv []string) {
tgt := ""
if len(argv) == 2 {
tgt = strings.TrimSpace(argv[1])
}
if debug {
println("Trying to cat to " + tgt)
}
if tgt == "" {
println("Usage: cat <target file>")
return
}
if debug {
println("Getting entry")
}
f, err := fs.Open(tgt)
if err != nil {
println("Could not open: " + err.Error())
return
}
defer f.Close()
if f.IsDir() {
println("Not a file: " + tgt)
return
}
off := 0x0
buf := make([]byte, 64)
for {
n, err := f.Read(buf)
if err != nil {
if err == io.EOF {
break
}
println("Error reading " + tgt + ": " + err.Error())
time.Sleep(1 * time.Second)
}
xxdfprint(os.Stdout, uint32(off), buf[:n])
off += n
}
}
func xxd(argv []string) {
var err error
var addr uint64 = 0x0
var size int = 64
switch len(argv) {
case 3:
if size, err = strconv.Atoi(argv[2]); err != nil {
println("Invalid size argument: " + err.Error() + "\r\n")
return
}
if size > 512 || size < 1 {
fmt.Printf("Size of hexdump must be greater than 0 and less than %d\r\n", 512)
return
}
fallthrough
case 2:
/*
if argv[1][:2] != "0x" {
println("Invalid hex address (should start with 0x)")
return
}
*/
if addr, err = strconv.ParseUint(argv[1], 16, 32); err != nil {
println("Invalid address: " + err.Error() + "\r\n")
return
}
fallthrough
case 1:
// no args supplied, so nothing to do here, just use the defaults
default:
println("usage: xxd <hex address, ex: 0xA0> <size of hexdump in bytes>\r\n")
return
}
buf := make([]byte, size)
//bsz := uint64(flash.SectorSize)
//blockdev.ReadBlock(uint32(addr/bsz), uint32(addr%bsz), buf)
flashdev.ReadAt(buf, int64(addr))
xxdfprint(os.Stdout, uint32(addr), buf)
}
func xxdfprint(w io.Writer, offset uint32, b []byte) {
var l int
var buf16 = make([]byte, 16)
for i, c := 0, len(b); i < c; i += 16 {
l = i + 16
if l >= c {
l = c
}
fmt.Fprintf(w, "%08x: % x ", offset+uint32(i), b[i:l])
for j, n := 0, l-i; j < 16; j++ {
if j >= n || !strconv.IsPrint(rune(b[i+j])) || b[i+j] >= 0x80 {
buf16[j] = '.'
} else {
buf16[j] = b[i+j]
}
}
console.Write(buf16)
println()
}
}
func prompt() {
print("==> ")
}
+18
View File
@@ -0,0 +1,18 @@
//go:build feather_m4 || feather_m4_can || feather_nrf52840
// +build feather_m4 feather_m4_can feather_nrf52840
package main
import (
"machine"
)
func init() {
spi = machine.SPI0
sckPin = machine.SPI0_SCK_PIN
sdoPin = machine.SPI0_SDO_PIN
sdiPin = machine.SPI0_SDI_PIN
csPin = machine.D10
ledPin = machine.LED
}
+18
View File
@@ -0,0 +1,18 @@
//go:build grandcentral_m4
// +build grandcentral_m4
package main
import (
"machine"
)
func init() {
spi = machine.SPI1
sckPin = machine.SDCARD_SCK_PIN
sdoPin = machine.SDCARD_SDO_PIN
sdiPin = machine.SDCARD_SDI_PIN
csPin = machine.SDCARD_CS_PIN
ledPin = machine.LED
}
+18
View File
@@ -0,0 +1,18 @@
//go:build atsamd21 && !p1am_100
// +build atsamd21,!p1am_100
package main
import (
"machine"
)
func init() {
spi = machine.SPI0
sckPin = machine.SPI0_SCK_PIN
sdoPin = machine.SPI0_SDO_PIN
sdiPin = machine.SPI0_SDI_PIN
csPin = machine.D2
ledPin = machine.LED
}
+49
View File
@@ -0,0 +1,49 @@
package main
import (
"fmt"
"machine"
"time"
"tinygo.org/x/drivers/examples/sdcard/tinyfs/console"
"tinygo.org/x/drivers/sdcard"
"tinygo.org/x/tinyfs/fatfs"
)
var (
spi machine.SPI
sckPin machine.Pin
sdoPin machine.Pin
sdiPin machine.Pin
csPin machine.Pin
ledPin machine.Pin
)
func main() {
waitSerial()
sd := sdcard.New(spi, sckPin, sdoPin, sdiPin, csPin)
err := sd.Configure()
if err != nil {
fmt.Printf("%s\r\n", err.Error())
for {
time.Sleep(time.Hour)
}
}
filesystem := fatfs.New(&sd)
// Configure FATFS with sector size (must match value in ff.h - use 512)
filesystem.Configure(&fatfs.Config{
SectorSize: 512,
})
console.RunFor(&sd, filesystem)
}
// Wait for user to open serial console
func waitSerial() {
for !machine.Serial.DTR() {
time.Sleep(100 * time.Millisecond)
}
}
+18
View File
@@ -0,0 +1,18 @@
//go:build p1am_100
// +build p1am_100
package main
import (
"machine"
)
func init() {
spi = machine.SDCARD_SPI
sckPin = machine.SDCARD_SCK_PIN
sdoPin = machine.SDCARD_SDO_PIN
sdiPin = machine.SDCARD_SDI_PIN
csPin = machine.SDCARD_SS_PIN
ledPin = machine.LED
}
+18
View File
@@ -0,0 +1,18 @@
//go:build pygamer
// +build pygamer
package main
import (
"machine"
)
func init() {
spi = machine.SPI0
sckPin = machine.SPI0_SCK_PIN
sdoPin = machine.SPI0_SDO_PIN
sdiPin = machine.SPI0_SDI_PIN
csPin = machine.D4
ledPin = machine.LED
}
+18
View File
@@ -0,0 +1,18 @@
//go:build pyportal
// +build pyportal
package main
import (
"machine"
)
func init() {
spi = machine.SPI0
sckPin = machine.SPI0_SCK_PIN
sdoPin = machine.SPI0_SDO_PIN
sdiPin = machine.SPI0_SDI_PIN
csPin = machine.D32 // SD_CS
ledPin = machine.LED
}
+18
View File
@@ -0,0 +1,18 @@
//go:build wioterminal
// +build wioterminal
package main
import (
"machine"
)
func init() {
spi = machine.SPI2
sckPin = machine.SCK2
sdoPin = machine.SDO2
sdiPin = machine.SDI2
csPin = machine.SS2
ledPin = machine.LED
}
+43 -27
View File
@@ -2,9 +2,8 @@
package main
import (
"encoding/binary"
"fmt"
"machine"
"strconv"
"time"
"tinygo.org/x/drivers/wifinina"
@@ -52,15 +51,37 @@ func main() {
connectToAP()
for {
println("---------------------------------")
println("----------------------------------------")
printSSID()
printRSSI()
printMac()
printIPs()
printTime()
printMacAddress()
time.Sleep(10 * time.Second)
}
}
func printSSID() {
print("SSID: ")
ssid, err := adaptor.GetCurrentSSID()
if err != nil {
println("Unknown (error: ", err.Error(), ")")
return
}
println(ssid)
}
func printRSSI() {
print("RSSI: ")
rssi, err := adaptor.GetCurrentRSSI()
if err != nil {
println("Unknown (error: ", err.Error(), ")")
return
}
println(strconv.Itoa(int(rssi)))
}
func printIPs() {
ip, subnet, gateway, err := adaptor.GetIP()
if err != nil {
@@ -69,39 +90,38 @@ func printIPs() {
}
println("IP: ", ip.String())
println("Subnet: ", subnet.String())
println("Gateway IP: ", gateway.String())
println("Gateway: ", gateway.String())
}
func printTime() {
print("Time: ")
t, err := adaptor.GetTime()
if err != nil {
println("Unknown (error: ", err.Error(), ")")
for {
if err != nil {
println("Unknown (error: ", err.Error(), ")")
return
}
if t != 0 {
break
}
time.Sleep(time.Second)
t, err = adaptor.GetTime()
}
println(time.Unix(int64(t), 0).String())
}
func printMacAddress() {
print("MAC Address: ")
b := make([]byte, 8)
func printMac() {
print("MAC: ")
mac, err := adaptor.GetMACAddress()
if err != nil {
println("Unknown (", err.Error(), ")")
}
binary.LittleEndian.PutUint64(b, uint64(mac))
macAddress := ""
for i := 5; i >= 0; i-- {
macAddress += fmt.Sprintf("%0X", b[i])
if i != 0 {
macAddress += ":"
}
}
println(macAddress)
println(mac.String())
}
// Wait for user to open serial console
func waitSerial() {
for !machine.UART0.DTR() {
for !machine.Serial.DTR() {
time.Sleep(100 * time.Millisecond)
}
}
@@ -115,16 +135,12 @@ func connectToAP() {
}
}
time.Sleep(2 * time.Second)
message("Connecting to " + ssid)
println("Connecting to " + ssid)
adaptor.SetPassphrase(ssid, pass)
for st, _ := adaptor.GetConnectionStatus(); st != wifinina.StatusConnected; {
message("Connection status: " + st.String())
println("Connection status: " + st.String())
time.Sleep(1 * time.Second)
st, _ = adaptor.GetConnectionStatus()
}
message("Connected.")
}
func message(msg string) {
println(msg, "\r")
println("Connected.")
}
+161
View File
@@ -0,0 +1,161 @@
// This example opens a TCP connection using a device with WiFiNINA firmware
// and sends a HTTP request to retrieve a webpage, based on the following
// Arduino example:
//
// https://github.com/arduino-libraries/WiFiNINA/blob/master/examples/WiFiWebClientRepeating/
//
// This example will not work with samd21 or other systems with less than 32KB
// of RAM. Use the following if you want to run wifinina on samd21, etc.
//
// examples/wifinina/webclient
// examples/wifinina/tlsclient
//
package main
import (
"bufio"
"fmt"
"machine"
"strings"
"time"
"tinygo.org/x/drivers/net"
"tinygo.org/x/drivers/net/http"
"tinygo.org/x/drivers/wifinina"
)
// access point info
const ssid = ""
const pass = ""
// IP address of the server aka "hub". Replace with your own info.
// Can specify a URL starting with http or https
const url = "http://tinygo.org/"
// these are the default pins for the Arduino Nano33 IoT.
// change these to connect to a different UART or pins for the ESP8266/ESP32
var (
// these are the default pins for the Arduino Nano33 IoT.
spi = machine.NINA_SPI
// this is the ESP chip that has the WIFININA firmware flashed on it
adaptor *wifinina.Device
)
var buf [0x400]byte
var lastRequestTime time.Time
var conn net.Conn
func setup() {
// Configure SPI for 8Mhz, Mode 0, MSB First
spi.Configure(machine.SPIConfig{
Frequency: 8 * 1e6,
SDO: machine.NINA_SDO,
SDI: machine.NINA_SDI,
SCK: machine.NINA_SCK,
})
adaptor = wifinina.New(spi,
machine.NINA_CS,
machine.NINA_ACK,
machine.NINA_GPIO0,
machine.NINA_RESETN)
adaptor.Configure()
}
func main() {
setup()
http.SetBuf(buf[:])
waitSerial()
connectToAP()
// You can send and receive cookies in the following way
// import "tinygo.org/x/drivers/net/http/cookiejar"
// jar, err := cookiejar.New(nil)
// if err != nil {
// return err
// }
// client := &http.Client{Jar: jar}
// http.DefaultClient = client
cnt := 0
for {
// Various examples are as follows
//
// -- Get
// resp, err := http.Get(url)
//
// -- Post
// body := `cnt=12`
// resp, err = http.Post(url, "application/x-www-form-urlencoded", strings.NewReader(body))
//
// -- Post with JSON
// body := `{"msg": "hello"}`
// resp, err := http.Post(url, "application/json", strings.NewReader(body))
resp, err := http.Get(url)
if err != nil {
fmt.Printf("%s\r\n", err.Error())
continue
}
fmt.Printf("%s %s\r\n", resp.Proto, resp.Status)
for k, v := range resp.Header {
fmt.Printf("%s: %s\r\n", k, strings.Join(v, " "))
}
fmt.Printf("\r\n")
scanner := bufio.NewScanner(resp.Body)
for scanner.Scan() {
fmt.Printf("%s\r\n", scanner.Text())
}
resp.Body.Close()
cnt++
fmt.Printf("-------- %d --------\r\n", cnt)
time.Sleep(10 * time.Second)
}
}
// Wait for user to open serial console
func waitSerial() {
for !machine.Serial.DTR() {
time.Sleep(100 * time.Millisecond)
}
}
// connect to access point
func connectToAP() {
if len(ssid) == 0 || len(pass) == 0 {
for {
println("Connection failed: Either ssid or password not set")
time.Sleep(10 * time.Second)
}
}
time.Sleep(2 * time.Second)
message("Connecting to " + ssid)
adaptor.SetPassphrase(ssid, pass)
for st, _ := adaptor.GetConnectionStatus(); st != wifinina.StatusConnected; {
message("Connection status: " + st.String())
time.Sleep(1 * time.Second)
st, _ = adaptor.GetConnectionStatus()
}
message("Connected.")
time.Sleep(2 * time.Second)
ip, _, _, err := adaptor.GetIP()
for ; err != nil; ip, _, _, err = adaptor.GetIP() {
message(err.Error())
time.Sleep(1 * time.Second)
}
message(ip.String())
}
func message(msg string) {
println(msg, "\r")
}
+1 -1
View File
@@ -92,7 +92,7 @@ func main() {
// Wait for user to open serial console
func waitSerial() {
for !machine.UART0.DTR() {
for !machine.Serial.DTR() {
time.Sleep(100 * time.Millisecond)
}
}
+1 -1
View File
@@ -77,7 +77,7 @@ func main() {
// Wait for user to open serial console
func waitSerial() {
for !machine.UART0.DTR() {
for !machine.Serial.DTR() {
time.Sleep(100 * time.Millisecond)
}
}
+1 -1
View File
@@ -74,7 +74,7 @@ func main() {
// Wait for user to open serial console
func waitSerial() {
for !machine.UART0.DTR() {
for !machine.Serial.DTR() {
time.Sleep(100 * time.Millisecond)
}
}
+3 -1
View File
@@ -1,9 +1,11 @@
//go:build arduino
// +build arduino
package main
import "machine"
// Replace neo in the code below to match the pin
// 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
+3 -1
View File
@@ -1,3 +1,4 @@
//go:build digispark
// +build digispark
package main
@@ -5,6 +6,7 @@ package main
import "machine"
// This is the pin assignment for the Digispark only.
// Replace neo in the code below to match the pin
// 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
-1
View File
@@ -15,7 +15,6 @@ import (
var leds [10]color.RGBA
func main() {
led := machine.LED
led.Configure(machine.PinConfig{Mode: machine.PinOutput})
neo.Configure(machine.PinConfig{Mode: machine.PinOutput})
+5 -3
View File
@@ -1,9 +1,11 @@
// +build !digispark,!arduino
//go:build !digispark && !arduino && !qtpy
// +build !digispark,!arduino,!qtpy
package main
import "machine"
// Replace neo in the code below to match the pin
// Replace neo and led in the code below to match the pin
// that you are using if different.
var neo machine.Pin = machine.NEOPIXELS
var neo machine.Pin = machine.WS2812
var led = machine.LED
+17
View File
@@ -0,0 +1,17 @@
//go:build qtpy
// +build qtpy
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.NEOPIXELS
var led = machine.NoPin
func init() {
pwr := machine.NEOPIXELS_POWER
pwr.Configure(machine.PinConfig{Mode: machine.PinOutput})
pwr.High()
}
+1
View File
@@ -1,3 +1,4 @@
//go:build atsamd51
// +build atsamd51
package flash
+4
View File
@@ -5,4 +5,8 @@ go 1.15
require (
github.com/eclipse/paho.mqtt.golang v1.2.0
github.com/frankban/quicktest v1.10.2
golang.org/x/net v0.0.0-20210614182718-04defd469f4e // indirect
tinygo.org/x/tinyfont v0.2.1
tinygo.org/x/tinyfs v0.1.0
tinygo.org/x/tinyterm v0.1.0
)
+19
View File
@@ -1,3 +1,4 @@
github.com/bgould/http v0.0.0-20190627042742-d268792bdee7/go.mod h1:BTqvVegvwifopl4KTEDth6Zezs9eR+lCWhvGKvkxJHE=
github.com/eclipse/paho.mqtt.golang v1.2.0 h1:1F8mhG9+aO5/xpdtFkW4SxOJB67ukuDC3t2y2qayIX0=
github.com/eclipse/paho.mqtt.golang v1.2.0/go.mod h1:H9keYFcgq3Qr5OUJm/JZI/i6U7joQ8SYLhZwfeOo6Ts=
github.com/frankban/quicktest v1.10.2 h1:19ARM85nVi4xH7xPXuc5eM/udya5ieh7b/Sv+d844Tk=
@@ -9,5 +10,23 @@ github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfn
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/valyala/fastjson v1.6.3/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY=
golang.org/x/net v0.0.0-20210614182718-04defd469f4e h1:XpT3nA5TvE525Ne3hInMh6+GETgn27Zfm9dxsThnX2Q=
golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
tinygo.org/x/drivers v0.14.0/go.mod h1:uT2svMq3EpBZpKkGO+NQHjxjGf1f42ra4OnMMwQL2aI=
tinygo.org/x/drivers v0.15.1/go.mod h1:uT2svMq3EpBZpKkGO+NQHjxjGf1f42ra4OnMMwQL2aI=
tinygo.org/x/drivers v0.16.0/go.mod h1:uT2svMq3EpBZpKkGO+NQHjxjGf1f42ra4OnMMwQL2aI=
tinygo.org/x/tinyfont v0.2.1 h1:FAaemBzw8wsfhAtG6fWW+QjyWw/K8YqEeiWo4N1pv4o=
tinygo.org/x/tinyfont v0.2.1/go.mod h1:eLqnYSrFRjt5STxWaMeOWJTzrKhXqpWw7nU3bPfKOAM=
tinygo.org/x/tinyfs v0.1.0 h1:yx1Tq9L60rpCm6HURo45x+Tnag+O9RGSbQfgeCb6XYU=
tinygo.org/x/tinyfs v0.1.0/go.mod h1:ysc8Y92iHfhTXeyEM9+c7zviUQ4fN9UCFgSOFfMWv20=
tinygo.org/x/tinyterm v0.1.0 h1:80i+j+KWoxCFa/Xfp6pWbh79x+8zUdMXC1vaKj2QhkY=
tinygo.org/x/tinyterm v0.1.0/go.mod h1:/DDhNnGwNF2/tNgHywvyZuCGnbH3ov49Z/6e8LPLRR4=
+17 -1
View File
@@ -160,6 +160,20 @@ func (d *Device) DrawRGBBitmap(x, y int16, data []uint16, w, h int16) error {
return nil
}
// DrawRGBBitmap8 copies an RGB bitmap to the internal buffer at given coordinates
func (d *Device) DrawRGBBitmap8(x, y int16, data []uint8, w, h int16) error {
k, i := d.Size()
if x < 0 || y < 0 || w <= 0 || h <= 0 ||
x >= k || (x+w) > k || y >= i || (y+h) > i {
return errors.New("rectangle coordinates outside display area")
}
d.setWindow(x, y, w, h)
d.startWrite()
d.driver.write8sl(data)
d.endWrite()
return nil
}
// FillRectangle fills a rectangle at given coordinates with a color
func (d *Device) FillRectangle(x, y, width, height int16, c color.RGBA) error {
k, i := d.Size()
@@ -300,7 +314,9 @@ func (d *Device) sendCommand(cmd byte, data []byte) {
d.dc.Low()
d.driver.write8(cmd)
d.dc.High()
d.driver.write8sl(data)
if data != nil {
d.driver.write8sl(data)
}
d.endWrite()
}
+1
View File
@@ -1,3 +1,4 @@
//go:build atsamd51
// +build atsamd51
package ili9341
+74
View File
@@ -0,0 +1,74 @@
//go:build !atsamd51 && !atsamd21
// +build !atsamd51,!atsamd21
package ili9341
import (
"machine"
"tinygo.org/x/drivers"
)
var buf [64]byte
type spiDriver struct {
bus drivers.SPI
}
func NewSPI(bus drivers.SPI, dc, cs, rst machine.Pin) *Device {
return &Device{
dc: dc,
cs: cs,
rst: rst,
rd: machine.NoPin,
driver: &spiDriver{
bus: bus,
},
}
}
func (pd *spiDriver) configure(config *Config) {
}
func (pd *spiDriver) write8(b byte) {
buf[0] = b
pd.bus.Tx(buf[:1], nil)
}
func (pd *spiDriver) write8n(b byte, n int) {
buf[0] = b
for i := 0; i < n; i++ {
pd.bus.Tx(buf[:1], nil)
}
}
func (pd *spiDriver) write8sl(b []byte) {
pd.bus.Tx(b, nil)
}
func (pd *spiDriver) write16(data uint16) {
buf[0] = uint8(data >> 8)
buf[1] = uint8(data)
pd.bus.Tx(buf[:2], nil)
}
func (pd *spiDriver) write16n(data uint16, n int) {
for i := 0; i < len(buf); i += 2 {
buf[i] = uint8(data >> 8)
buf[i+1] = uint8(data)
}
for i := 0; i < (n >> 5); i++ {
pd.bus.Tx(buf[:], nil)
}
pd.bus.Tx(buf[:n%64], nil)
}
func (pd *spiDriver) write16sl(data []uint16) {
for i, c := 0, len(data); i < c; i++ {
buf[0] = uint8(data[i] >> 8)
buf[1] = uint8(data[i])
pd.bus.Tx(buf[:2], nil)
}
}
+1
View File
@@ -1,3 +1,4 @@
//go:build atsamd21
// +build atsamd21
package ili9341
+1
View File
@@ -1,3 +1,4 @@
//go:build atsamd51
// +build atsamd51
package ili9341
+60
View File
@@ -0,0 +1,60 @@
# tinygo.org/x/drivers/image
This is an image package that uses less RAM to run on a microcontroller.
Unlike Go's original image package, `image.Decode()` does not return `image.Image`.
Instead, a callback can be set to process the data corresponding to the image.
## How to use
First, use `SetCallback()` to set the callback.
Then call `png.Decode()` or `jpeg.Decode()`.
The callback will be called as many times as necessary to load the image.
`SetCallback()` needs to be given a Buffer to handle the callback and the actual function to be called.
The `data []uint16` in the callback is in RGB565 format.
The `io.Reader` to pass to `Decode()` specifies the binary data of the image.
```go
func drawPng(display *ili9341.Device) error {
p := strings.NewReader(pngImage)
png.SetCallback(buffer[:], func(data []uint16, x, y, w, h, width, height int16) {
err := display.DrawRGBBitmap(x, y, data[:w*h], w, h)
if err != nil {
errorMessage(fmt.Errorf("error drawPng: %s", err))
}
})
return png.Decode(p)
}
```
```go
func drawJpeg(display *ili9341.Device) error {
p := strings.NewReader(jpegImage)
jpeg.SetCallback(buffer[:], func(data []uint16, x, y, w, h, width, height int16) {
err := display.DrawRGBBitmap(x, y, data[:w*h], w, h)
if err != nil {
errorMessage(fmt.Errorf("error drawJpeg: %s", err))
}
})
return jpeg.Decode(p)
}
```
## How to create an image
The following program will output an image binary like the one in [images.go](./examples/ili9341/slideshow/images.go).
```
go run ./cmd/convert2bin ./path/to/png_or_jpg.png
```
## Examples
An example can be found below.
Processing jpegs requires a minimum of 32KB of RAM.
* [./examples/ili9341/slideshow](./examples/ili9341/slideshow)
+748
View File
@@ -0,0 +1,748 @@
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package flate
import (
"fmt"
"io"
"math"
)
const (
NoCompression = 0
BestSpeed = 1
BestCompression = 9
DefaultCompression = -1
// HuffmanOnly disables Lempel-Ziv match searching and only performs Huffman
// entropy encoding. This mode is useful in compressing data that has
// already been compressed with an LZ style algorithm (e.g. Snappy or LZ4)
// that lacks an entropy encoder. Compression gains are achieved when
// certain bytes in the input stream occur more frequently than others.
//
// Note that HuffmanOnly produces a compressed output that is
// RFC 1951 compliant. That is, any valid DEFLATE decompressor will
// continue to be able to decompress this output.
HuffmanOnly = -2
)
const (
logWindowSize = 15
windowSize = 1 << logWindowSize
windowMask = windowSize - 1
// The LZ77 step produces a sequence of literal tokens and <length, offset>
// pair tokens. The offset is also known as distance. The underlying wire
// format limits the range of lengths and offsets. For example, there are
// 256 legitimate lengths: those in the range [3, 258]. This package's
// compressor uses a higher minimum match length, enabling optimizations
// such as finding matches via 32-bit loads and compares.
baseMatchLength = 3 // The smallest match length per the RFC section 3.2.5
minMatchLength = 4 // The smallest match length that the compressor actually emits
maxMatchLength = 258 // The largest match length
baseMatchOffset = 1 // The smallest match offset
maxMatchOffset = 1 << 15 // The largest match offset
// The maximum number of tokens we put into a single flate block, just to
// stop things from getting too large.
maxFlateBlockTokens = 1 << 14
maxStoreBlockSize = 65535
hashBits = 17 // After 17 performance degrades
hashSize = 1 << hashBits
hashMask = (1 << hashBits) - 1
maxHashOffset = 1 << 24
skipNever = math.MaxInt32
)
type compressionLevel struct {
level, good, lazy, nice, chain, fastSkipHashing int
}
var levels = []compressionLevel{
{0, 0, 0, 0, 0, 0}, // NoCompression.
{1, 0, 0, 0, 0, 0}, // BestSpeed uses a custom algorithm; see deflatefast.go.
// For levels 2-3 we don't bother trying with lazy matches.
{2, 4, 0, 16, 8, 5},
{3, 4, 0, 32, 32, 6},
// Levels 4-9 use increasingly more lazy matching
// and increasingly stringent conditions for "good enough".
{4, 4, 4, 16, 16, skipNever},
{5, 8, 16, 32, 32, skipNever},
{6, 8, 16, 128, 128, skipNever},
{7, 8, 32, 128, 256, skipNever},
{8, 32, 128, 258, 1024, skipNever},
{9, 32, 258, 258, 4096, skipNever},
}
type compressor struct {
compressionLevel
w *huffmanBitWriter
bulkHasher func([]byte, []uint32)
// compression algorithm
fill func(*compressor, []byte) int // copy data to window
step func(*compressor) // process window
sync bool // requesting flush
bestSpeed *deflateFast // Encoder for BestSpeed
// Input hash chains
// hashHead[hashValue] contains the largest inputIndex with the specified hash value
// If hashHead[hashValue] is within the current window, then
// hashPrev[hashHead[hashValue] & windowMask] contains the previous index
// with the same hash value.
chainHead int
hashHead [hashSize]uint32
hashPrev [windowSize]uint32
hashOffset int
// input window: unprocessed data is window[index:windowEnd]
index int
window []byte
windowEnd int
blockStart int // window index where current tokens start
byteAvailable bool // if true, still need to process window[index-1].
// queued output tokens
tokens []token
// deflate state
length int
offset int
hash uint32
maxInsertIndex int
err error
// hashMatch must be able to contain hashes for the maximum match length.
hashMatch [maxMatchLength - 1]uint32
}
func (d *compressor) fillDeflate(b []byte) int {
if d.index >= 2*windowSize-(minMatchLength+maxMatchLength) {
// shift the window by windowSize
copy(d.window, d.window[windowSize:2*windowSize])
d.index -= windowSize
d.windowEnd -= windowSize
if d.blockStart >= windowSize {
d.blockStart -= windowSize
} else {
d.blockStart = math.MaxInt32
}
d.hashOffset += windowSize
if d.hashOffset > maxHashOffset {
delta := d.hashOffset - 1
d.hashOffset -= delta
d.chainHead -= delta
// Iterate over slices instead of arrays to avoid copying
// the entire table onto the stack (Issue #18625).
for i, v := range d.hashPrev[:] {
if int(v) > delta {
d.hashPrev[i] = uint32(int(v) - delta)
} else {
d.hashPrev[i] = 0
}
}
for i, v := range d.hashHead[:] {
if int(v) > delta {
d.hashHead[i] = uint32(int(v) - delta)
} else {
d.hashHead[i] = 0
}
}
}
}
n := copy(d.window[d.windowEnd:], b)
d.windowEnd += n
return n
}
func (d *compressor) writeBlock(tokens []token, index int) error {
if index > 0 {
var window []byte
if d.blockStart <= index {
window = d.window[d.blockStart:index]
}
d.blockStart = index
d.w.writeBlock(tokens, false, window)
return d.w.err
}
return nil
}
// fillWindow will fill the current window with the supplied
// dictionary and calculate all hashes.
// This is much faster than doing a full encode.
// Should only be used after a reset.
func (d *compressor) fillWindow(b []byte) {
// Do not fill window if we are in store-only mode.
if d.compressionLevel.level < 2 {
return
}
if d.index != 0 || d.windowEnd != 0 {
panic("internal error: fillWindow called with stale data")
}
// If we are given too much, cut it.
if len(b) > windowSize {
b = b[len(b)-windowSize:]
}
// Add all to window.
n := copy(d.window, b)
// Calculate 256 hashes at the time (more L1 cache hits)
loops := (n + 256 - minMatchLength) / 256
for j := 0; j < loops; j++ {
index := j * 256
end := index + 256 + minMatchLength - 1
if end > n {
end = n
}
toCheck := d.window[index:end]
dstSize := len(toCheck) - minMatchLength + 1
if dstSize <= 0 {
continue
}
dst := d.hashMatch[:dstSize]
d.bulkHasher(toCheck, dst)
var newH uint32
for i, val := range dst {
di := i + index
newH = val
hh := &d.hashHead[newH&hashMask]
// Get previous value with the same hash.
// Our chain should point to the previous value.
d.hashPrev[di&windowMask] = *hh
// Set the head of the hash chain to us.
*hh = uint32(di + d.hashOffset)
}
d.hash = newH
}
// Update window information.
d.windowEnd = n
d.index = n
}
// Try to find a match starting at index whose length is greater than prevSize.
// We only look at chainCount possibilities before giving up.
func (d *compressor) findMatch(pos int, prevHead int, prevLength int, lookahead int) (length, offset int, ok bool) {
minMatchLook := maxMatchLength
if lookahead < minMatchLook {
minMatchLook = lookahead
}
win := d.window[0 : pos+minMatchLook]
// We quit when we get a match that's at least nice long
nice := len(win) - pos
if d.nice < nice {
nice = d.nice
}
// If we've got a match that's good enough, only look in 1/4 the chain.
tries := d.chain
length = prevLength
if length >= d.good {
tries >>= 2
}
wEnd := win[pos+length]
wPos := win[pos:]
minIndex := pos - windowSize
for i := prevHead; tries > 0; tries-- {
if wEnd == win[i+length] {
n := matchLen(win[i:], wPos, minMatchLook)
if n > length && (n > minMatchLength || pos-i <= 4096) {
length = n
offset = pos - i
ok = true
if n >= nice {
// The match is good enough that we don't try to find a better one.
break
}
wEnd = win[pos+n]
}
}
if i == minIndex {
// hashPrev[i & windowMask] has already been overwritten, so stop now.
break
}
i = int(d.hashPrev[i&windowMask]) - d.hashOffset
if i < minIndex || i < 0 {
break
}
}
return
}
func (d *compressor) writeStoredBlock(buf []byte) error {
if d.w.writeStoredHeader(len(buf), false); d.w.err != nil {
return d.w.err
}
d.w.writeBytes(buf)
return d.w.err
}
const hashmul = 0x1e35a7bd
// hash4 returns a hash representation of the first 4 bytes
// of the supplied slice.
// The caller must ensure that len(b) >= 4.
func hash4(b []byte) uint32 {
return ((uint32(b[3]) | uint32(b[2])<<8 | uint32(b[1])<<16 | uint32(b[0])<<24) * hashmul) >> (32 - hashBits)
}
// bulkHash4 will compute hashes using the same
// algorithm as hash4
func bulkHash4(b []byte, dst []uint32) {
if len(b) < minMatchLength {
return
}
hb := uint32(b[3]) | uint32(b[2])<<8 | uint32(b[1])<<16 | uint32(b[0])<<24
dst[0] = (hb * hashmul) >> (32 - hashBits)
end := len(b) - minMatchLength + 1
for i := 1; i < end; i++ {
hb = (hb << 8) | uint32(b[i+3])
dst[i] = (hb * hashmul) >> (32 - hashBits)
}
}
// matchLen returns the number of matching bytes in a and b
// up to length 'max'. Both slices must be at least 'max'
// bytes in size.
func matchLen(a, b []byte, max int) int {
a = a[:max]
b = b[:len(a)]
for i, av := range a {
if b[i] != av {
return i
}
}
return max
}
// encSpeed will compress and store the currently added data,
// if enough has been accumulated or we at the end of the stream.
// Any error that occurred will be in d.err
func (d *compressor) encSpeed() {
// We only compress if we have maxStoreBlockSize.
if d.windowEnd < maxStoreBlockSize {
if !d.sync {
return
}
// Handle small sizes.
if d.windowEnd < 128 {
switch {
case d.windowEnd == 0:
return
case d.windowEnd <= 16:
d.err = d.writeStoredBlock(d.window[:d.windowEnd])
default:
d.w.writeBlockHuff(false, d.window[:d.windowEnd])
d.err = d.w.err
}
d.windowEnd = 0
d.bestSpeed.reset()
return
}
}
// Encode the block.
d.tokens = d.bestSpeed.encode(d.tokens[:0], d.window[:d.windowEnd])
// If we removed less than 1/16th, Huffman compress the block.
if len(d.tokens) > d.windowEnd-(d.windowEnd>>4) {
d.w.writeBlockHuff(false, d.window[:d.windowEnd])
} else {
d.w.writeBlockDynamic(d.tokens, false, d.window[:d.windowEnd])
}
d.err = d.w.err
d.windowEnd = 0
}
func (d *compressor) initDeflate() {
d.window = make([]byte, 2*windowSize)
d.hashOffset = 1
d.tokens = make([]token, 0, maxFlateBlockTokens+1)
d.length = minMatchLength - 1
d.offset = 0
d.byteAvailable = false
d.index = 0
d.hash = 0
d.chainHead = -1
d.bulkHasher = bulkHash4
}
func (d *compressor) deflate() {
if d.windowEnd-d.index < minMatchLength+maxMatchLength && !d.sync {
return
}
d.maxInsertIndex = d.windowEnd - (minMatchLength - 1)
if d.index < d.maxInsertIndex {
d.hash = hash4(d.window[d.index : d.index+minMatchLength])
}
Loop:
for {
if d.index > d.windowEnd {
panic("index > windowEnd")
}
lookahead := d.windowEnd - d.index
if lookahead < minMatchLength+maxMatchLength {
if !d.sync {
break Loop
}
if d.index > d.windowEnd {
panic("index > windowEnd")
}
if lookahead == 0 {
// Flush current output block if any.
if d.byteAvailable {
// There is still one pending token that needs to be flushed
d.tokens = append(d.tokens, literalToken(uint32(d.window[d.index-1])))
d.byteAvailable = false
}
if len(d.tokens) > 0 {
if d.err = d.writeBlock(d.tokens, d.index); d.err != nil {
return
}
d.tokens = d.tokens[:0]
}
break Loop
}
}
if d.index < d.maxInsertIndex {
// Update the hash
d.hash = hash4(d.window[d.index : d.index+minMatchLength])
hh := &d.hashHead[d.hash&hashMask]
d.chainHead = int(*hh)
d.hashPrev[d.index&windowMask] = uint32(d.chainHead)
*hh = uint32(d.index + d.hashOffset)
}
prevLength := d.length
prevOffset := d.offset
d.length = minMatchLength - 1
d.offset = 0
minIndex := d.index - windowSize
if minIndex < 0 {
minIndex = 0
}
if d.chainHead-d.hashOffset >= minIndex &&
(d.fastSkipHashing != skipNever && lookahead > minMatchLength-1 ||
d.fastSkipHashing == skipNever && lookahead > prevLength && prevLength < d.lazy) {
if newLength, newOffset, ok := d.findMatch(d.index, d.chainHead-d.hashOffset, minMatchLength-1, lookahead); ok {
d.length = newLength
d.offset = newOffset
}
}
if d.fastSkipHashing != skipNever && d.length >= minMatchLength ||
d.fastSkipHashing == skipNever && prevLength >= minMatchLength && d.length <= prevLength {
// There was a match at the previous step, and the current match is
// not better. Output the previous match.
if d.fastSkipHashing != skipNever {
d.tokens = append(d.tokens, matchToken(uint32(d.length-baseMatchLength), uint32(d.offset-baseMatchOffset)))
} else {
d.tokens = append(d.tokens, matchToken(uint32(prevLength-baseMatchLength), uint32(prevOffset-baseMatchOffset)))
}
// Insert in the hash table all strings up to the end of the match.
// index and index-1 are already inserted. If there is not enough
// lookahead, the last two strings are not inserted into the hash
// table.
if d.length <= d.fastSkipHashing {
var newIndex int
if d.fastSkipHashing != skipNever {
newIndex = d.index + d.length
} else {
newIndex = d.index + prevLength - 1
}
index := d.index
for index++; index < newIndex; index++ {
if index < d.maxInsertIndex {
d.hash = hash4(d.window[index : index+minMatchLength])
// Get previous value with the same hash.
// Our chain should point to the previous value.
hh := &d.hashHead[d.hash&hashMask]
d.hashPrev[index&windowMask] = *hh
// Set the head of the hash chain to us.
*hh = uint32(index + d.hashOffset)
}
}
d.index = index
if d.fastSkipHashing == skipNever {
d.byteAvailable = false
d.length = minMatchLength - 1
}
} else {
// For matches this long, we don't bother inserting each individual
// item into the table.
d.index += d.length
if d.index < d.maxInsertIndex {
d.hash = hash4(d.window[d.index : d.index+minMatchLength])
}
}
if len(d.tokens) == maxFlateBlockTokens {
// The block includes the current character
if d.err = d.writeBlock(d.tokens, d.index); d.err != nil {
return
}
d.tokens = d.tokens[:0]
}
} else {
if d.fastSkipHashing != skipNever || d.byteAvailable {
i := d.index - 1
if d.fastSkipHashing != skipNever {
i = d.index
}
d.tokens = append(d.tokens, literalToken(uint32(d.window[i])))
if len(d.tokens) == maxFlateBlockTokens {
if d.err = d.writeBlock(d.tokens, i+1); d.err != nil {
return
}
d.tokens = d.tokens[:0]
}
}
d.index++
if d.fastSkipHashing == skipNever {
d.byteAvailable = true
}
}
}
}
func (d *compressor) fillStore(b []byte) int {
n := copy(d.window[d.windowEnd:], b)
d.windowEnd += n
return n
}
func (d *compressor) store() {
if d.windowEnd > 0 && (d.windowEnd == maxStoreBlockSize || d.sync) {
d.err = d.writeStoredBlock(d.window[:d.windowEnd])
d.windowEnd = 0
}
}
// storeHuff compresses and stores the currently added data
// when the d.window is full or we are at the end of the stream.
// Any error that occurred will be in d.err
func (d *compressor) storeHuff() {
if d.windowEnd < len(d.window) && !d.sync || d.windowEnd == 0 {
return
}
d.w.writeBlockHuff(false, d.window[:d.windowEnd])
d.err = d.w.err
d.windowEnd = 0
}
func (d *compressor) write(b []byte) (n int, err error) {
if d.err != nil {
return 0, d.err
}
n = len(b)
for len(b) > 0 {
d.step(d)
b = b[d.fill(d, b):]
if d.err != nil {
return 0, d.err
}
}
return n, nil
}
func (d *compressor) syncFlush() error {
if d.err != nil {
return d.err
}
d.sync = true
d.step(d)
if d.err == nil {
d.w.writeStoredHeader(0, false)
d.w.flush()
d.err = d.w.err
}
d.sync = false
return d.err
}
func (d *compressor) init(w io.Writer, level int) (err error) {
d.w = newHuffmanBitWriter(w)
switch {
case level == NoCompression:
d.window = make([]byte, maxStoreBlockSize)
d.fill = (*compressor).fillStore
d.step = (*compressor).store
case level == HuffmanOnly:
d.window = make([]byte, maxStoreBlockSize)
d.fill = (*compressor).fillStore
d.step = (*compressor).storeHuff
case level == BestSpeed:
d.compressionLevel = levels[level]
d.window = make([]byte, maxStoreBlockSize)
d.fill = (*compressor).fillStore
d.step = (*compressor).encSpeed
d.bestSpeed = newDeflateFast()
d.tokens = make([]token, maxStoreBlockSize)
case level == DefaultCompression:
level = 6
fallthrough
case 2 <= level && level <= 9:
d.compressionLevel = levels[level]
d.initDeflate()
d.fill = (*compressor).fillDeflate
d.step = (*compressor).deflate
default:
return fmt.Errorf("flate: invalid compression level %d: want value in range [-2, 9]", level)
}
return nil
}
func (d *compressor) reset(w io.Writer) {
d.w.reset(w)
d.sync = false
d.err = nil
switch d.compressionLevel.level {
case NoCompression:
d.windowEnd = 0
case BestSpeed:
d.windowEnd = 0
d.tokens = d.tokens[:0]
d.bestSpeed.reset()
default:
d.chainHead = -1
for i := range d.hashHead {
d.hashHead[i] = 0
}
for i := range d.hashPrev {
d.hashPrev[i] = 0
}
d.hashOffset = 1
d.index, d.windowEnd = 0, 0
d.blockStart, d.byteAvailable = 0, false
d.tokens = d.tokens[:0]
d.length = minMatchLength - 1
d.offset = 0
d.hash = 0
d.maxInsertIndex = 0
}
}
func (d *compressor) close() error {
if d.err != nil {
return d.err
}
d.sync = true
d.step(d)
if d.err != nil {
return d.err
}
if d.w.writeStoredHeader(0, true); d.w.err != nil {
return d.w.err
}
d.w.flush()
return d.w.err
}
// NewWriter returns a new Writer compressing data at the given level.
// Following zlib, levels range from 1 (BestSpeed) to 9 (BestCompression);
// higher levels typically run slower but compress more. Level 0
// (NoCompression) does not attempt any compression; it only adds the
// necessary DEFLATE framing.
// Level -1 (DefaultCompression) uses the default compression level.
// Level -2 (HuffmanOnly) will use Huffman compression only, giving
// a very fast compression for all types of input, but sacrificing considerable
// compression efficiency.
//
// If level is in the range [-2, 9] then the error returned will be nil.
// Otherwise the error returned will be non-nil.
func NewWriter(w io.Writer, level int) (*Writer, error) {
var dw Writer
if err := dw.d.init(w, level); err != nil {
return nil, err
}
return &dw, nil
}
// NewWriterDict is like NewWriter but initializes the new
// Writer with a preset dictionary. The returned Writer behaves
// as if the dictionary had been written to it without producing
// any compressed output. The compressed data written to w
// can only be decompressed by a Reader initialized with the
// same dictionary.
func NewWriterDict(w io.Writer, level int, dict []byte) (*Writer, error) {
dw := &dictWriter{w}
zw, err := NewWriter(dw, level)
if err != nil {
return nil, err
}
zw.d.fillWindow(dict)
zw.dict = append(zw.dict, dict...) // duplicate dictionary for Reset method.
return zw, err
}
type dictWriter struct {
w io.Writer
}
func (w *dictWriter) Write(b []byte) (n int, err error) {
return w.w.Write(b)
}
// A Writer takes data written to it and writes the compressed
// form of that data to an underlying writer (see NewWriter).
type Writer struct {
d compressor
dict []byte
}
// Write writes data to w, which will eventually write the
// compressed form of data to its underlying writer.
func (w *Writer) Write(data []byte) (n int, err error) {
return w.d.write(data)
}
// Flush flushes any pending data to the underlying writer.
// It is useful mainly in compressed network protocols, to ensure that
// a remote reader has enough data to reconstruct a packet.
// Flush does not return until the data has been written.
// Calling Flush when there is no pending data still causes the Writer
// to emit a sync marker of at least 4 bytes.
// If the underlying writer returns an error, Flush returns that error.
//
// In the terminology of the zlib library, Flush is equivalent to Z_SYNC_FLUSH.
func (w *Writer) Flush() error {
// For more about flushing:
// https://www.bolet.org/~pornin/deflate-flush.html
return w.d.syncFlush()
}
// Close flushes and closes the writer.
func (w *Writer) Close() error {
return w.d.close()
}
// Reset discards the writer's state and makes it equivalent to
// the result of NewWriter or NewWriterDict called with dst
// and w's level and dictionary.
func (w *Writer) Reset(dst io.Writer) {
if dw, ok := w.d.w.writer.(*dictWriter); ok {
// w was created with NewWriterDict
dw.w = dst
w.d.reset(dw)
w.d.fillWindow(w.dict)
} else {
// w was created with NewWriter
w.d.reset(dst)
}
}
@@ -0,0 +1,984 @@
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package flate
import (
"bytes"
"errors"
"fmt"
"internal/testenv"
"io"
"math/rand"
"os"
"reflect"
"runtime/debug"
"sync"
"testing"
)
type deflateTest struct {
in []byte
level int
out []byte
}
type deflateInflateTest struct {
in []byte
}
type reverseBitsTest struct {
in uint16
bitCount uint8
out uint16
}
var deflateTests = []*deflateTest{
{[]byte{}, 0, []byte{1, 0, 0, 255, 255}},
{[]byte{0x11}, -1, []byte{18, 4, 4, 0, 0, 255, 255}},
{[]byte{0x11}, DefaultCompression, []byte{18, 4, 4, 0, 0, 255, 255}},
{[]byte{0x11}, 4, []byte{18, 4, 4, 0, 0, 255, 255}},
{[]byte{0x11}, 0, []byte{0, 1, 0, 254, 255, 17, 1, 0, 0, 255, 255}},
{[]byte{0x11, 0x12}, 0, []byte{0, 2, 0, 253, 255, 17, 18, 1, 0, 0, 255, 255}},
{[]byte{0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11}, 0,
[]byte{0, 8, 0, 247, 255, 17, 17, 17, 17, 17, 17, 17, 17, 1, 0, 0, 255, 255},
},
{[]byte{}, 2, []byte{1, 0, 0, 255, 255}},
{[]byte{0x11}, 2, []byte{18, 4, 4, 0, 0, 255, 255}},
{[]byte{0x11, 0x12}, 2, []byte{18, 20, 2, 4, 0, 0, 255, 255}},
{[]byte{0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11}, 2, []byte{18, 132, 2, 64, 0, 0, 0, 255, 255}},
{[]byte{}, 9, []byte{1, 0, 0, 255, 255}},
{[]byte{0x11}, 9, []byte{18, 4, 4, 0, 0, 255, 255}},
{[]byte{0x11, 0x12}, 9, []byte{18, 20, 2, 4, 0, 0, 255, 255}},
{[]byte{0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11}, 9, []byte{18, 132, 2, 64, 0, 0, 0, 255, 255}},
}
var deflateInflateTests = []*deflateInflateTest{
{[]byte{}},
{[]byte{0x11}},
{[]byte{0x11, 0x12}},
{[]byte{0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11}},
{[]byte{0x11, 0x10, 0x13, 0x41, 0x21, 0x21, 0x41, 0x13, 0x87, 0x78, 0x13}},
{largeDataChunk()},
}
var reverseBitsTests = []*reverseBitsTest{
{1, 1, 1},
{1, 2, 2},
{1, 3, 4},
{1, 4, 8},
{1, 5, 16},
{17, 5, 17},
{257, 9, 257},
{29, 5, 23},
}
func largeDataChunk() []byte {
result := make([]byte, 100000)
for i := range result {
result[i] = byte(i * i & 0xFF)
}
return result
}
func TestBulkHash4(t *testing.T) {
for _, x := range deflateTests {
y := x.out
if len(y) < minMatchLength {
continue
}
y = append(y, y...)
for j := 4; j < len(y); j++ {
y := y[:j]
dst := make([]uint32, len(y)-minMatchLength+1)
for i := range dst {
dst[i] = uint32(i + 100)
}
bulkHash4(y, dst)
for i, got := range dst {
want := hash4(y[i:])
if got != want && got == uint32(i)+100 {
t.Errorf("Len:%d Index:%d, want 0x%08x but not modified", len(y), i, want)
} else if got != want {
t.Errorf("Len:%d Index:%d, got 0x%08x want:0x%08x", len(y), i, got, want)
}
}
}
}
}
func TestDeflate(t *testing.T) {
for _, h := range deflateTests {
var buf bytes.Buffer
w, err := NewWriter(&buf, h.level)
if err != nil {
t.Errorf("NewWriter: %v", err)
continue
}
w.Write(h.in)
w.Close()
if !bytes.Equal(buf.Bytes(), h.out) {
t.Errorf("Deflate(%d, %x) = \n%#v, want \n%#v", h.level, h.in, buf.Bytes(), h.out)
}
}
}
// A sparseReader returns a stream consisting of 0s followed by 1<<16 1s.
// This tests missing hash references in a very large input.
type sparseReader struct {
l int64
cur int64
}
func (r *sparseReader) Read(b []byte) (n int, err error) {
if r.cur >= r.l {
return 0, io.EOF
}
n = len(b)
cur := r.cur + int64(n)
if cur > r.l {
n -= int(cur - r.l)
cur = r.l
}
for i := range b[0:n] {
if r.cur+int64(i) >= r.l-1<<16 {
b[i] = 1
} else {
b[i] = 0
}
}
r.cur = cur
return
}
func TestVeryLongSparseChunk(t *testing.T) {
if testing.Short() {
t.Skip("skipping sparse chunk during short test")
}
w, err := NewWriter(io.Discard, 1)
if err != nil {
t.Errorf("NewWriter: %v", err)
return
}
if _, err = io.Copy(w, &sparseReader{l: 23e8}); err != nil {
t.Errorf("Compress failed: %v", err)
return
}
}
type syncBuffer struct {
buf bytes.Buffer
mu sync.RWMutex
closed bool
ready chan bool
}
func newSyncBuffer() *syncBuffer {
return &syncBuffer{ready: make(chan bool, 1)}
}
func (b *syncBuffer) Read(p []byte) (n int, err error) {
for {
b.mu.RLock()
n, err = b.buf.Read(p)
b.mu.RUnlock()
if n > 0 || b.closed {
return
}
<-b.ready
}
}
func (b *syncBuffer) signal() {
select {
case b.ready <- true:
default:
}
}
func (b *syncBuffer) Write(p []byte) (n int, err error) {
n, err = b.buf.Write(p)
b.signal()
return
}
func (b *syncBuffer) WriteMode() {
b.mu.Lock()
}
func (b *syncBuffer) ReadMode() {
b.mu.Unlock()
b.signal()
}
func (b *syncBuffer) Close() error {
b.closed = true
b.signal()
return nil
}
func testSync(t *testing.T, level int, input []byte, name string) {
if len(input) == 0 {
return
}
t.Logf("--testSync %d, %d, %s", level, len(input), name)
buf := newSyncBuffer()
buf1 := new(bytes.Buffer)
buf.WriteMode()
w, err := NewWriter(io.MultiWriter(buf, buf1), level)
if err != nil {
t.Errorf("NewWriter: %v", err)
return
}
r := NewReader(buf)
// Write half the input and read back.
for i := 0; i < 2; i++ {
var lo, hi int
if i == 0 {
lo, hi = 0, (len(input)+1)/2
} else {
lo, hi = (len(input)+1)/2, len(input)
}
t.Logf("#%d: write %d-%d", i, lo, hi)
if _, err := w.Write(input[lo:hi]); err != nil {
t.Errorf("testSync: write: %v", err)
return
}
if i == 0 {
if err := w.Flush(); err != nil {
t.Errorf("testSync: flush: %v", err)
return
}
} else {
if err := w.Close(); err != nil {
t.Errorf("testSync: close: %v", err)
}
}
buf.ReadMode()
out := make([]byte, hi-lo+1)
m, err := io.ReadAtLeast(r, out, hi-lo)
t.Logf("#%d: read %d", i, m)
if m != hi-lo || err != nil {
t.Errorf("testSync/%d (%d, %d, %s): read %d: %d, %v (%d left)", i, level, len(input), name, hi-lo, m, err, buf.buf.Len())
return
}
if !bytes.Equal(input[lo:hi], out[:hi-lo]) {
t.Errorf("testSync/%d: read wrong bytes: %x vs %x", i, input[lo:hi], out[:hi-lo])
return
}
// This test originally checked that after reading
// the first half of the input, there was nothing left
// in the read buffer (buf.buf.Len() != 0) but that is
// not necessarily the case: the write Flush may emit
// some extra framing bits that are not necessary
// to process to obtain the first half of the uncompressed
// data. The test ran correctly most of the time, because
// the background goroutine had usually read even
// those extra bits by now, but it's not a useful thing to
// check.
buf.WriteMode()
}
buf.ReadMode()
out := make([]byte, 10)
if n, err := r.Read(out); n > 0 || err != io.EOF {
t.Errorf("testSync (%d, %d, %s): final Read: %d, %v (hex: %x)", level, len(input), name, n, err, out[0:n])
}
if buf.buf.Len() != 0 {
t.Errorf("testSync (%d, %d, %s): extra data at end", level, len(input), name)
}
r.Close()
// stream should work for ordinary reader too
r = NewReader(buf1)
out, err = io.ReadAll(r)
if err != nil {
t.Errorf("testSync: read: %s", err)
return
}
r.Close()
if !bytes.Equal(input, out) {
t.Errorf("testSync: decompress(compress(data)) != data: level=%d input=%s", level, name)
}
}
func testToFromWithLevelAndLimit(t *testing.T, level int, input []byte, name string, limit int) {
var buffer bytes.Buffer
w, err := NewWriter(&buffer, level)
if err != nil {
t.Errorf("NewWriter: %v", err)
return
}
w.Write(input)
w.Close()
if limit > 0 && buffer.Len() > limit {
t.Errorf("level: %d, len(compress(data)) = %d > limit = %d", level, buffer.Len(), limit)
return
}
if limit > 0 {
t.Logf("level: %d, size:%.2f%%, %d b\n", level, float64(buffer.Len()*100)/float64(limit), buffer.Len())
}
r := NewReader(&buffer)
out, err := io.ReadAll(r)
if err != nil {
t.Errorf("read: %s", err)
return
}
r.Close()
if !bytes.Equal(input, out) {
t.Errorf("decompress(compress(data)) != data: level=%d input=%s", level, name)
return
}
testSync(t, level, input, name)
}
func testToFromWithLimit(t *testing.T, input []byte, name string, limit [11]int) {
for i := 0; i < 10; i++ {
testToFromWithLevelAndLimit(t, i, input, name, limit[i])
}
// Test HuffmanCompression
testToFromWithLevelAndLimit(t, -2, input, name, limit[10])
}
func TestDeflateInflate(t *testing.T) {
t.Parallel()
for i, h := range deflateInflateTests {
if testing.Short() && len(h.in) > 10000 {
continue
}
testToFromWithLimit(t, h.in, fmt.Sprintf("#%d", i), [11]int{})
}
}
func TestReverseBits(t *testing.T) {
for _, h := range reverseBitsTests {
if v := reverseBits(h.in, h.bitCount); v != h.out {
t.Errorf("reverseBits(%v,%v) = %v, want %v",
h.in, h.bitCount, v, h.out)
}
}
}
type deflateInflateStringTest struct {
filename string
label string
limit [11]int
}
var deflateInflateStringTests = []deflateInflateStringTest{
{
"../testdata/e.txt",
"2.718281828...",
[...]int{100018, 50650, 50960, 51150, 50930, 50790, 50790, 50790, 50790, 50790, 43683},
},
{
"../../testdata/Isaac.Newton-Opticks.txt",
"Isaac.Newton-Opticks",
[...]int{567248, 218338, 198211, 193152, 181100, 175427, 175427, 173597, 173422, 173422, 325240},
},
}
func TestDeflateInflateString(t *testing.T) {
t.Parallel()
if testing.Short() && testenv.Builder() == "" {
t.Skip("skipping in short mode")
}
for _, test := range deflateInflateStringTests {
gold, err := os.ReadFile(test.filename)
if err != nil {
t.Error(err)
}
testToFromWithLimit(t, gold, test.label, test.limit)
if testing.Short() {
break
}
}
}
func TestReaderDict(t *testing.T) {
const (
dict = "hello world"
text = "hello again world"
)
var b bytes.Buffer
w, err := NewWriter(&b, 5)
if err != nil {
t.Fatalf("NewWriter: %v", err)
}
w.Write([]byte(dict))
w.Flush()
b.Reset()
w.Write([]byte(text))
w.Close()
r := NewReaderDict(&b, []byte(dict))
data, err := io.ReadAll(r)
if err != nil {
t.Fatal(err)
}
if string(data) != "hello again world" {
t.Fatalf("read returned %q want %q", string(data), text)
}
}
func TestWriterDict(t *testing.T) {
const (
dict = "hello world"
text = "hello again world"
)
var b bytes.Buffer
w, err := NewWriter(&b, 5)
if err != nil {
t.Fatalf("NewWriter: %v", err)
}
w.Write([]byte(dict))
w.Flush()
b.Reset()
w.Write([]byte(text))
w.Close()
var b1 bytes.Buffer
w, _ = NewWriterDict(&b1, 5, []byte(dict))
w.Write([]byte(text))
w.Close()
if !bytes.Equal(b1.Bytes(), b.Bytes()) {
t.Fatalf("writer wrote %q want %q", b1.Bytes(), b.Bytes())
}
}
// See https://golang.org/issue/2508
func TestRegression2508(t *testing.T) {
if testing.Short() {
t.Logf("test disabled with -short")
return
}
w, err := NewWriter(io.Discard, 1)
if err != nil {
t.Fatalf("NewWriter: %v", err)
}
buf := make([]byte, 1024)
for i := 0; i < 131072; i++ {
if _, err := w.Write(buf); err != nil {
t.Fatalf("writer failed: %v", err)
}
}
w.Close()
}
func TestWriterReset(t *testing.T) {
t.Parallel()
for level := 0; level <= 9; level++ {
if testing.Short() && level > 1 {
break
}
w, err := NewWriter(io.Discard, level)
if err != nil {
t.Fatalf("NewWriter: %v", err)
}
buf := []byte("hello world")
n := 1024
if testing.Short() {
n = 10
}
for i := 0; i < n; i++ {
w.Write(buf)
}
w.Reset(io.Discard)
wref, err := NewWriter(io.Discard, level)
if err != nil {
t.Fatalf("NewWriter: %v", err)
}
// DeepEqual doesn't compare functions.
w.d.fill, wref.d.fill = nil, nil
w.d.step, wref.d.step = nil, nil
w.d.bulkHasher, wref.d.bulkHasher = nil, nil
w.d.bestSpeed, wref.d.bestSpeed = nil, nil
// hashMatch is always overwritten when used.
copy(w.d.hashMatch[:], wref.d.hashMatch[:])
if len(w.d.tokens) != 0 {
t.Errorf("level %d Writer not reset after Reset. %d tokens were present", level, len(w.d.tokens))
}
// As long as the length is 0, we don't care about the content.
w.d.tokens = wref.d.tokens
// We don't care if there are values in the window, as long as it is at d.index is 0
w.d.window = wref.d.window
if !reflect.DeepEqual(w, wref) {
t.Errorf("level %d Writer not reset after Reset", level)
}
}
levels := []int{0, 1, 2, 5, 9}
for _, level := range levels {
t.Run(fmt.Sprint(level), func(t *testing.T) {
testResetOutput(t, level, nil)
})
}
t.Run("dict", func(t *testing.T) {
for _, level := range levels {
t.Run(fmt.Sprint(level), func(t *testing.T) {
testResetOutput(t, level, nil)
})
}
})
}
func testResetOutput(t *testing.T, level int, dict []byte) {
writeData := func(w *Writer) {
msg := []byte("now is the time for all good gophers")
w.Write(msg)
w.Flush()
hello := []byte("hello world")
for i := 0; i < 1024; i++ {
w.Write(hello)
}
fill := bytes.Repeat([]byte("x"), 65000)
w.Write(fill)
}
buf := new(bytes.Buffer)
var w *Writer
var err error
if dict == nil {
w, err = NewWriter(buf, level)
} else {
w, err = NewWriterDict(buf, level, dict)
}
if err != nil {
t.Fatalf("NewWriter: %v", err)
}
writeData(w)
w.Close()
out1 := buf.Bytes()
buf2 := new(bytes.Buffer)
w.Reset(buf2)
writeData(w)
w.Close()
out2 := buf2.Bytes()
if len(out1) != len(out2) {
t.Errorf("got %d, expected %d bytes", len(out2), len(out1))
return
}
if !bytes.Equal(out1, out2) {
mm := 0
for i, b := range out1[:len(out2)] {
if b != out2[i] {
t.Errorf("mismatch index %d: %#02x, expected %#02x", i, out2[i], b)
}
mm++
if mm == 10 {
t.Fatal("Stopping")
}
}
}
t.Logf("got %d bytes", len(out1))
}
// TestBestSpeed tests that round-tripping through deflate and then inflate
// recovers the original input. The Write sizes are near the thresholds in the
// compressor.encSpeed method (0, 16, 128), as well as near maxStoreBlockSize
// (65535).
func TestBestSpeed(t *testing.T) {
t.Parallel()
abc := make([]byte, 128)
for i := range abc {
abc[i] = byte(i)
}
abcabc := bytes.Repeat(abc, 131072/len(abc))
var want []byte
testCases := [][]int{
{65536, 0},
{65536, 1},
{65536, 1, 256},
{65536, 1, 65536},
{65536, 14},
{65536, 15},
{65536, 16},
{65536, 16, 256},
{65536, 16, 65536},
{65536, 127},
{65536, 128},
{65536, 128, 256},
{65536, 128, 65536},
{65536, 129},
{65536, 65536, 256},
{65536, 65536, 65536},
}
for i, tc := range testCases {
if i >= 3 && testing.Short() {
break
}
for _, firstN := range []int{1, 65534, 65535, 65536, 65537, 131072} {
tc[0] = firstN
outer:
for _, flush := range []bool{false, true} {
buf := new(bytes.Buffer)
want = want[:0]
w, err := NewWriter(buf, BestSpeed)
if err != nil {
t.Errorf("i=%d, firstN=%d, flush=%t: NewWriter: %v", i, firstN, flush, err)
continue
}
for _, n := range tc {
want = append(want, abcabc[:n]...)
if _, err := w.Write(abcabc[:n]); err != nil {
t.Errorf("i=%d, firstN=%d, flush=%t: Write: %v", i, firstN, flush, err)
continue outer
}
if !flush {
continue
}
if err := w.Flush(); err != nil {
t.Errorf("i=%d, firstN=%d, flush=%t: Flush: %v", i, firstN, flush, err)
continue outer
}
}
if err := w.Close(); err != nil {
t.Errorf("i=%d, firstN=%d, flush=%t: Close: %v", i, firstN, flush, err)
continue
}
r := NewReader(buf)
got, err := io.ReadAll(r)
if err != nil {
t.Errorf("i=%d, firstN=%d, flush=%t: ReadAll: %v", i, firstN, flush, err)
continue
}
r.Close()
if !bytes.Equal(got, want) {
t.Errorf("i=%d, firstN=%d, flush=%t: corruption during deflate-then-inflate", i, firstN, flush)
continue
}
}
}
}
}
var errIO = errors.New("IO error")
// failWriter fails with errIO exactly at the nth call to Write.
type failWriter struct{ n int }
func (w *failWriter) Write(b []byte) (int, error) {
w.n--
if w.n == -1 {
return 0, errIO
}
return len(b), nil
}
func TestWriterPersistentError(t *testing.T) {
t.Parallel()
d, err := os.ReadFile("../../testdata/Isaac.Newton-Opticks.txt")
if err != nil {
t.Fatalf("ReadFile: %v", err)
}
d = d[:10000] // Keep this test short
zw, err := NewWriter(nil, DefaultCompression)
if err != nil {
t.Fatalf("NewWriter: %v", err)
}
// Sweep over the threshold at which an error is returned.
// The variable i makes it such that the ith call to failWriter.Write will
// return errIO. Since failWriter errors are not persistent, we must ensure
// that flate.Writer errors are persistent.
for i := 0; i < 1000; i++ {
fw := &failWriter{i}
zw.Reset(fw)
_, werr := zw.Write(d)
cerr := zw.Close()
if werr != errIO && werr != nil {
t.Errorf("test %d, mismatching Write error: got %v, want %v", i, werr, errIO)
}
if cerr != errIO && fw.n < 0 {
t.Errorf("test %d, mismatching Close error: got %v, want %v", i, cerr, errIO)
}
if fw.n >= 0 {
// At this point, the failure threshold was sufficiently high enough
// that we wrote the whole stream without any errors.
return
}
}
}
func TestBestSpeedMatch(t *testing.T) {
t.Parallel()
cases := []struct {
previous, current []byte
t, s, want int32
}{{
previous: []byte{0, 0, 0, 1, 2},
current: []byte{3, 4, 5, 0, 1, 2, 3, 4, 5},
t: -3,
s: 3,
want: 6,
}, {
previous: []byte{0, 0, 0, 1, 2},
current: []byte{2, 4, 5, 0, 1, 2, 3, 4, 5},
t: -3,
s: 3,
want: 3,
}, {
previous: []byte{0, 0, 0, 1, 1},
current: []byte{3, 4, 5, 0, 1, 2, 3, 4, 5},
t: -3,
s: 3,
want: 2,
}, {
previous: []byte{0, 0, 0, 1, 2},
current: []byte{2, 2, 2, 2, 1, 2, 3, 4, 5},
t: -1,
s: 0,
want: 4,
}, {
previous: []byte{0, 0, 0, 1, 2, 3, 4, 5, 2, 2},
current: []byte{2, 2, 2, 2, 1, 2, 3, 4, 5},
t: -7,
s: 4,
want: 5,
}, {
previous: []byte{9, 9, 9, 9, 9},
current: []byte{2, 2, 2, 2, 1, 2, 3, 4, 5},
t: -1,
s: 0,
want: 0,
}, {
previous: []byte{9, 9, 9, 9, 9},
current: []byte{9, 2, 2, 2, 1, 2, 3, 4, 5},
t: 0,
s: 1,
want: 0,
}, {
previous: []byte{},
current: []byte{9, 2, 2, 2, 1, 2, 3, 4, 5},
t: -5,
s: 1,
want: 0,
}, {
previous: []byte{},
current: []byte{9, 2, 2, 2, 1, 2, 3, 4, 5},
t: -1,
s: 1,
want: 0,
}, {
previous: []byte{},
current: []byte{2, 2, 2, 2, 1, 2, 3, 4, 5},
t: 0,
s: 1,
want: 3,
}, {
previous: []byte{3, 4, 5},
current: []byte{3, 4, 5},
t: -3,
s: 0,
want: 3,
}, {
previous: make([]byte, 1000),
current: make([]byte, 1000),
t: -1000,
s: 0,
want: maxMatchLength - 4,
}, {
previous: make([]byte, 200),
current: make([]byte, 500),
t: -200,
s: 0,
want: maxMatchLength - 4,
}, {
previous: make([]byte, 200),
current: make([]byte, 500),
t: 0,
s: 1,
want: maxMatchLength - 4,
}, {
previous: make([]byte, maxMatchLength-4),
current: make([]byte, 500),
t: -(maxMatchLength - 4),
s: 0,
want: maxMatchLength - 4,
}, {
previous: make([]byte, 200),
current: make([]byte, 500),
t: -200,
s: 400,
want: 100,
}, {
previous: make([]byte, 10),
current: make([]byte, 500),
t: 200,
s: 400,
want: 100,
}}
for i, c := range cases {
e := deflateFast{prev: c.previous}
got := e.matchLen(c.s, c.t, c.current)
if got != c.want {
t.Errorf("Test %d: match length, want %d, got %d", i, c.want, got)
}
}
}
func TestBestSpeedMaxMatchOffset(t *testing.T) {
t.Parallel()
const abc, xyz = "abcdefgh", "stuvwxyz"
for _, matchBefore := range []bool{false, true} {
for _, extra := range []int{0, inputMargin - 1, inputMargin, inputMargin + 1, 2 * inputMargin} {
for offsetAdj := -5; offsetAdj <= +5; offsetAdj++ {
report := func(desc string, err error) {
t.Errorf("matchBefore=%t, extra=%d, offsetAdj=%d: %s%v",
matchBefore, extra, offsetAdj, desc, err)
}
offset := maxMatchOffset + offsetAdj
// Make src to be a []byte of the form
// "%s%s%s%s%s" % (abc, zeros0, xyzMaybe, abc, zeros1)
// where:
// zeros0 is approximately maxMatchOffset zeros.
// xyzMaybe is either xyz or the empty string.
// zeros1 is between 0 and 30 zeros.
// The difference between the two abc's will be offset, which
// is maxMatchOffset plus or minus a small adjustment.
src := make([]byte, offset+len(abc)+extra)
copy(src, abc)
if !matchBefore {
copy(src[offset-len(xyz):], xyz)
}
copy(src[offset:], abc)
buf := new(bytes.Buffer)
w, err := NewWriter(buf, BestSpeed)
if err != nil {
report("NewWriter: ", err)
continue
}
if _, err := w.Write(src); err != nil {
report("Write: ", err)
continue
}
if err := w.Close(); err != nil {
report("Writer.Close: ", err)
continue
}
r := NewReader(buf)
dst, err := io.ReadAll(r)
r.Close()
if err != nil {
report("ReadAll: ", err)
continue
}
if !bytes.Equal(dst, src) {
report("", fmt.Errorf("bytes differ after round-tripping"))
continue
}
}
}
}
}
func TestBestSpeedShiftOffsets(t *testing.T) {
// Test if shiftoffsets properly preserves matches and resets out-of-range matches
// seen in https://github.com/golang/go/issues/4142
enc := newDeflateFast()
// testData may not generate internal matches.
testData := make([]byte, 32)
rng := rand.New(rand.NewSource(0))
for i := range testData {
testData[i] = byte(rng.Uint32())
}
// Encode the testdata with clean state.
// Second part should pick up matches from the first block.
wantFirstTokens := len(enc.encode(nil, testData))
wantSecondTokens := len(enc.encode(nil, testData))
if wantFirstTokens <= wantSecondTokens {
t.Fatalf("test needs matches between inputs to be generated")
}
// Forward the current indicator to before wraparound.
enc.cur = bufferReset - int32(len(testData))
// Part 1 before wrap, should match clean state.
got := len(enc.encode(nil, testData))
if wantFirstTokens != got {
t.Errorf("got %d, want %d tokens", got, wantFirstTokens)
}
// Verify we are about to wrap.
if enc.cur != bufferReset {
t.Errorf("got %d, want e.cur to be at bufferReset (%d)", enc.cur, bufferReset)
}
// Part 2 should match clean state as well even if wrapped.
got = len(enc.encode(nil, testData))
if wantSecondTokens != got {
t.Errorf("got %d, want %d token", got, wantSecondTokens)
}
// Verify that we wrapped.
if enc.cur >= bufferReset {
t.Errorf("want e.cur to be < bufferReset (%d), got %d", bufferReset, enc.cur)
}
// Forward the current buffer, leaving the matches at the bottom.
enc.cur = bufferReset
enc.shiftOffsets()
// Ensure that no matches were picked up.
got = len(enc.encode(nil, testData))
if wantFirstTokens != got {
t.Errorf("got %d, want %d tokens", got, wantFirstTokens)
}
}
func TestMaxStackSize(t *testing.T) {
// This test must not run in parallel with other tests as debug.SetMaxStack
// affects all goroutines.
n := debug.SetMaxStack(1 << 16)
defer debug.SetMaxStack(n)
var wg sync.WaitGroup
defer wg.Wait()
b := make([]byte, 1<<20)
for level := HuffmanOnly; level <= BestCompression; level++ {
// Run in separate goroutine to increase probability of stack regrowth.
wg.Add(1)
go func(level int) {
defer wg.Done()
zw, err := NewWriter(io.Discard, level)
if err != nil {
t.Errorf("level %d, NewWriter() = %v, want nil", level, err)
}
if n, err := zw.Write(b); n != len(b) || err != nil {
t.Errorf("level %d, Write() = (%d, %v), want (%d, nil)", level, n, err, len(b))
}
if err := zw.Close(); err != nil {
t.Errorf("level %d, Close() = %v, want nil", level, err)
}
zw.Reset(io.Discard)
}(level)
}
}
@@ -0,0 +1,309 @@
// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package flate
import "math"
// This encoding algorithm, which prioritizes speed over output size, is
// based on Snappy's LZ77-style encoder: github.com/golang/snappy
const (
tableBits = 14 // Bits used in the table.
tableSize = 1 << tableBits // Size of the table.
tableMask = tableSize - 1 // Mask for table indices. Redundant, but can eliminate bounds checks.
tableShift = 32 - tableBits // Right-shift to get the tableBits most significant bits of a uint32.
// Reset the buffer offset when reaching this.
// Offsets are stored between blocks as int32 values.
// Since the offset we are checking against is at the beginning
// of the buffer, we need to subtract the current and input
// buffer to not risk overflowing the int32.
bufferReset = math.MaxInt32 - maxStoreBlockSize*2
)
func load32(b []byte, i int32) uint32 {
b = b[i : i+4 : len(b)] // Help the compiler eliminate bounds checks on the next line.
return uint32(b[0]) | uint32(b[1])<<8 | uint32(b[2])<<16 | uint32(b[3])<<24
}
func load64(b []byte, i int32) uint64 {
b = b[i : i+8 : len(b)] // Help the compiler eliminate bounds checks on the next line.
return uint64(b[0]) | uint64(b[1])<<8 | uint64(b[2])<<16 | uint64(b[3])<<24 |
uint64(b[4])<<32 | uint64(b[5])<<40 | uint64(b[6])<<48 | uint64(b[7])<<56
}
func hash(u uint32) uint32 {
return (u * 0x1e35a7bd) >> tableShift
}
// These constants are defined by the Snappy implementation so that its
// assembly implementation can fast-path some 16-bytes-at-a-time copies. They
// aren't necessary in the pure Go implementation, as we don't use those same
// optimizations, but using the same thresholds doesn't really hurt.
const (
inputMargin = 16 - 1
minNonLiteralBlockSize = 1 + 1 + inputMargin
)
type tableEntry struct {
val uint32 // Value at destination
offset int32
}
// deflateFast maintains the table for matches,
// and the previous byte block for cross block matching.
type deflateFast struct {
table [tableSize]tableEntry
prev []byte // Previous block, zero length if unknown.
cur int32 // Current match offset.
}
func newDeflateFast() *deflateFast {
return &deflateFast{cur: maxStoreBlockSize, prev: make([]byte, 0, maxStoreBlockSize)}
}
// encode encodes a block given in src and appends tokens
// to dst and returns the result.
func (e *deflateFast) encode(dst []token, src []byte) []token {
// Ensure that e.cur doesn't wrap.
if e.cur >= bufferReset {
e.shiftOffsets()
}
// This check isn't in the Snappy implementation, but there, the caller
// instead of the callee handles this case.
if len(src) < minNonLiteralBlockSize {
e.cur += maxStoreBlockSize
e.prev = e.prev[:0]
return emitLiteral(dst, src)
}
// sLimit is when to stop looking for offset/length copies. The inputMargin
// lets us use a fast path for emitLiteral in the main loop, while we are
// looking for copies.
sLimit := int32(len(src) - inputMargin)
// nextEmit is where in src the next emitLiteral should start from.
nextEmit := int32(0)
s := int32(0)
cv := load32(src, s)
nextHash := hash(cv)
for {
// Copied from the C++ snappy implementation:
//
// Heuristic match skipping: If 32 bytes are scanned with no matches
// found, start looking only at every other byte. If 32 more bytes are
// scanned (or skipped), look at every third byte, etc.. When a match
// is found, immediately go back to looking at every byte. This is a
// small loss (~5% performance, ~0.1% density) for compressible data
// due to more bookkeeping, but for non-compressible data (such as
// JPEG) it's a huge win since the compressor quickly "realizes" the
// data is incompressible and doesn't bother looking for matches
// everywhere.
//
// The "skip" variable keeps track of how many bytes there are since
// the last match; dividing it by 32 (ie. right-shifting by five) gives
// the number of bytes to move ahead for each iteration.
skip := int32(32)
nextS := s
var candidate tableEntry
for {
s = nextS
bytesBetweenHashLookups := skip >> 5
nextS = s + bytesBetweenHashLookups
skip += bytesBetweenHashLookups
if nextS > sLimit {
goto emitRemainder
}
candidate = e.table[nextHash&tableMask]
now := load32(src, nextS)
e.table[nextHash&tableMask] = tableEntry{offset: s + e.cur, val: cv}
nextHash = hash(now)
offset := s - (candidate.offset - e.cur)
if offset > maxMatchOffset || cv != candidate.val {
// Out of range or not matched.
cv = now
continue
}
break
}
// A 4-byte match has been found. We'll later see if more than 4 bytes
// match. But, prior to the match, src[nextEmit:s] are unmatched. Emit
// them as literal bytes.
dst = emitLiteral(dst, src[nextEmit:s])
// Call emitCopy, and then see if another emitCopy could be our next
// move. Repeat until we find no match for the input immediately after
// what was consumed by the last emitCopy call.
//
// If we exit this loop normally then we need to call emitLiteral next,
// though we don't yet know how big the literal will be. We handle that
// by proceeding to the next iteration of the main loop. We also can
// exit this loop via goto if we get close to exhausting the input.
for {
// Invariant: we have a 4-byte match at s, and no need to emit any
// literal bytes prior to s.
// Extend the 4-byte match as long as possible.
//
s += 4
t := candidate.offset - e.cur + 4
l := e.matchLen(s, t, src)
// matchToken is flate's equivalent of Snappy's emitCopy. (length,offset)
dst = append(dst, matchToken(uint32(l+4-baseMatchLength), uint32(s-t-baseMatchOffset)))
s += l
nextEmit = s
if s >= sLimit {
goto emitRemainder
}
// We could immediately start working at s now, but to improve
// compression we first update the hash table at s-1 and at s. If
// another emitCopy is not our next move, also calculate nextHash
// at s+1. At least on GOARCH=amd64, these three hash calculations
// are faster as one load64 call (with some shifts) instead of
// three load32 calls.
x := load64(src, s-1)
prevHash := hash(uint32(x))
e.table[prevHash&tableMask] = tableEntry{offset: e.cur + s - 1, val: uint32(x)}
x >>= 8
currHash := hash(uint32(x))
candidate = e.table[currHash&tableMask]
e.table[currHash&tableMask] = tableEntry{offset: e.cur + s, val: uint32(x)}
offset := s - (candidate.offset - e.cur)
if offset > maxMatchOffset || uint32(x) != candidate.val {
cv = uint32(x >> 8)
nextHash = hash(cv)
s++
break
}
}
}
emitRemainder:
if int(nextEmit) < len(src) {
dst = emitLiteral(dst, src[nextEmit:])
}
e.cur += int32(len(src))
e.prev = e.prev[:len(src)]
copy(e.prev, src)
return dst
}
func emitLiteral(dst []token, lit []byte) []token {
for _, v := range lit {
dst = append(dst, literalToken(uint32(v)))
}
return dst
}
// matchLen returns the match length between src[s:] and src[t:].
// t can be negative to indicate the match is starting in e.prev.
// We assume that src[s-4:s] and src[t-4:t] already match.
func (e *deflateFast) matchLen(s, t int32, src []byte) int32 {
s1 := int(s) + maxMatchLength - 4
if s1 > len(src) {
s1 = len(src)
}
// If we are inside the current block
if t >= 0 {
b := src[t:]
a := src[s:s1]
b = b[:len(a)]
// Extend the match to be as long as possible.
for i := range a {
if a[i] != b[i] {
return int32(i)
}
}
return int32(len(a))
}
// We found a match in the previous block.
tp := int32(len(e.prev)) + t
if tp < 0 {
return 0
}
// Extend the match to be as long as possible.
a := src[s:s1]
b := e.prev[tp:]
if len(b) > len(a) {
b = b[:len(a)]
}
a = a[:len(b)]
for i := range b {
if a[i] != b[i] {
return int32(i)
}
}
// If we reached our limit, we matched everything we are
// allowed to in the previous block and we return.
n := int32(len(b))
if int(s+n) == s1 {
return n
}
// Continue looking for more matches in the current block.
a = src[s+n : s1]
b = src[:len(a)]
for i := range a {
if a[i] != b[i] {
return int32(i) + n
}
}
return int32(len(a)) + n
}
// Reset resets the encoding history.
// This ensures that no matches are made to the previous block.
func (e *deflateFast) reset() {
e.prev = e.prev[:0]
// Bump the offset, so all matches will fail distance check.
// Nothing should be >= e.cur in the table.
e.cur += maxMatchOffset
// Protect against e.cur wraparound.
if e.cur >= bufferReset {
e.shiftOffsets()
}
}
// shiftOffsets will shift down all match offset.
// This is only called in rare situations to prevent integer overflow.
//
// See https://golang.org/issue/18636 and https://github.com/golang/go/issues/34121.
func (e *deflateFast) shiftOffsets() {
if len(e.prev) == 0 {
// We have no history; just clear the table.
for i := range e.table[:] {
e.table[i] = tableEntry{}
}
e.cur = maxMatchOffset + 1
return
}
// Shift down everything in the table that isn't already too far away.
for i := range e.table[:] {
v := e.table[i].offset - e.cur + maxMatchOffset + 1
if v < 0 {
// We want to reset e.cur to maxMatchOffset + 1, so we need to shift
// all table entries down by (e.cur - (maxMatchOffset + 1)).
// Because we ignore matches > maxMatchOffset, we can cap
// any negative offsets at 0.
v = 0
}
e.table[i].offset = v
}
e.cur = maxMatchOffset + 1
}
@@ -0,0 +1,186 @@
// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package flate
// dictDecoder implements the LZ77 sliding dictionary as used in decompression.
// LZ77 decompresses data through sequences of two forms of commands:
//
// * Literal insertions: Runs of one or more symbols are inserted into the data
// stream as is. This is accomplished through the writeByte method for a
// single symbol, or combinations of writeSlice/writeMark for multiple symbols.
// Any valid stream must start with a literal insertion if no preset dictionary
// is used.
//
// * Backward copies: Runs of one or more symbols are copied from previously
// emitted data. Backward copies come as the tuple (dist, length) where dist
// determines how far back in the stream to copy from and length determines how
// many bytes to copy. Note that it is valid for the length to be greater than
// the distance. Since LZ77 uses forward copies, that situation is used to
// perform a form of run-length encoding on repeated runs of symbols.
// The writeCopy and tryWriteCopy are used to implement this command.
//
// For performance reasons, this implementation performs little to no sanity
// checks about the arguments. As such, the invariants documented for each
// method call must be respected.
type dictDecoder struct {
hist []byte // Sliding window history
// Invariant: 0 <= rdPos <= wrPos <= len(hist)
wrPos int // Current output position in buffer
rdPos int // Have emitted hist[:rdPos] already
full bool // Has a full window length been written yet?
}
// To minimize the memory usage in TinyGo, it is defined as a fixed array
// instead of a make().
var ddHistBuf [1 << 15]byte
// init initializes dictDecoder to have a sliding window dictionary of the given
// size. If a preset dict is provided, it will initialize the dictionary with
// the contents of dict.
func (dd *dictDecoder) init(size int, dict []byte) {
*dd = dictDecoder{hist: dd.hist}
if cap(dd.hist) < size {
dd.hist = ddHistBuf[:size]
}
dd.hist = dd.hist[:size]
if len(dict) > len(dd.hist) {
dict = dict[len(dict)-len(dd.hist):]
}
dd.wrPos = copy(dd.hist, dict)
if dd.wrPos == len(dd.hist) {
dd.wrPos = 0
dd.full = true
}
dd.rdPos = dd.wrPos
}
// histSize reports the total amount of historical data in the dictionary.
func (dd *dictDecoder) histSize() int {
if dd.full {
return len(dd.hist)
}
return dd.wrPos
}
// availRead reports the number of bytes that can be flushed by readFlush.
func (dd *dictDecoder) availRead() int {
return dd.wrPos - dd.rdPos
}
// availWrite reports the available amount of output buffer space.
func (dd *dictDecoder) availWrite() int {
return len(dd.hist) - dd.wrPos
}
// writeSlice returns a slice of the available buffer to write data to.
//
// This invariant will be kept: len(s) <= availWrite()
func (dd *dictDecoder) writeSlice() []byte {
return dd.hist[dd.wrPos:]
}
// writeMark advances the writer pointer by cnt.
//
// This invariant must be kept: 0 <= cnt <= availWrite()
func (dd *dictDecoder) writeMark(cnt int) {
dd.wrPos += cnt
}
// writeByte writes a single byte to the dictionary.
//
// This invariant must be kept: 0 < availWrite()
func (dd *dictDecoder) writeByte(c byte) {
dd.hist[dd.wrPos] = c
dd.wrPos++
}
// writeCopy copies a string at a given (dist, length) to the output.
// This returns the number of bytes copied and may be less than the requested
// length if the available space in the output buffer is too small.
//
// This invariant must be kept: 0 < dist <= histSize()
func (dd *dictDecoder) writeCopy(dist, length int) int {
dstBase := dd.wrPos
dstPos := dstBase
srcPos := dstPos - dist
endPos := dstPos + length
if endPos > len(dd.hist) {
endPos = len(dd.hist)
}
// Copy non-overlapping section after destination position.
//
// This section is non-overlapping in that the copy length for this section
// is always less than or equal to the backwards distance. This can occur
// if a distance refers to data that wraps-around in the buffer.
// Thus, a backwards copy is performed here; that is, the exact bytes in
// the source prior to the copy is placed in the destination.
if srcPos < 0 {
srcPos += len(dd.hist)
dstPos += copy(dd.hist[dstPos:endPos], dd.hist[srcPos:])
srcPos = 0
}
// Copy possibly overlapping section before destination position.
//
// This section can overlap if the copy length for this section is larger
// than the backwards distance. This is allowed by LZ77 so that repeated
// strings can be succinctly represented using (dist, length) pairs.
// Thus, a forwards copy is performed here; that is, the bytes copied is
// possibly dependent on the resulting bytes in the destination as the copy
// progresses along. This is functionally equivalent to the following:
//
// for i := 0; i < endPos-dstPos; i++ {
// dd.hist[dstPos+i] = dd.hist[srcPos+i]
// }
// dstPos = endPos
//
for dstPos < endPos {
dstPos += copy(dd.hist[dstPos:endPos], dd.hist[srcPos:dstPos])
}
dd.wrPos = dstPos
return dstPos - dstBase
}
// tryWriteCopy tries to copy a string at a given (distance, length) to the
// output. This specialized version is optimized for short distances.
//
// This method is designed to be inlined for performance reasons.
//
// This invariant must be kept: 0 < dist <= histSize()
func (dd *dictDecoder) tryWriteCopy(dist, length int) int {
dstPos := dd.wrPos
endPos := dstPos + length
if dstPos < dist || endPos > len(dd.hist) {
return 0
}
dstBase := dstPos
srcPos := dstPos - dist
// Copy possibly overlapping section before destination position.
for dstPos < endPos {
dstPos += copy(dd.hist[dstPos:endPos], dd.hist[srcPos:dstPos])
}
dd.wrPos = dstPos
return dstPos - dstBase
}
// readFlush returns a slice of the historical buffer that is ready to be
// emitted to the user. The data returned by readFlush must be fully consumed
// before calling any other dictDecoder methods.
func (dd *dictDecoder) readFlush() []byte {
toRead := dd.hist[dd.rdPos:dd.wrPos]
dd.rdPos = dd.wrPos
if dd.wrPos == len(dd.hist) {
dd.wrPos, dd.rdPos = 0, 0
dd.full = true
}
return toRead
}
@@ -0,0 +1,139 @@
// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package flate
import (
"bytes"
"strings"
"testing"
)
func TestDictDecoder(t *testing.T) {
const (
abc = "ABC\n"
fox = "The quick brown fox jumped over the lazy dog!\n"
poem = "The Road Not Taken\nRobert Frost\n" +
"\n" +
"Two roads diverged in a yellow wood,\n" +
"And sorry I could not travel both\n" +
"And be one traveler, long I stood\n" +
"And looked down one as far as I could\n" +
"To where it bent in the undergrowth;\n" +
"\n" +
"Then took the other, as just as fair,\n" +
"And having perhaps the better claim,\n" +
"Because it was grassy and wanted wear;\n" +
"Though as for that the passing there\n" +
"Had worn them really about the same,\n" +
"\n" +
"And both that morning equally lay\n" +
"In leaves no step had trodden black.\n" +
"Oh, I kept the first for another day!\n" +
"Yet knowing how way leads on to way,\n" +
"I doubted if I should ever come back.\n" +
"\n" +
"I shall be telling this with a sigh\n" +
"Somewhere ages and ages hence:\n" +
"Two roads diverged in a wood, and I-\n" +
"I took the one less traveled by,\n" +
"And that has made all the difference.\n"
)
var poemRefs = []struct {
dist int // Backward distance (0 if this is an insertion)
length int // Length of copy or insertion
}{
{0, 38}, {33, 3}, {0, 48}, {79, 3}, {0, 11}, {34, 5}, {0, 6}, {23, 7},
{0, 8}, {50, 3}, {0, 2}, {69, 3}, {34, 5}, {0, 4}, {97, 3}, {0, 4},
{43, 5}, {0, 6}, {7, 4}, {88, 7}, {0, 12}, {80, 3}, {0, 2}, {141, 4},
{0, 1}, {196, 3}, {0, 3}, {157, 3}, {0, 6}, {181, 3}, {0, 2}, {23, 3},
{77, 3}, {28, 5}, {128, 3}, {110, 4}, {70, 3}, {0, 4}, {85, 6}, {0, 2},
{182, 6}, {0, 4}, {133, 3}, {0, 7}, {47, 5}, {0, 20}, {112, 5}, {0, 1},
{58, 3}, {0, 8}, {59, 3}, {0, 4}, {173, 3}, {0, 5}, {114, 3}, {0, 4},
{92, 5}, {0, 2}, {71, 3}, {0, 2}, {76, 5}, {0, 1}, {46, 3}, {96, 4},
{130, 4}, {0, 3}, {360, 3}, {0, 3}, {178, 5}, {0, 7}, {75, 3}, {0, 3},
{45, 6}, {0, 6}, {299, 6}, {180, 3}, {70, 6}, {0, 1}, {48, 3}, {66, 4},
{0, 3}, {47, 5}, {0, 9}, {325, 3}, {0, 1}, {359, 3}, {318, 3}, {0, 2},
{199, 3}, {0, 1}, {344, 3}, {0, 3}, {248, 3}, {0, 10}, {310, 3}, {0, 3},
{93, 6}, {0, 3}, {252, 3}, {157, 4}, {0, 2}, {273, 5}, {0, 14}, {99, 4},
{0, 1}, {464, 4}, {0, 2}, {92, 4}, {495, 3}, {0, 1}, {322, 4}, {16, 4},
{0, 3}, {402, 3}, {0, 2}, {237, 4}, {0, 2}, {432, 4}, {0, 1}, {483, 5},
{0, 2}, {294, 4}, {0, 2}, {306, 3}, {113, 5}, {0, 1}, {26, 4}, {164, 3},
{488, 4}, {0, 1}, {542, 3}, {248, 6}, {0, 5}, {205, 3}, {0, 8}, {48, 3},
{449, 6}, {0, 2}, {192, 3}, {328, 4}, {9, 5}, {433, 3}, {0, 3}, {622, 25},
{615, 5}, {46, 5}, {0, 2}, {104, 3}, {475, 10}, {549, 3}, {0, 4}, {597, 8},
{314, 3}, {0, 1}, {473, 6}, {317, 5}, {0, 1}, {400, 3}, {0, 3}, {109, 3},
{151, 3}, {48, 4}, {0, 4}, {125, 3}, {108, 3}, {0, 2},
}
var got, want bytes.Buffer
var dd dictDecoder
dd.init(1<<11, nil)
var writeCopy = func(dist, length int) {
for length > 0 {
cnt := dd.tryWriteCopy(dist, length)
if cnt == 0 {
cnt = dd.writeCopy(dist, length)
}
length -= cnt
if dd.availWrite() == 0 {
got.Write(dd.readFlush())
}
}
}
var writeString = func(str string) {
for len(str) > 0 {
cnt := copy(dd.writeSlice(), str)
str = str[cnt:]
dd.writeMark(cnt)
if dd.availWrite() == 0 {
got.Write(dd.readFlush())
}
}
}
writeString(".")
want.WriteByte('.')
str := poem
for _, ref := range poemRefs {
if ref.dist == 0 {
writeString(str[:ref.length])
} else {
writeCopy(ref.dist, ref.length)
}
str = str[ref.length:]
}
want.WriteString(poem)
writeCopy(dd.histSize(), 33)
want.Write(want.Bytes()[:33])
writeString(abc)
writeCopy(len(abc), 59*len(abc))
want.WriteString(strings.Repeat(abc, 60))
writeString(fox)
writeCopy(len(fox), 9*len(fox))
want.WriteString(strings.Repeat(fox, 10))
writeString(".")
writeCopy(1, 9)
want.WriteString(strings.Repeat(".", 10))
writeString(strings.ToUpper(poem))
writeCopy(len(poem), 7*len(poem))
want.WriteString(strings.Repeat(strings.ToUpper(poem), 8))
writeCopy(dd.histSize(), 10)
want.Write(want.Bytes()[want.Len()-dd.histSize():][:10])
got.Write(dd.readFlush())
if got.String() != want.String() {
t.Errorf("final string mismatch:\ngot %q\nwant %q", got.String(), want.String())
}
}
@@ -0,0 +1,243 @@
// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package flate_test
import (
"bytes"
"compress/flate"
"fmt"
"io"
"log"
"os"
"strings"
"sync"
)
// In performance critical applications, Reset can be used to discard the
// current compressor or decompressor state and reinitialize them quickly
// by taking advantage of previously allocated memory.
func Example_reset() {
proverbs := []string{
"Don't communicate by sharing memory, share memory by communicating.\n",
"Concurrency is not parallelism.\n",
"The bigger the interface, the weaker the abstraction.\n",
"Documentation is for users.\n",
}
var r strings.Reader
var b bytes.Buffer
buf := make([]byte, 32<<10)
zw, err := flate.NewWriter(nil, flate.DefaultCompression)
if err != nil {
log.Fatal(err)
}
zr := flate.NewReader(nil)
for _, s := range proverbs {
r.Reset(s)
b.Reset()
// Reset the compressor and encode from some input stream.
zw.Reset(&b)
if _, err := io.CopyBuffer(zw, &r, buf); err != nil {
log.Fatal(err)
}
if err := zw.Close(); err != nil {
log.Fatal(err)
}
// Reset the decompressor and decode to some output stream.
if err := zr.(flate.Resetter).Reset(&b, nil); err != nil {
log.Fatal(err)
}
if _, err := io.CopyBuffer(os.Stdout, zr, buf); err != nil {
log.Fatal(err)
}
if err := zr.Close(); err != nil {
log.Fatal(err)
}
}
// Output:
// Don't communicate by sharing memory, share memory by communicating.
// Concurrency is not parallelism.
// The bigger the interface, the weaker the abstraction.
// Documentation is for users.
}
// A preset dictionary can be used to improve the compression ratio.
// The downside to using a dictionary is that the compressor and decompressor
// must agree in advance what dictionary to use.
func Example_dictionary() {
// The dictionary is a string of bytes. When compressing some input data,
// the compressor will attempt to substitute substrings with matches found
// in the dictionary. As such, the dictionary should only contain substrings
// that are expected to be found in the actual data stream.
const dict = `<?xml version="1.0"?>` + `<book>` + `<data>` + `<meta name="` + `" content="`
// The data to compress should (but is not required to) contain frequent
// substrings that match those in the dictionary.
const data = `<?xml version="1.0"?>
<book>
<meta name="title" content="The Go Programming Language"/>
<meta name="authors" content="Alan Donovan and Brian Kernighan"/>
<meta name="published" content="2015-10-26"/>
<meta name="isbn" content="978-0134190440"/>
<data>...</data>
</book>
`
var b bytes.Buffer
// Compress the data using the specially crafted dictionary.
zw, err := flate.NewWriterDict(&b, flate.DefaultCompression, []byte(dict))
if err != nil {
log.Fatal(err)
}
if _, err := io.Copy(zw, strings.NewReader(data)); err != nil {
log.Fatal(err)
}
if err := zw.Close(); err != nil {
log.Fatal(err)
}
// The decompressor must use the same dictionary as the compressor.
// Otherwise, the input may appear as corrupted.
fmt.Println("Decompressed output using the dictionary:")
zr := flate.NewReaderDict(bytes.NewReader(b.Bytes()), []byte(dict))
if _, err := io.Copy(os.Stdout, zr); err != nil {
log.Fatal(err)
}
if err := zr.Close(); err != nil {
log.Fatal(err)
}
fmt.Println()
// Substitute all of the bytes in the dictionary with a '#' to visually
// demonstrate the approximate effectiveness of using a preset dictionary.
fmt.Println("Substrings matched by the dictionary are marked with #:")
hashDict := []byte(dict)
for i := range hashDict {
hashDict[i] = '#'
}
zr = flate.NewReaderDict(&b, hashDict)
if _, err := io.Copy(os.Stdout, zr); err != nil {
log.Fatal(err)
}
if err := zr.Close(); err != nil {
log.Fatal(err)
}
// Output:
// Decompressed output using the dictionary:
// <?xml version="1.0"?>
// <book>
// <meta name="title" content="The Go Programming Language"/>
// <meta name="authors" content="Alan Donovan and Brian Kernighan"/>
// <meta name="published" content="2015-10-26"/>
// <meta name="isbn" content="978-0134190440"/>
// <data>...</data>
// </book>
//
// Substrings matched by the dictionary are marked with #:
// #####################
// ######
// ############title###########The Go Programming Language"/#
// ############authors###########Alan Donovan and Brian Kernighan"/#
// ############published###########2015-10-26"/#
// ############isbn###########978-0134190440"/#
// ######...</#####
// </#####
}
// DEFLATE is suitable for transmitting compressed data across the network.
func Example_synchronization() {
var wg sync.WaitGroup
defer wg.Wait()
// Use io.Pipe to simulate a network connection.
// A real network application should take care to properly close the
// underlying connection.
rp, wp := io.Pipe()
// Start a goroutine to act as the transmitter.
wg.Add(1)
go func() {
defer wg.Done()
zw, err := flate.NewWriter(wp, flate.BestSpeed)
if err != nil {
log.Fatal(err)
}
b := make([]byte, 256)
for _, m := range strings.Fields("A long time ago in a galaxy far, far away...") {
// We use a simple framing format where the first byte is the
// message length, followed the message itself.
b[0] = uint8(copy(b[1:], m))
if _, err := zw.Write(b[:1+len(m)]); err != nil {
log.Fatal(err)
}
// Flush ensures that the receiver can read all data sent so far.
if err := zw.Flush(); err != nil {
log.Fatal(err)
}
}
if err := zw.Close(); err != nil {
log.Fatal(err)
}
}()
// Start a goroutine to act as the receiver.
wg.Add(1)
go func() {
defer wg.Done()
zr := flate.NewReader(rp)
b := make([]byte, 256)
for {
// Read the message length.
// This is guaranteed to return for every corresponding
// Flush and Close on the transmitter side.
if _, err := io.ReadFull(zr, b[:1]); err != nil {
if err == io.EOF {
break // The transmitter closed the stream
}
log.Fatal(err)
}
// Read the message content.
n := int(b[0])
if _, err := io.ReadFull(zr, b[:n]); err != nil {
log.Fatal(err)
}
fmt.Printf("Received %d bytes: %s\n", n, b[:n])
}
fmt.Println()
if err := zr.Close(); err != nil {
log.Fatal(err)
}
}()
// Output:
// Received 1 bytes: A
// Received 4 bytes: long
// Received 4 bytes: time
// Received 3 bytes: ago
// Received 2 bytes: in
// Received 1 bytes: a
// Received 6 bytes: galaxy
// Received 4 bytes: far,
// Received 3 bytes: far
// Received 7 bytes: away...
}

Some files were not shown because too many files have changed in this diff Show More