Compare commits

..

1 Commits

Author SHA1 Message Date
deadprogram 992c02c661 lora: add starting point for US915 settings
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-09-17 19:50:39 +02:00
169 changed files with 16439 additions and 8857 deletions
-37
View File
@@ -1,40 +1,3 @@
0.26.0
---
- **core**
- i2c iface refactor: Resolve #559
- fix uses of legacy i2c WriteRegister calls
- add correct Tx implementation for mock I2C interfaces
- bump golang.org/x/net version
- **new devices**
- **bma42x**
- add new BMA421/BMA425 driver
- **ndir**
- add Sandbox Electronics NDIR CO2 sensor driver (#580)
- **mpu9150**
- implement driver for Mpu9150 (#596)
- **sht4x**
- implement driver for sht4x (#597)
- **pcf8523**
- implement driver for pcf8523 (#599)
- **enhancements**
- **ssd1306**
- improve bus error handling
- **bugfixes**
- **st7789**
- fix scrolling when rotated by 180°
- **st7789**
- fix incorrect Rotation configuration
- fix SetScrollArea
- **ili9341**
- fix SetScrollArea
- **build**
- use latest tag of tinygo-dev container for running tests
0.25.0
---
-233
View File
@@ -1,233 +0,0 @@
### Table of Contents
- ["net" Package](#net-package)
- [Using "net" Package](#using-net-package)
- [Using "net/http" Package](#using-nethttp-package)
- [Using "crypto/tls" Package](#using-cryptotls-package)
- [Using Sockets](#using-sockets)
## "net" Package
TinyGo's "net" package is ported from Go. The port offers a subset of Go's
"net" package. The subset maintains Go 1 compatiblity guarantee. A Go
application that uses "net" will most-likey just work on TinyGo if the usage is
within the subset offered. (There may be external constraints such as limited
SRAM on some targets that may limit full "net" functionality).
Continue below for details on using "net" and "net/http" packages.
See src/net/READMD.md in the TinyGo repo for more details on maintaining
TinyGo's "net" package.
## Using "net" Package
Ideally, TinyGo's "net" package would be Go's "net" package and applications
using "net" would just work, as-is. TinyGo's net package is a partial port of
Go's net package, so some things may not work because they have not been
ported.
There are a few features excluded during the porting process, in particular:
- No IPv6 support
- No DualStack support
Run ```go doc -all ./src/net``` in TinyGo repo to see full listing of what has
been ported. Here is a list of things known to work. You can find examples
of these at [examples/net](examples/net/).
### What is Known to Work
(These are all IPv4 only).
- TCP client and server
- UDP client
- TLS client
- HTTP client and server
- HTTPS client
- NTP client (UDP)
- MQTT client (paho & natiu)
- WebSocket client and server
Multiple sockets can be opened in a single app. For example, the app could run
as an http server listen on port :80 and also use NTP to get the current time
or send something over MQTT. There is a practical limit to the number of
active sockets per app, around 8 or 10, so don't go crazy.
Applications using Go's net package will need a few setup steps to work with
TinyGo's net package. The steps are required before using "net".
### Step 1: Probe to Load Network Driver
Call Probe() to load the correct network driver for your target. Probe()
allows the app to work on multiple targets.
```go
package main
import (
"tinygo.org/x/drivers/netlink/probe"
)
func main() {
// load network driver for target
link, dev := probe.Probe()
...
}
```
Probe() will load the driver with default configuration for the target. For
custom configuration, the app can open code Probe() for the target
requirements.
Probe() returns a [Netlinker](netlink/README.md) and a
[Netdever](netdev/README.md), interfaces implemented by the network driver.
Next, we'll use the Netlinker interface to connect the target to an IP network.
### Step 2: Connect to an IP Network
Before the net package is fully functional, we need to connect the target to an
IP network.
```go
package main
import (
"tinygo.org/x/drivers/netlink"
"tinygo.org/x/drivers/netlink/probe"
)
func main() {
// load network driver for target
link, _ := probe.Probe()
// Connect target to IP network
link.NetConnect(&netlink.ConnectParams{
Ssid: "my SSID",
Passphrase: "my passphrase",
})
// OK to use "net" from here on
...
}
```
Optionally, get notified of IP network connects and disconnects:
```go
link.Notify(func(e netlink.Event) {
switch e {
case netlink.EventNetUp: println("Network UP")
case netlink.EventNetDown: println("Network DOWN")
})
```
Here is an example of an http server listening on port :8080:
```go
package main
import (
"fmt"
"net/http"
"tinygo.org/x/drivers/netlink"
"tinygo.org/x/drivers/netlink/probe"
)
func HelloServer(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, %s!", r.URL.Path[1:])
}
func main() {
// load network driver for target
link, _ := probe.Probe()
// Connect target to IP network
link.NetConnect(&netlink.ConnectParams{
Ssid: "my SSID",
Passphrase: "my passphrase",
})
// Serve it up
http.HandleFunc("/", HelloServer)
http.ListenAndServe(":8080", nil)
}
```
## Using "net/http" Package
TinyGo's net/http package is a partial port of Go's net/http package, providing
a subset of the full net/http package. There are a few features excluded
during the porting process, in particular:
- No HTTP/2 support
- No TLS support for HTTP servers (no https servers)
- HTTP client request can't be reused
HTTP client methods (http.Get, http.Head, http.Post, and http.PostForm) are
functional. Dial clients support both HTTP and HTTPS URLs.
HTTP server methods and objects are mostly ported, but for HTTP only; HTTPS
servers are not supported.
HTTP request and response handling code is mostly ported, so most the intricacy
of parsing and writing headers is handled as in the full net/http package.
Run ```go doc -all ./src/net/http``` in TinyGo repo to see full listing.
## Using "crypto/tls" Package
TinyGo's TLS support (crypto/tls) relies on hardware offload of the TLS
protocol. This is different from Go's crypto/tls package which handles the TLS
protocol in software.
TinyGo's TLS support is only available for client applications. You can
http.Get() to an https:// address, but you cannot http.ListenAndServeTLS() an
https server.
The offloading hardware has pre-defined TLS certificates built-in.
## Using Sockets
The Netdever interface is a BSD socket-like interface so an application can make direct
socket calls, bypassing the "net" package for the lowest overhead.
Here is a simple TCP client application using direct sockets:
```go
package main
import (
"net" // only need to parse IP address
"tinygo.org/x/drivers/netdev"
"tinygo.org/x/drivers/netlink"
"tinygo.org/x/drivers/netlink/probe"
)
func main() {
// load network driver for target
link, dev := probe.Probe()
// Connect target to IP network
link.NetConnect(&netlink.ConnectParams{
Ssid: "my SSID",
Passphrase: "my passphrase",
})
// omit error handling
sock, _ := dev.Socket(netdev.AF_INET, netdev.SOCK_STREAM, netdev.IPPROTO_TCP)
dev.Connect(sock, "", net.ParseIP("10.0.0.100"), 8080)
dev.Send(sock, []bytes("hello"), 0, 0)
dev.Close(sock)
link.NetDisconnect()
}
```
+6 -4
View File
@@ -3,10 +3,7 @@
[![PkgGoDev](https://pkg.go.dev/badge/tinygo.org/x/drivers)](https://pkg.go.dev/tinygo.org/x/drivers) [![Build](https://github.com/tinygo-org/drivers/actions/workflows/build.yml/badge.svg?branch=dev)](https://github.com/tinygo-org/drivers/actions/workflows/build.yml)
This package provides a collection of 101 different hardware drivers for devices such as sensors and displays that can be used together with [TinyGo](https://tinygo.org).
For the complete list, please see:
https://tinygo.org/docs/reference/devices/
This package provides a collection of hardware drivers for devices such as sensors and displays that can be used together with [TinyGo](https://tinygo.org).
## Installing
@@ -53,6 +50,11 @@ func main() {
}
```
## Supported devices
There are currently 96 devices supported. For the complete list, please see:
https://tinygo.org/docs/reference/devices/
## Contributing
Your contributions are welcome!
-196
View File
@@ -1,196 +0,0 @@
// Package adafruit4650 implements a driver for the Adafruit FeatherWing OLED - 128x64 OLED display.
// The display is backed itself by a SH1107 driver chip.
//
// Store: https://www.adafruit.com/product/4650
//
// Documentation: https://learn.adafruit.com/adafruit-128x64-oled-featherwing
package adafruit4650
import (
"image/color"
"time"
"tinygo.org/x/drivers"
)
const DefaultAddress = 0x3c
const (
commandSetLowColumn = 0x00
commandSetHighColumn = 0x10
commandSetPage = 0xb0
)
const (
width = 128
height = 64
)
// Device represents an Adafruit 4650 device
type Device struct {
bus drivers.I2C
Address uint8
buffer []byte
width int16
height int16
}
// New creates a new device, not configuring anything yet.
func New(bus drivers.I2C) Device {
return Device{
bus: bus,
Address: DefaultAddress,
width: width,
height: height,
}
}
// Configure initializes the display with default configuration
func (d *Device) Configure() error {
bufferSize := d.width * d.height / 8
d.buffer = make([]byte, bufferSize)
// This sequence is an amalgamation of the datasheet, official Arduino driver, CircuitPython driver and other drivers
initSequence := []byte{
0xae, // display off, sleep mode
//0xd5, 0x41, // set display clock divider (from original datasheet)
0xd5, 0x51, // set display clock divider (from Adafruit driver)
0xd9, 0x22, // pre-charge/dis-charge period mode: 2 DCLKs/2 DCLKs (POR)
0x20, // memory mode
0x81, 0x4f, // contrast setting = 0x4f
0xad, 0x8a, // set dc/dc pump
0xa0, // segment remap, flip-x
0xc0, // common output scan direction
0xdc, 0x00, // set display start line 0 (POR=0)
0xa8, 0x3f, // multiplex ratio, height - 1 = 0x3f
0xd3, 0x60, // set display offset mode = 0x60
0xdb, 0x35, // VCOM deselect level = 0.770 (POR)
0xa4, // entire display off, retain RAM, normal status (POR)
0xa6, // normal (not reversed) display
0xaf, // display on
}
err := d.writeCommands(initSequence)
if err != nil {
return err
}
// recommended in the datasheet, same in other drivers
time.Sleep(100 * time.Millisecond)
return nil
}
// ClearDisplay clears the image buffer as well as the actual display
func (d *Device) ClearDisplay() error {
d.ClearBuffer()
return d.Display()
}
// ClearBuffer clears the buffer
func (d *Device) ClearBuffer() {
bzero(d.buffer)
}
// SetPixel modifies the internal buffer. Since this display has a bit-depth of 1 bit any non-zero
// color component will be treated as 'on', otherwise 'off'.
func (d *Device) SetPixel(x int16, y int16, c color.RGBA) {
if x < 0 || x >= d.width || y < 0 || y >= d.height {
return
}
// RAM layout
// *-----> y
// |
// x| col0 col1 ... col63
// v p0 a0 b0 ..
// a1 b1 ..
// .. .. ..
// a7 b7 ..
// p1 a0 b0
// a1 b1
//
//flip y - so the display orientation matches the silk screen labeling etc.
y = d.height - y - 1
page := x / 8
bytesPerPage := d.height
byteIndex := y + bytesPerPage*page
bit := x % 8
if (c.R | c.G | c.B) != 0 {
d.buffer[byteIndex] |= 1 << uint8(bit)
} else {
d.buffer[byteIndex] &^= 1 << uint8(bit)
}
}
// Display sends the whole buffer to the screen
func (d *Device) Display() error {
bytesPerPage := d.height
pages := (d.width + 7) / 8
for page := int16(0); page < pages; page++ {
err := d.setRAMPosition(uint8(page), 0)
if err != nil {
return err
}
offset := page * bytesPerPage
err = d.writeRAM(d.buffer[offset : offset+bytesPerPage])
if err != nil {
return err
}
}
return nil
}
// setRAMPosition updates the device's current page and column position
func (d *Device) setRAMPosition(page uint8, column uint8) error {
if page > 15 {
panic("page out of bounds")
}
if column > 127 {
panic("column out of bounds")
}
setPage := commandSetPage | (page & 0xF)
lo := column & 0xF
setLowColumn := commandSetLowColumn | lo
hi := (column >> 4) & 0x7
setHighColumn := commandSetHighColumn | hi
cmds := []byte{
setPage,
setLowColumn,
setHighColumn,
}
return d.writeCommands(cmds)
}
// Size returns the current size of the display.
func (d *Device) Size() (w, h int16) {
return d.width, d.height
}
func (d *Device) writeCommands(commands []byte) error {
onlyCommandsFollowing := byte(0x00)
return d.bus.Tx(uint16(d.Address), append([]byte{onlyCommandsFollowing}, commands...), nil)
}
func (d *Device) writeRAM(data []byte) error {
onlyRAMFollowing := byte(0x40)
return d.bus.Tx(uint16(d.Address), append([]byte{onlyRAMFollowing}, data...), nil)
}
func bzero(buf []byte) {
for i := range buf {
buf[i] = 0
}
}
-176
View File
@@ -1,176 +0,0 @@
package adafruit4650
import (
"bytes"
_ "embed"
"encoding/hex"
"fmt"
"image"
"image/color"
"image/draw"
"image/png"
"os"
"testing"
"time"
"tinygo.org/x/drivers"
"tinygo.org/x/tinyfont"
"tinygo.org/x/tinyfont/freemono"
)
//go:embed expected_hello_world.png
var expectedHelloWorld []byte
// mockBus mocks a fake i2c device adafruit4650 display.
// The memory layout assumes that clients set up the device in a particular way and always send complete
// pages to the device buffer.
type mockBus struct {
img draw.Image
line int
addr uint8
currentPage int
currentColumn int
}
func (m *mockBus) Tx(addr uint16, w, r []byte) error {
if addr != uint16(m.addr) {
panic("unexpected address")
}
if r != nil {
panic("mock does not support reads")
}
if w[0] == 0x00 {
if w[1]&0xf0 == 0xb0 {
m.currentPage = int(w[1] & 0x0f)
lo := w[2] & 0x0f
hi := w[2] & 0x07
m.currentColumn = int(hi<<4 | lo)
}
return nil
}
if w[0] != 0x40 {
panic("unexpected first byte: " + hex.EncodeToString(w[0:1]))
}
return m.writeRAM(w[1:])
}
func newMock() *mockBus {
m := image.NewRGBA(image.Rect(0, 0, width, height))
return &mockBus{img: m, addr: DefaultAddress, currentPage: -1, currentColumn: -1}
}
func (m *mockBus) writeRAM(data []byte) error {
// RAM layout
// *-----> y
// |
// x| col0 col1 ... col63
// v p0 a0 b0 ..
// a1 b1 ..
// .. .. ..
// a7 b7 ..
// p1 a0 b0
// a1 b1
//
fmt.Printf("writing page %d\n", m.currentPage)
// assuming entire pages will be written
for x := 0; x < 8; x++ {
for y := 0; y < height; y++ {
col := data[y]
c := color.Black
if col&(1<<x) != 0 {
c = color.White
}
m.img.Set(x+m.currentPage*8, height-y-1, c)
}
}
return nil
}
func (m *mockBus) toImage() *image.RGBA {
container := image.NewRGBA(m.img.Bounds().Inset(-1))
draw.Draw(container, container.Bounds(), image.NewUniform(color.RGBA{G: 255, A: 255}), image.Point{}, draw.Over)
draw.Draw(container, m.img.Bounds(), m.img, image.Point{}, draw.Over)
return container
}
func TestDevice_Display(t *testing.T) {
bus := newMock()
dev := New(bus)
dev.Configure()
drawPlus(&dev)
drawHellowWorld(&dev)
//when
dev.Display()
//then
actual := bus.toImage()
expected, err := png.Decode(bytes.NewReader(expectedHelloWorld))
if err != nil {
panic(err)
}
assertEqualImages(t, actual, expected)
}
func drawPlus(d drivers.Displayer) {
for i := int16(0); i < 128; i++ {
d.SetPixel(i, 32, color.RGBA{R: 1})
}
for i := int16(0); i < 64; i++ {
d.SetPixel(64, i, color.RGBA{R: 1})
}
}
func drawHellowWorld(d drivers.Displayer) {
tinyfont.WriteLine(d, &freemono.Regular9pt7b, 0, 32, "Hello World!", color.RGBA{R: 0xff, G: 0xff, B: 0xff, A: 0xff})
}
func assertEqualImages(t testing.TB, actual, expected image.Image) {
if actual.Bounds().Dx() != expected.Bounds().Dx() || actual.Bounds().Dy() != expected.Bounds().Dy() {
f := writeImage(actual)
t.Fatalf("differing size: was %v, expected %v, saved actual to %s", actual.Bounds(), expected.Bounds(), f)
}
bb := expected.Bounds()
for x := bb.Min.X; x < bb.Max.X; x++ {
for y := bb.Min.Y; y < bb.Max.Y; y++ {
actualBB := actual.Bounds()
if actual.At(x+actualBB.Min.X, y+actualBB.Min.Y) != expected.At(x, y) {
f := writeImage(actual)
t.Fatalf("different pixel at %d/%d: %v != %v, saved actual at %s", x, y, actual.At(x, y), expected.At(x, y), f)
}
}
}
}
func writeImage(img image.Image) string {
fn := fmt.Sprintf("%d.png", time.Now().Unix())
f, err := os.OpenFile(fn, os.O_RDWR|os.O_CREATE, 0644)
if err != nil {
panic(err)
}
defer f.Close()
err = png.Encode(f, img)
if err != nil {
panic(err)
}
return fn
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 449 B

-231
View File
@@ -1,231 +0,0 @@
package apds9930
import (
"errors"
"tinygo.org/x/drivers"
)
var errInvalidParam = errors.New("apds9930: invalid param")
type Dev struct {
bus drivers.I2C
_txerr error
addr uint16
buf [3]byte
}
func New(bus drivers.I2C, addr uint8) Dev {
return Dev{bus: bus, addr: uint16(addr)}
}
// Status contains info on:
//
// AVALID: Indicates that the ALS Ch0/Ch1 channels have completed an integration cycle.
// PSValid. Indicates that the PS has completed an integration cycle.
// AINTL ALS Interrupt. Indicates that the device is asserting an ALS interrupt
// PINT: Proximity Interrupt. Indicates that the device is asserting a proximity interrupt.
// PSAT: Proximity Saturation. Indicates that the proximity measurement is saturated
type Status uint8
func (s Status) ALSAvailable() bool { return s&(1<<0) != 0 } // AVALID
func (s Status) ProximityAvailable() bool { return s&(1<<1) != 0 } // PVALID
func (s Status) HasALSInterrupt() bool { return s&(1<<4) != 0 } // AINT
func (s Status) HasProxInterrupt() bool { return s&(1<<5) != 0 } // PINT
func (s Status) IsProximitySaturated() bool { return s&(1<<6) != 0 } // PSAT
type Enable uint8
const (
EnPower Enable = 1 << iota
EnALS
EnProx
EnWait
EnALSInt
EnProxInt
EnSleepAfterInt
)
// Luminic control gain.
type ALSGain uint8
const (
AGain1 ALSGain = iota
AGain8
AGain16
AGain120
)
type ProxGain uint8
const (
PGain1 ProxGain = iota
PGain2
PGain4
PGain8
)
type Drive uint8
const (
Drive100mA Drive = iota
Drive50mA
Drive25mA
Drive12_5mA
)
type Config struct {
ProxGain ProxGain
ALSGain ALSGain
LEDDrive Drive
}
func (d *Dev) Init(cfg Config) error {
if cfg.LEDDrive > Drive100mA {
return errInvalidParam
}
d.txNew()
d.txWrite8(regENABLE, 0x00) // disable all features.
d.txWrite8(regATIME, 0xee) // set default integration time.
d.txWrite8(regPPULSE, 0x04)
d.txWrite8(regWTIME, 0xee) // set default wait time.
d.txWrite8(regPTIME, 0xff) // set default pulse count.
var ctlval uint8 = 0b10 << 4 // Use Channel 1 diode.
ctlval |= uint8(cfg.LEDDrive&0b11) << 6
ctlval |= uint8(cfg.ProxGain&0b11) << 2
ctlval |= uint8(cfg.ALSGain & 0b11)
d.txWrite8(regCONTROL, ctlval)
return d.txErr()
}
func (d *Dev) Status() (Status, error) {
d.txNew()
v := d.txRead8(regSTATUS)
return Status(v), d.txErr()
}
// Enable sets the ENABLE register used primarily to
// power the APDS-9930 device on/off, enable functions, and interrupts.
// Arguments must be ORed, i.e: d.Enable(EnPower|EnProx); to enable proximity.
func (d *Dev) Enable(en Enable) error {
en &= 0b01111111 // Seventh bit reserved.
d.txNew()
d.txWrite8(regENABLE, uint8(en))
return d.txErr()
}
func (d *Dev) enableLightSensor(withInterrupts bool) error {
return nil
}
func (d *Dev) setAmbientLightGain() {
}
func (d *Dev) EnableProximity() error {
return d.Enable(EnPower | EnALS | EnProx | EnWait)
}
func (d *Dev) proxIntLowThresh() (uint16, error) {
d.txNew()
return d.txRead16(regPILTL), d.txErr()
}
func (d *Dev) setProxIntLowThresh(loThresh uint16) error {
d.txNew()
d.txWrite16(regPILTL, loThresh)
return d.txErr()
}
func (d *Dev) proxIntHighThresh() (uint16, error) {
d.txNew()
val := d.txRead16(regPIHTL)
return val, d.txErr()
}
func (d *Dev) setProxIntHighThresh(hiThresh uint16) error {
d.txNew()
d.txWrite16(regPIHTL, hiThresh)
return d.txErr()
}
func (d *Dev) LEDDrive() (Drive, error) {
d.txNew()
val := (d.txRead8(regCONTROL) >> 6) & 0b11
return Drive(val), d.txErr()
}
// SetLEDDrive drive strength for proximity and ALS
//
// Value LED Current
// 3 100 mA
// 2 50 mA
// 1 25 mA
// 0 12.5 mA
func (d *Dev) SetLEDDrive(drive Drive) error {
if drive > 3 {
return errInvalidParam
}
current, err := d.LEDDrive()
if err != nil {
return err
}
// Replace LED bits in Control register.
current &= 0b00111111
current |= drive << 6
d.txNew()
d.txWrite8(regCONTROL, uint8(current))
return d.txErr()
}
func (d *Dev) proxGain() (uint8, error) {
val := d.txRead8(regCONTROL)
return (val >> 2) & 0b11, d.txErr()
}
// ReadProximity returns a 10-bit value (0..1023), the higher the value the closer the object
func (d *Dev) ReadProximity() uint16 {
d.txNew()
v := d.txRead16(regPDATAL)
if d.txErr() != nil {
return 0
}
return v
}
func (d *Dev) txRead16(addr uint8) uint16 {
if d.txErr() != nil {
return 0
}
d.buf[0] = addr | protoAutoInc
d._txerr = d.bus.Tx(d.addr, d.buf[:1], d.buf[1:3])
return uint16(d.buf[1]) | uint16(d.buf[2])<<8
}
func (d *Dev) txRead8(addr uint8) uint8 {
if d.txErr() != nil {
return 0
}
d.buf[0] = addr | protoAutoInc
d._txerr = d.bus.Tx(d.addr, d.buf[:1], d.buf[1:2])
return d.buf[1]
}
func (d *Dev) txWrite16(addr uint8, val uint16) {
d.txWrite8(addr, uint8(val))
d.txWrite8(addr+1, uint8(val>>8))
}
func (d *Dev) txWrite8(reg uint8, val uint8) {
if d.txErr() != nil {
return
}
d.buf[0] = reg | 0x80
d.buf[1] = val
d._txerr = d.bus.Tx(d.addr, d.buf[:2], nil)
}
func (d *Dev) txNew() { d._txerr = nil }
func (d *Dev) txErr() error { return d._txerr }
-23
View File
@@ -1,23 +0,0 @@
package apds9930
const (
protoAutoInc = 0xA0
)
const (
regENABLE = 0x00
regATIME = 0x01
regPTIME = 0x02
regWTIME = 0x03
regPILTL = 0x08
regPILTH = 0x09
regPIHTL = 0x0A
regPIHTH = 0x0B
regCONFIG = 0x0D
regPPULSE = 0x0E
regCONTROL = 0x0F
regSTATUS = 0x13
regPDATAL = 0x18
regPDATAH = 0x19
regPOFFSET = 0x1E
)
+1 -1
View File
@@ -11,7 +11,7 @@ import (
"tinygo.org/x/drivers"
)
// Device wraps an I2C connection to an AT24CX device.
// Device wraps an I2C connection to a DS3231 device.
type Device struct {
bus drivers.I2C
Address uint16
+1 -1
View File
@@ -49,7 +49,7 @@ func Sleep(duration time.Duration) {
// * The CPU frequency is lower than 256MHz. If it is higher, long sleep
// times (1-16ms) may not work correctly.
cycles := uint32(duration) * (machine.CPUFrequency() / 1000_000) / 1000
slept := C.tinygo_drivers_sleep(C.uint32_t(cycles))
slept := C.tinygo_drivers_sleep(cycles)
if !slept {
// Fallback for platforms without inline assembly support.
time.Sleep(duration)
+20
View File
@@ -0,0 +1,20 @@
package espat
import (
"time"
"tinygo.org/x/drivers/net"
)
func (d *Device) ConnectToAccessPoint(ssid, pass string, timeout time.Duration) error {
if len(ssid) == 0 {
return net.ErrWiFiMissingSSID
}
d.SetWifiMode(WifiModeClient)
return d.ConnectToAP(ssid, pass, int(timeout.Seconds()))
}
func (d *Device) Disconnect() error {
return d.DisconnectFromAP()
}
+34 -297
View File
@@ -15,302 +15,41 @@
//
// AT command set:
// https://www.espressif.com/sites/default/files/documentation/4a-esp8266_at_instruction_set_en.pdf
//
// 02/2023 sfeldma@gmail.com Heavily modified to use netdev interface
package espat // import "tinygo.org/x/drivers/espat"
import (
"errors"
"fmt"
"machine"
"net"
"net/netip"
"strconv"
"strings"
"sync"
"time"
"tinygo.org/x/drivers/netdev"
"tinygo.org/x/drivers/netlink"
"tinygo.org/x/drivers"
"tinygo.org/x/drivers/net"
)
type Config struct {
// UART config
Uart *machine.UART
Tx machine.Pin
Rx machine.Pin
}
type socket struct {
inUse bool
protocol int
laddr netip.AddrPort
}
// Device wraps UART connection to the ESP8266/ESP32.
type Device struct {
cfg *Config
uart *machine.UART
bus drivers.UART
// command responses that come back from the ESP8266/ESP32
response []byte
// data received from a TCP/UDP connection forwarded by the ESP8266/ESP32
data []byte
socket socket
mu sync.Mutex
socketdata []byte
}
func NewDevice(cfg *Config) *Device {
return &Device{
cfg: cfg,
response: make([]byte, 1500),
data: make([]byte, 0, 1500),
}
// ActiveDevice is the currently configured Device in use. There can only be one.
var ActiveDevice *Device
// New returns a new espat driver. Pass in a fully configured UART bus.
func New(b drivers.UART) *Device {
return &Device{bus: b, response: make([]byte, 512), socketdata: make([]byte, 0, 1024)}
}
func (d *Device) NetConnect(params *netlink.ConnectParams) error {
if len(params.Ssid) == 0 {
return netlink.ErrMissingSSID
}
d.uart = d.cfg.Uart
d.uart.Configure(machine.UARTConfig{TX: d.cfg.Tx, RX: d.cfg.Rx})
// Connect to ESP8266/ESP32
fmt.Printf("Connecting to device...")
for i := 0; i < 5; i++ {
if d.Connected() {
break
}
time.Sleep(1 * time.Second)
}
if !d.Connected() {
fmt.Printf("FAILED\r\n")
return netlink.ErrConnectFailed
}
fmt.Printf("CONNECTED\r\n")
// Connect to Wifi AP
fmt.Printf("Connecting to Wifi SSID '%s'...", params.Ssid)
d.SetWifiMode(WifiModeClient)
err := d.ConnectToAP(params.Ssid, params.Passphrase, 10 /* secs */)
if err != nil {
fmt.Printf("FAILED\r\n")
return err
}
fmt.Printf("CONNECTED\r\n")
ip, err := d.Addr()
if err != nil {
return err
}
fmt.Printf("DHCP-assigned IP: %s\r\n", ip)
fmt.Printf("\r\n")
return nil
}
func (d *Device) NetDisconnect() {
d.DisconnectFromAP()
fmt.Printf("\r\nDisconnected from Wifi\r\n\r\n")
}
func (d *Device) NetNotify(cb func(netlink.Event)) {
// Not supported
}
func (d *Device) GetHostByName(name string) (netip.Addr, error) {
ip, err := d.GetDNS(name)
if err != nil {
return netip.Addr{}, err
}
return netip.ParseAddr(ip)
}
func (d *Device) GetHardwareAddr() (net.HardwareAddr, error) {
return net.HardwareAddr{}, netlink.ErrNotSupported
}
func (d *Device) Addr() (netip.Addr, error) {
resp, err := d.GetClientIP()
if err != nil {
return netip.Addr{}, err
}
prefix := "+CIPSTA:ip:"
for _, line := range strings.Split(resp, "\n") {
if ok := strings.HasPrefix(line, prefix); ok {
ip := line[len(prefix)+1 : len(line)-2]
return netip.ParseAddr(ip)
}
}
return netip.Addr{}, fmt.Errorf("Error getting IP address")
}
func (d *Device) Socket(domain int, stype int, protocol int) (int, error) {
switch domain {
case netdev.AF_INET:
default:
return -1, netdev.ErrFamilyNotSupported
}
switch {
case protocol == netdev.IPPROTO_TCP && stype == netdev.SOCK_STREAM:
case protocol == netdev.IPPROTO_TLS && stype == netdev.SOCK_STREAM:
case protocol == netdev.IPPROTO_UDP && stype == netdev.SOCK_DGRAM:
default:
return -1, netdev.ErrProtocolNotSupported
}
// Only supporting single connection mode, so only one socket at a time
if d.socket.inUse {
return -1, netdev.ErrNoMoreSockets
}
d.socket.inUse = true
d.socket.protocol = protocol
return 0, nil
}
func (d *Device) Bind(sockfd int, ip netip.AddrPort) error {
d.socket.laddr = ip
return nil
}
func (d *Device) Connect(sockfd int, host string, ip netip.AddrPort) error {
var err error
var addr = ip.Addr().String()
var rport = strconv.Itoa(int(ip.Port()))
var lport = strconv.Itoa(int(d.socket.laddr.Port()))
switch d.socket.protocol {
case netdev.IPPROTO_TCP:
err = d.ConnectTCPSocket(addr, rport)
case netdev.IPPROTO_UDP:
err = d.ConnectUDPSocket(addr, rport, lport)
case netdev.IPPROTO_TLS:
err = d.ConnectSSLSocket(host, rport)
}
if err != nil {
if host == "" {
return fmt.Errorf("Connect to %s timed out", ip)
} else {
return fmt.Errorf("Connect to %s:%d timed out", host, ip.Port())
}
}
return nil
}
func (d *Device) Listen(sockfd int, backlog int) error {
switch d.socket.protocol {
case netdev.IPPROTO_UDP:
default:
return netdev.ErrProtocolNotSupported
}
return nil
}
func (d *Device) Accept(sockfd int) (int, netip.AddrPort, error) {
return -1, netip.AddrPort{}, netdev.ErrNotSupported
}
func (d *Device) sendChunk(sockfd int, buf []byte, deadline time.Time) (int, error) {
// Check if we've timed out
if !deadline.IsZero() {
if time.Now().After(deadline) {
return -1, netdev.ErrTimeout
}
}
err := d.StartSocketSend(len(buf))
if err != nil {
return -1, err
}
n, err := d.Write(buf)
if err != nil {
return -1, err
}
_, err = d.Response(1000)
if err != nil {
return -1, err
}
return n, err
}
func (d *Device) Send(sockfd int, buf []byte, flags int, deadline time.Time) (int, error) {
d.mu.Lock()
defer d.mu.Unlock()
// Break large bufs into chunks so we don't overrun the hw queue
chunkSize := 1436
for i := 0; i < len(buf); i += chunkSize {
end := i + chunkSize
if end > len(buf) {
end = len(buf)
}
_, err := d.sendChunk(sockfd, buf[i:end], deadline)
if err != nil {
return -1, err
}
}
return len(buf), nil
}
func (d *Device) Recv(sockfd int, buf []byte, flags int, deadline time.Time) (int, error) {
d.mu.Lock()
defer d.mu.Unlock()
var length = len(buf)
// Limit length read size to chunk large read requests
if length > 1436 {
length = 1436
}
for {
// Check if we've timed out
if !deadline.IsZero() {
if time.Now().After(deadline) {
return -1, netdev.ErrTimeout
}
}
n, err := d.ReadSocket(buf[:length])
if err != nil {
return -1, err
}
if n == 0 {
d.mu.Unlock()
time.Sleep(100 * time.Millisecond)
d.mu.Lock()
continue
}
return n, nil
}
}
func (d *Device) Close(sockfd int) error {
d.mu.Lock()
defer d.mu.Unlock()
d.socket.inUse = false
return d.DisconnectSocket()
}
func (d *Device) SetSockOpt(sockfd int, level int, opt int, value interface{}) error {
return netdev.ErrNotSupported
// Configure sets up the device for communication.
func (d Device) Configure() {
ActiveDevice = &d
net.ActiveDevice = ActiveDevice
}
// Connected checks if there is communication with the ESP8266/ESP32.
@@ -318,7 +57,7 @@ func (d *Device) Connected() bool {
d.Execute(Test)
// handle response here, should include "OK"
_, err := d.Response(1000)
_, err := d.Response(100)
if err != nil {
return false
}
@@ -327,12 +66,12 @@ func (d *Device) Connected() bool {
// Write raw bytes to the UART.
func (d *Device) Write(b []byte) (n int, err error) {
return d.uart.Write(b)
return d.bus.Write(b)
}
// Read raw bytes from the UART.
func (d *Device) Read(b []byte) (n int, err error) {
return d.uart.Read(b)
return d.bus.Read(b)
}
// how long in milliseconds to pause after sending AT commands
@@ -361,10 +100,9 @@ func (d Device) Set(cmd, params string) error {
// Version returns the ESP8266/ESP32 firmware version info.
func (d Device) Version() []byte {
d.Execute(Version)
r, err := d.Response(2000)
r, err := d.Response(100)
if err != nil {
//return []byte("unknown")
return []byte(err.Error())
return []byte("unknown")
}
return r
}
@@ -394,16 +132,16 @@ func (d *Device) ReadSocket(b []byte) (n int, err error) {
d.Response(300)
count := len(b)
if len(b) >= len(d.data) {
if len(b) >= len(d.socketdata) {
// copy it all, then clear socket data
count = len(d.data)
copy(b, d.data[:count])
d.data = d.data[:0]
count = len(d.socketdata)
copy(b, d.socketdata[:count])
d.socketdata = d.socketdata[:0]
} else {
// copy all we can, then keep the remaining socket data around
copy(b, d.data[:count])
copy(d.data, d.data[count:])
d.data = d.data[:len(d.data)-count]
copy(b, d.socketdata[:count])
copy(d.socketdata, d.socketdata[count:])
d.socketdata = d.socketdata[:len(d.socketdata)-count]
}
return count, nil
@@ -419,11 +157,11 @@ func (d *Device) Response(timeout int) ([]byte, error) {
retries := timeout / pause
for {
size = d.uart.Buffered()
size = d.bus.Buffered()
if size > 0 {
end += size
d.uart.Read(d.response[start:end])
d.bus.Read(d.response[start:end])
// if "+IPD" then read socket data
if strings.Contains(string(d.response[:end]), "+IPD") {
@@ -466,19 +204,18 @@ func (d *Device) parseIPD(end int) error {
val := string(d.response[s+5 : e])
// TODO: verify count
v, err := strconv.Atoi(val)
_, err := strconv.Atoi(val)
if err != nil {
// not expected data here. what to do?
return err
}
// load up the socket data
//d.data = append(d.data, d.response[e+1:end]...)
d.data = append(d.data, d.response[e+1:e+1+v]...)
d.socketdata = append(d.socketdata, d.response[e+1:end]...)
return nil
}
// IsSocketDataAvailable returns of there is socket data available
func (d *Device) IsSocketDataAvailable() bool {
return len(d.data) > 0 || d.uart.Buffered() > 0
return len(d.socketdata) > 0 || d.bus.Buffered() > 0
}
+1 -1
View File
@@ -51,7 +51,7 @@ func (d *Device) ConnectTCPSocket(addr, port string) error {
// ConnectUDPSocket creates a new UDP connection for the ESP8266/ESP32.
func (d *Device) ConnectUDPSocket(addr, sendport, listenport string) error {
protocol := "UDP"
val := "\"" + protocol + "\",\"" + addr + "\"," + sendport + "," + listenport + ",0"
val := "\"" + protocol + "\",\"" + addr + "\"," + sendport + "," + listenport + ",2"
err := d.Set(TCPConnect, val)
if err != nil {
return err
+4 -1
View File
@@ -44,7 +44,10 @@ func (d *Device) ConnectToAP(ssid, pwd string, ws int) error {
d.Set(ConnectAP, val)
_, err := d.Response(ws * 1000)
return err
if err != nil {
return err
}
return nil
}
// DisconnectFromAP disconnects the ESP8266/ESP32 from the current access point.
-42
View File
@@ -1,42 +0,0 @@
package main
import (
"image/color"
"machine"
"tinygo.org/x/drivers"
"tinygo.org/x/drivers/adafruit4650"
"tinygo.org/x/tinyfont"
"tinygo.org/x/tinyfont/freemono"
)
func main() {
machine.I2C0.Configure(machine.I2CConfig{})
dev := adafruit4650.New(machine.I2C0)
err := dev.Configure()
if err != nil {
panic(err)
}
drawPlus(&dev)
drawHelloWorld(&dev)
err = dev.Display()
if err != nil {
panic(err)
}
}
func drawPlus(d drivers.Displayer) {
for i := int16(0); i < 128; i++ {
d.SetPixel(i, 32, color.RGBA{R: 1})
}
for i := int16(0); i < 64; i++ {
d.SetPixel(64, i, color.RGBA{R: 1})
}
}
func drawHelloWorld(d drivers.Displayer) {
tinyfont.WriteLine(d, &freemono.Regular9pt7b, 0, 32, "Hello World!", color.RGBA{R: 0xff, G: 0xff, B: 0xff, A: 0xff})
}
-43
View File
@@ -1,43 +0,0 @@
package main
import (
"machine"
"time"
"tinygo.org/x/drivers/apds9930"
)
func main() {
// Sleep to catch any errors through the serial monitor.
time.Sleep(1000 * time.Millisecond)
bus := machine.I2C0
// use Nano 33 BLE Sense's internal I2C bus
err := bus.Configure(machine.I2CConfig{
SCL: machine.GP1,
SDA: machine.GP0,
Frequency: 100 * machine.KHz,
})
if err != nil {
panic(err.Error())
}
sensor := apds9930.New(bus, 0x39)
err = sensor.Init(apds9930.Config{})
if err != nil {
panic(err)
}
err = sensor.EnableProximity()
if err != nil {
panic(err)
}
println("proximity enabled!")
for {
stat, _ := sensor.Status()
if !stat.ProximityAvailable() {
time.Sleep(5 * time.Millisecond)
continue
}
prox := sensor.ReadProximity()
println("proximity:", prox)
}
}
+1 -1
View File
@@ -1,4 +1,4 @@
// Connects to an DS3231 I2C Real Time Clock (RTC).
// Connects to an MAG3110 I2C magnetometer.
package main
import (
+145
View File
@@ -0,0 +1,145 @@
// This is a console to a ESP8266/ESP32 running on the device UART1.
// Allows you to type AT commands from your computer via the microcontroller.
//
// In other words:
// Your computer <--> UART0 <--> MCU <--> UART1 <--> ESP8266 <--> INTERNET
//
// More information on the Espressif AT command set at:
// https://www.espressif.com/sites/default/files/documentation/4a-esp8266_at_instruction_set_en.pdf
package main
import (
"machine"
"time"
"tinygo.org/x/drivers/espat"
)
// change actAsAP to true to act as an access point instead of connecting to one.
const actAsAP = false
var (
// access point info
ssid string
pass string
)
// 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 (
uart = machine.UART1
tx = machine.PA22
rx = machine.PA23
console = machine.Serial
adaptor *espat.Device
)
func main() {
uart.Configure(machine.UARTConfig{TX: tx, RX: rx})
// Init esp8266
adaptor = espat.New(uart)
adaptor.Configure()
// first check if connected
if connectToESP() {
println("Connected to wifi adaptor.")
adaptor.Echo(false)
connectToAP()
} else {
println("")
failMessage("Unable to connect to wifi adaptor.")
return
}
println("Type an AT command then press enter:")
prompt()
input := make([]byte, 64)
i := 0
for {
if console.Buffered() > 0 {
data, _ := console.ReadByte()
switch data {
case 13:
// return key
console.Write([]byte("\r\n"))
// send command to ESP8266
input[i] = byte('\r')
input[i+1] = byte('\n')
adaptor.Write(input[:i+2])
// display response
r, _ := adaptor.Response(500)
console.Write(r)
// prompt
prompt()
i = 0
continue
default:
// just echo the character
console.WriteByte(data)
input[i] = data
i++
}
}
time.Sleep(10 * time.Millisecond)
}
}
func prompt() {
print("ESPAT>")
}
// connect to ESP8266/ESP32
func connectToESP() bool {
for i := 0; i < 5; i++ {
println("Connecting to wifi adaptor...")
if adaptor.Connected() {
return true
}
time.Sleep(1 * time.Second)
}
return false
}
// connect to access point
func connectToAP() {
println("Connecting to wifi network '" + ssid + "'")
if err := adaptor.ConnectToAccessPoint(ssid, pass, 10*time.Second); err != nil {
failMessage(err.Error())
}
println("Connected.")
ip, err := adaptor.GetClientIP()
if err != nil {
failMessage(err.Error())
}
println(ip)
}
// provide access point
func provideAP() {
println("Starting wifi network as access point '" + ssid + "'...")
adaptor.SetWifiMode(espat.WifiModeAP)
adaptor.SetAPConfig(ssid, pass, 7, espat.WifiAPSecurityWPA2_PSK)
println("Ready.")
ip, _ := adaptor.GetAPIP()
println(ip)
}
func failMessage(msg string) {
for {
println(msg)
time.Sleep(1 * time.Second)
}
}
+131
View File
@@ -0,0 +1,131 @@
// This is a sensor hub that uses a ESP8266/ESP32 running on the device UART1.
// It creates a UDP "server" you can use to get info to/from your computer via the microcontroller.
//
// In other words:
// Your computer <--> UART0 <--> MCU <--> UART1 <--> ESP8266 <--> INTERNET
package main
import (
"machine"
"time"
"tinygo.org/x/drivers/espat"
"tinygo.org/x/drivers/net"
)
// change actAsAP to true to act as an access point instead of connecting to one.
const actAsAP = false
var (
// access point info
ssid string
pass string
)
// 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 (
uart = machine.UART1
tx = machine.PA22
rx = machine.PA23
adaptor *espat.Device
)
func main() {
uart.Configure(machine.UARTConfig{TX: tx, RX: rx})
// Init esp8266
adaptor = espat.New(uart)
adaptor.Configure()
readyled := machine.LED
readyled.Configure(machine.PinConfig{Mode: machine.PinOutput})
readyled.High()
// first check if connected
if connectToESP() {
println("Connected to wifi adaptor.")
adaptor.Echo(false)
connectToAP()
} else {
println("")
failMessage("Unable to connect to wifi adaptor.")
return
}
// now make UDP connection
laddr := &net.UDPAddr{Port: 2222}
println("Loading UDP listener...")
conn, _ := net.ListenUDP("UDP", laddr)
println("Waiting for data...")
data := make([]byte, 50)
blink := true
for {
n, _ := conn.Read(data)
if n > 0 {
println(string(data[:n]))
conn.Write([]byte("hello back\r\n"))
}
blink = !blink
if blink {
readyled.High()
} else {
readyled.Low()
}
time.Sleep(500 * time.Millisecond)
}
// Right now this code is never reached. Need a way to trigger it...
println("Disconnecting UDP...")
conn.Close()
println("Done.")
}
// connect to ESP8266/ESP32
func connectToESP() bool {
for i := 0; i < 5; i++ {
println("Connecting to wifi adaptor...")
if adaptor.Connected() {
return true
}
time.Sleep(1 * time.Second)
}
return false
}
// connect to access point
func connectToAP() {
println("Connecting to wifi network '" + ssid + "'")
if err := adaptor.ConnectToAccessPoint(ssid, pass, 10*time.Second); err != nil {
failMessage(err.Error())
}
println("Connected.")
ip, err := adaptor.GetClientIP()
if err != nil {
failMessage(err.Error())
}
println(ip)
}
// provide access point
func provideAP() {
println("Starting wifi network as access point '" + ssid + "'...")
adaptor.SetWifiMode(espat.WifiModeAP)
adaptor.SetAPConfig(ssid, pass, 7, espat.WifiAPSecurityWPA2_PSK)
println("Ready.")
ip, _ := adaptor.GetAPIP()
println(ip)
}
func failMessage(msg string) {
for {
println(msg)
time.Sleep(1 * time.Second)
}
}
+110
View File
@@ -0,0 +1,110 @@
// This is a sensor station that uses a ESP8266 or ESP32 running on the device UART1.
// It creates a UDP connection you can use to get info to/from your computer via the microcontroller.
//
// In other words:
// Your computer <--> UART0 <--> MCU <--> UART1 <--> ESP8266
package main
import (
"machine"
"time"
"tinygo.org/x/drivers/espat"
"tinygo.org/x/drivers/net"
)
var (
// access point info
ssid string
pass string
)
// IP address of the listener aka "hub". Replace with your own info.
const hubIP = "0.0.0.0"
// 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 (
uart = machine.UART1
tx = machine.PA22
rx = machine.PA23
adaptor *espat.Device
)
func main() {
uart.Configure(machine.UARTConfig{TX: tx, RX: rx})
// Init esp8266/esp32
adaptor = espat.New(uart)
adaptor.Configure()
// first check if connected
if connectToESP() {
println("Connected to wifi adaptor.")
adaptor.Echo(false)
connectToAP()
} else {
println("")
failMessage("Unable to connect to wifi adaptor.")
return
}
// now make UDP connection
ip := net.ParseIP(hubIP)
raddr := &net.UDPAddr{IP: ip, Port: 2222}
laddr := &net.UDPAddr{Port: 2222}
println("Dialing UDP connection...")
conn, _ := net.DialUDP("udp", laddr, raddr)
for {
// send data
println("Sending data...")
conn.Write([]byte("hello\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.")
}
// connect to ESP8266/ESP32
func connectToESP() bool {
for i := 0; i < 5; i++ {
println("Connecting to wifi adaptor...")
if adaptor.Connected() {
return true
}
time.Sleep(1 * time.Second)
}
return false
}
// connect to access point
func connectToAP() {
println("Connecting to wifi network '" + ssid + "'")
if err := adaptor.ConnectToAccessPoint(ssid, pass, 10*time.Second); err != nil {
failMessage(err.Error())
}
println("Connected.")
ip, err := adaptor.GetClientIP()
if err != nil {
failMessage(err.Error())
}
println(ip)
}
func failMessage(msg string) {
for {
println(msg)
time.Sleep(1 * time.Second)
}
}
+144
View File
@@ -0,0 +1,144 @@
// This is a sensor station that uses a ESP8266 or ESP32 running on the device UART1.
// It creates an MQTT connection that publishes a message every second
// to an MQTT broker.
//
// In other words:
// Your computer <--> UART0 <--> MCU <--> UART1 <--> ESP8266 <--> Internet <--> MQTT broker.
//
// You must install the Paho MQTT package to build this program:
//
// go get -u github.com/eclipse/paho.mqtt.golang
package main
import (
"machine"
"math/rand"
"time"
"tinygo.org/x/drivers/espat"
"tinygo.org/x/drivers/net/mqtt"
)
var (
// access point info
ssid string
pass string
)
// IP address of the MQTT broker to use. Replace with your own info.
const server = "tcp://test.mosquitto.org:1883"
//const server = "ssl://test.mosquitto.org:8883"
// 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 (
uart = machine.UART2
tx = machine.PA22
rx = machine.PA23
console = machine.Serial
adaptor *espat.Device
topic = "tinygo"
)
func main() {
time.Sleep(3000 * time.Millisecond)
uart.Configure(machine.UARTConfig{TX: tx, RX: rx})
rand.Seed(time.Now().UnixNano())
// Init esp8266/esp32
adaptor = espat.New(uart)
adaptor.Configure()
// first check if connected
if connectToESP() {
println("Connected to wifi adaptor.")
adaptor.Echo(false)
connectToAP()
} else {
println("")
failMessage("Unable to connect to wifi adaptor.")
return
}
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())
}
for {
println("Publishing MQTT message...")
data := []byte("{\"e\":[{ \"n\":\"hello\", \"v\":101 }]}")
token := cl.Publish(topic, 0, false, data)
token.Wait()
if token.Error() != nil {
println(token.Error().Error())
}
time.Sleep(1000 * time.Millisecond)
}
// Right now this code is never reached. Need a way to trigger it...
println("Disconnecting MQTT...")
cl.Disconnect(100)
println("Done.")
}
// connect to ESP8266/ESP32
func connectToESP() bool {
for i := 0; i < 5; i++ {
println("Connecting to wifi adaptor...")
if adaptor.Connected() {
return true
}
time.Sleep(1 * time.Second)
}
return false
}
// connect to access point
func connectToAP() {
println("Connecting to wifi network '" + ssid + "'")
if err := adaptor.ConnectToAccessPoint(ssid, pass, 10*time.Second); err != nil {
failMessage(err.Error())
}
println("Connected.")
ip, err := adaptor.GetClientIP()
if err != nil {
failMessage(err.Error())
}
println(ip)
}
// 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)
}
}
+165
View File
@@ -0,0 +1,165 @@
// This is a sensor station that uses a ESP8266 or ESP32 running on the device UART1.
// It creates an MQTT connection that publishes a message every second
// to an MQTT broker.
//
// In other words:
// Your computer <--> UART0 <--> MCU <--> UART1 <--> ESP8266 <--> Internet <--> MQTT broker.
//
// You must also install the Paho MQTT package to build this program:
//
// go get -u github.com/eclipse/paho.mqtt.golang
package main
import (
"fmt"
"machine"
"math/rand"
"time"
"tinygo.org/x/drivers/espat"
"tinygo.org/x/drivers/net/mqtt"
)
var (
// access point info
ssid string
pass string
)
// IP address of the MQTT broker to use. Replace with your own info.
//const server = "tcp://test.mosquitto.org:1883"
const server = "ssl://test.mosquitto.org:8883"
// change these to connect to a different UART or pins for the ESP8266/ESP32
var (
// these are defaults for the Arduino Nano33 IoT.
uart = machine.UART1
tx = machine.PA22
rx = machine.PA23
console = machine.Serial
adaptor *espat.Device
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 main() {
time.Sleep(3000 * time.Millisecond)
uart.Configure(machine.UARTConfig{TX: tx, RX: rx})
rand.Seed(time.Now().UnixNano())
// Init esp8266/esp32
adaptor = espat.New(uart)
adaptor.Configure()
// first check if connected
if connectToESP() {
println("Connected to wifi adaptor.")
adaptor.Echo(false)
connectToAP()
} else {
println("")
failMessage("Unable to connect to wifi adaptor.")
return
}
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.")
}
func publishing() {
for {
println("Publishing MQTT message...")
data := []byte("{\"e\":[{ \"n\":\"hello\", \"v\":101 }]}")
token := cl.Publish(topicTx, 0, false, data)
token.Wait()
if token.Error() != nil {
println(token.Error().Error())
}
time.Sleep(1000 * time.Millisecond)
}
}
// connect to ESP8266/ESP32
func connectToESP() bool {
for i := 0; i < 5; i++ {
println("Connecting to wifi adaptor...")
if adaptor.Connected() {
return true
}
time.Sleep(1 * time.Second)
}
return false
}
// connect to access point
func connectToAP() {
println("Connecting to wifi network '" + ssid + "'")
if err := adaptor.ConnectToAccessPoint(ssid, pass, 10*time.Second); err != nil {
failMessage(err.Error())
}
println("Connected.")
ip, err := adaptor.GetClientIP()
if err != nil {
failMessage(err.Error())
}
println(ip)
}
// 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)
}
}
+113
View File
@@ -0,0 +1,113 @@
// This is a sensor station that uses a ESP8266 or ESP32 running on the device UART1.
// It creates a UDP connection you can use to get info to/from your computer via the microcontroller.
//
// In other words:
// Your computer <--> UART0 <--> MCU <--> UART1 <--> ESP8266
package main
import (
"machine"
"time"
"tinygo.org/x/drivers/espat"
"tinygo.org/x/drivers/net"
)
var (
// access point info
ssid string
pass string
)
// IP address of the server aka "hub". Replace with your own info.
const serverIP = "0.0.0.0"
// 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 (
uart = machine.UART1
tx = machine.PA22
rx = machine.PA23
adaptor *espat.Device
)
func main() {
uart.Configure(machine.UARTConfig{TX: tx, RX: rx})
// Init esp8266/esp32
adaptor = espat.New(uart)
adaptor.Configure()
// first check if connected
if connectToESP() {
println("Connected to wifi adaptor.")
adaptor.Echo(false)
connectToAP()
} else {
println("")
failMessage("Unable to connect to wifi adaptor.")
return
}
// now make TCP connection
ip := net.ParseIP(serverIP)
raddr := &net.TCPAddr{IP: ip, Port: 8080}
laddr := &net.TCPAddr{Port: 8080}
println("Dialing TCP connection...")
conn, err := net.DialTCP("tcp", laddr, raddr)
if err != nil {
failMessage(err.Error())
}
for {
// send data
println("Sending data...")
conn.Write([]byte("hello\r\n"))
time.Sleep(1000 * time.Millisecond)
}
// Right now this code is never reached. Need a way to trigger it...
println("Disconnecting TCP...")
conn.Close()
println("Done.")
}
// connect to ESP8266/ESP32
func connectToESP() bool {
for i := 0; i < 5; i++ {
println("Connecting to wifi adaptor...")
if adaptor.Connected() {
return true
}
time.Sleep(1 * time.Second)
}
return false
}
// connect to access point
func connectToAP() {
println("Connecting to wifi network '" + ssid + "'")
if err := adaptor.ConnectToAccessPoint(ssid, pass, 10*time.Second); err != nil {
failMessage(err.Error())
}
println("Connected.")
ip, err := adaptor.GetClientIP()
if err != nil {
failMessage(err.Error())
}
println(ip)
}
func failMessage(msg string) {
for {
println(msg)
time.Sleep(1 * time.Second)
}
}
+22 -23
View File
@@ -8,16 +8,15 @@ import (
"tinygo.org/x/drivers/examples/ili9341/initdisplay"
"tinygo.org/x/drivers/examples/ili9341/pyportal_boing/graphics"
"tinygo.org/x/drivers/ili9341"
"tinygo.org/x/drivers/pixel"
)
const (
BGCOLOR = pixel.RGB565BE(0x75AD)
GRIDCOLOR = pixel.RGB565BE(0x15A8)
BGSHADOW = pixel.RGB565BE(0x8552)
GRIDSHADOW = pixel.RGB565BE(0x0C60)
RED = pixel.RGB565BE(0x00F8)
WHITE = pixel.RGB565BE(0xFFFF)
BGCOLOR = 0xAD75
GRIDCOLOR = 0xA815
BGSHADOW = 0x5285
GRIDSHADOW = 0x600C
RED = 0xF800
WHITE = 0xFFFF
YBOTTOM = 123 // Ball Y coord at bottom
YBOUNCE = -3.5 // Upward velocity on ball bounce
@@ -26,7 +25,7 @@ const (
)
var (
frameBuffer = pixel.NewImage[pixel.RGB565BE](graphics.BALLWIDTH+8, graphics.BALLHEIGHT+8)
frameBuffer = [(graphics.BALLHEIGHT + 8) * (graphics.BALLWIDTH + 8) * 2]uint8{}
startTime int64
frame int64
@@ -42,7 +41,7 @@ var (
balloldy float32
// Color table for ball rotation effect
palette [16]pixel.RGB565BE
palette [16]uint16
)
var (
@@ -109,7 +108,6 @@ func main() {
width = maxx - minx + 1
height = maxy - miny + 1
buffer := frameBuffer.Rescale(int(width), int(height))
// Ball animation frame # is incremented opposite the ball's X velocity
ballframe -= ballvx * 0.5
@@ -130,7 +128,7 @@ func main() {
}
// Only the changed rectangle is drawn into the 'renderbuf' array...
var c pixel.RGB565BE //, *destPtr;
var c uint16 //, *destPtr;
bx := minx - int16(ballx) // X relative to ball bitmap (can be negative)
by := miny - int16(bally) // Y relative to ball bitmap (can be negative)
bgx := minx // X relative to background bitmap (>= 0)
@@ -151,20 +149,19 @@ func main() {
(by >= 0) && (by < graphics.BALLHEIGHT) { // inside the ball bitmap area?
// Yes, do ball compositing math...
p = graphics.Ball[int(by*(graphics.BALLWIDTH/2))+int(bx1/2)] // Get packed value (2 pixels)
var nibble uint8
if (bx1 & 1) != 0 {
nibble = p & 0xF
c = uint16(p & 0xF)
} else {
nibble = p >> 4
c = uint16(p >> 4)
} // Unpack high or low nybble
if nibble == 0 { // Outside ball - just draw grid
if c == 0 { // Outside ball - just draw grid
if graphics.Background[bgidx]&(0x80>>(bgx1&7)) != 0 {
c = GRIDCOLOR
} else {
c = BGCOLOR
}
} else if nibble > 1 { // In ball area...
c = palette[nibble]
} else if c > 1 { // In ball area...
c = palette[c]
} else { // In shadow area...
if graphics.Background[bgidx]&(0x80>>(bgx1&7)) != 0 {
c = GRIDSHADOW
@@ -179,7 +176,8 @@ func main() {
c = BGCOLOR
}
}
buffer.Set(x, y, 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 +188,7 @@ func main() {
bgy++
}
display.DrawBitmap(minx, miny, buffer)
display.DrawRGBBitmap8(minx, miny, frameBuffer[:width*height*2], width, height)
// Show approximate frame rate
frame++
@@ -207,7 +205,6 @@ func DrawBackground() {
w, h := display.Size()
byteWidth := (w + 7) / 8 // Bitmap scanline pad = whole byte
var b uint8
buffer := frameBuffer.Rescale(int(w), 1)
for j := int16(0); j < h; j++ {
for k := int16(0); k < w; k++ {
if k&7 > 0 {
@@ -216,11 +213,13 @@ func DrawBackground() {
b = graphics.Background[j*byteWidth+k/8]
}
if b&0x80 == 0 {
buffer.Set(int(k), 0, BGCOLOR)
frameBuffer[2*k] = byte(BGCOLOR >> 8)
frameBuffer[2*k+1] = byte(BGCOLOR & 0xFF)
} else {
buffer.Set(int(k), 0, GRIDCOLOR)
frameBuffer[2*k] = byte(GRIDCOLOR >> 8)
frameBuffer[2*k+1] = byte(GRIDCOLOR & 0xFF)
}
}
display.DrawBitmap(0, j, buffer)
display.DrawRGBBitmap8(0, j, frameBuffer[0:w*2], w, 1)
}
}
+4 -4
View File
@@ -23,20 +23,20 @@ Builds/flashes atcmd console application with simulator instead of actual LoRa r
tinygo flash -target pico ./examples/lora/lorawan/atcmd/
```
## PyBadge with LoRa Featherwing for EU868 region
## PyBadge with LoRa Featherwing
Builds/flashes atcmd console application on PyBadge using LoRa Featherwing (RFM95/SX1276).
```
tinygo flash -target pybadge -tags featherwing -ldflags="-X main.reg=EU868" ./examples/lora/lorawan/atcmd/
tinygo flash -target pybadge -tags featherwing ./examples/lora/lorawan/atcmd/
```
## LoRa-E5 for US915 region
## LoRa-E5
Builds/flashes atcmd console application on Lora-E5 using onboard SX126x.
```
tinygo flash -target lorae5 -ldflags="-X main.reg=US915" ./examples/lora/lorawan/atcmd/
tinygo flash -target lorae5 ./examples/lora/lorawan/atcmd/
```
## Joining a Public Lorawan Network
+1 -12
View File
@@ -32,8 +32,6 @@ var (
defaultTimeout uint32 = 1000
)
var reg string
func main() {
uart.Configure(machine.UARTConfig{TX: tx, RX: rx})
@@ -47,16 +45,7 @@ func main() {
otaa = &lorawan.Otaa{}
lorawan.UseRadio(radio)
switch reg {
case "AU915":
lorawan.UseRegionSettings(region.AU915())
case "EU868":
lorawan.UseRegionSettings(region.EU868())
case "US915":
lorawan.UseRegionSettings(region.US915())
default:
lorawan.UseRegionSettings(region.EU868())
}
lorawan.UseRegionSettings(region.EU868())
for {
if uart.Buffered() > 0 {
+4 -4
View File
@@ -21,16 +21,16 @@ loraConnect: Connected !
tinygo flash -target pico ./examples/lora/lorawan/basic-demo
```
## PyBadge with LoRa Featherwing for EU868 region
## PyBadge with LoRa Featherwing
```
tinygo flash -target pybadge -tags featherwing -ldflags="-X main.reg=EU868" ./examples/lora/lorawan/basic-demo
tinygo flash -target pybadge -tags featherwing ./examples/lora/lorawan/basic-demo
```
## LoRa-E5 for US915 region
## LoRa-E5
```
tinygo flash -target lorae5 -ldflags="-X main.reg=US915" ./examples/lora/lorawan/basic-demo
tinygo flash -target lorae5 ./examples/lora/lorawan/basic-demo
```
+3 -14
View File
@@ -12,10 +12,7 @@ import (
"tinygo.org/x/drivers/lora/lorawan/region"
)
var (
reg string
debug string
)
var debug string
const (
LORAWAN_JOIN_TIMEOUT_SEC = 180
@@ -70,16 +67,8 @@ func main() {
// Connect the lorawan with the Lora Radio device.
lorawan.UseRadio(radio)
switch reg {
case "AU915":
lorawan.UseRegionSettings(region.AU915())
case "EU868":
lorawan.UseRegionSettings(region.EU868())
case "US915":
lorawan.UseRegionSettings(region.US915())
default:
lorawan.UseRegionSettings(region.EU868())
}
lorawan.UseRegionSettings(region.EU868())
// Configure AppEUI, DevEUI, APPKey, and public/private Lorawan Network
setLorawanKeys()
+7 -12
View File
@@ -23,18 +23,13 @@ func (sr *SimLoraRadio) Rx(timeoutMs uint32) ([]uint8, error) {
return nil, nil
}
func (sr *SimLoraRadio) SetFrequency(freq uint32) {}
func (sr *SimLoraRadio) SetIqMode(mode uint8) {}
func (sr *SimLoraRadio) SetCodingRate(cr uint8) {}
func (sr *SimLoraRadio) SetBandwidth(bw uint8) {}
func (sr *SimLoraRadio) SetCrc(enable bool) {}
func (sr *SimLoraRadio) SetSpreadingFactor(sf uint8) {}
func (sr *SimLoraRadio) SetHeaderType(headerType uint8) {}
func (sr *SimLoraRadio) SetPreambleLength(pLen uint16) {}
func (sr *SimLoraRadio) SetPublicNetwork(enabled bool) {}
func (sr *SimLoraRadio) SetSyncWord(syncWord uint16) {}
func (sr *SimLoraRadio) SetTxPower(txPower int8) {}
func (sr *SimLoraRadio) LoraConfig(cnf lora.Config) {}
func (sr *SimLoraRadio) SetFrequency(freq uint32) {}
func (sr *SimLoraRadio) SetIqMode(mode uint8) {}
func (sr *SimLoraRadio) SetCodingRate(cr uint8) {}
func (sr *SimLoraRadio) SetBandwidth(bw uint8) {}
func (sr *SimLoraRadio) SetCrc(enable bool) {}
func (sr *SimLoraRadio) SetSpreadingFactor(sf uint8) {}
func (sr *SimLoraRadio) LoraConfig(cnf lora.Config) {}
func FirmwareVersion() string {
return "simulator " + CurrentVersion()
-88
View File
@@ -1,88 +0,0 @@
// This example gets an URL using http.Get(). URL scheme can be http or https.
//
// Note: It may be necessary to increase the stack size when using "net/http".
// Use the -stack-size=4KB command line option.
//
// Some targets (Arduino Nano33 IoT) don't have enough SRAM to run http.Get().
// Use the following for those targets:
//
// examples/net/webclient (for HTTP)
// examples/net/tlsclient (for HTTPS)
//go:build pyportal || nano_rp2040 || metro_m4_airlift || arduino_mkrwifi1010 || matrixportal_m4 || wioterminal
package main
import (
"fmt"
"io"
"log"
"machine"
"net/http"
"net/url"
"strings"
"time"
"tinygo.org/x/drivers/netlink"
"tinygo.org/x/drivers/netlink/probe"
)
var (
ssid string
pass string
)
func main() {
waitSerial()
link, _ := probe.Probe()
err := link.NetConnect(&netlink.ConnectParams{
Ssid: ssid,
Passphrase: pass,
})
if err != nil {
log.Fatal(err)
}
name := "John Doe"
occupation := "gardener"
params := "name=" + url.QueryEscape(name) + "&" +
"occupation=" + url.QueryEscape(occupation)
path := fmt.Sprintf("https://httpbin.org/get?%s", params)
cnt := 0
for {
fmt.Printf("Getting %s\r\n\r\n", path)
resp, err := http.Get(path)
if err != nil {
fmt.Printf("%s\r\n", err.Error())
time.Sleep(10 * time.Second)
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")
body, err := io.ReadAll(resp.Body)
println(string(body))
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)
}
}
-68
View File
@@ -1,68 +0,0 @@
// This example gets an URL using http.Head(). URL scheme can be http or https.
//
// Note: It may be necessary to increase the stack size when using "net/http".
// Use the -stack-size=4KB command line option.
//
// Some targets (Arduino Nano33 IoT) don't have enough SRAM to run http.Head().
// Use the following for those targets:
//
// examples/net/webclient (for HTTP)
// examples/net/tlsclient (for HTTPS)
//go:build pyportal || nano_rp2040 || metro_m4_airlift || arduino_mkrwifi1010 || matrixportal_m4 || wioterminal
package main
import (
"bytes"
"fmt"
"log"
"machine"
"net/http"
"time"
"tinygo.org/x/drivers/netlink"
"tinygo.org/x/drivers/netlink/probe"
)
var (
ssid string
pass string
url string = "https://httpbin.org"
)
func main() {
waitSerial()
link, _ := probe.Probe()
err := link.NetConnect(&netlink.ConnectParams{
Ssid: ssid,
Passphrase: pass,
})
if err != nil {
log.Fatal(err)
}
resp, err := http.Head(url)
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
var buf bytes.Buffer
if err := resp.Write(&buf); err != nil {
log.Fatal(err)
}
fmt.Println(string(buf.Bytes()))
link.NetDisconnect()
}
// Wait for user to open serial console
func waitSerial() {
for !machine.Serial.DTR() {
time.Sleep(100 * time.Millisecond)
}
}
-72
View File
@@ -1,72 +0,0 @@
// This example posts an URL using http.Post(). URL scheme can be http or https.
//
// Note: It may be necessary to increase the stack size when using "net/http".
// Use the -stack-size=4KB command line option.
//
// Some targets (Arduino Nano33 IoT) don't have enough SRAM to run http.Post().
// Use the following for those targets:
//
// examples/net/webclient (for HTTP)
// examples/net/tlsclient (for HTTPS)
//go:build pyportal || nano_rp2040 || metro_m4_airlift || arduino_mkrwifi1010 || matrixportal_m4 || wioterminal
package main
import (
"bytes"
"fmt"
"io"
"log"
"machine"
"net/http"
"time"
"tinygo.org/x/drivers/netlink"
"tinygo.org/x/drivers/netlink/probe"
)
var (
ssid string
pass string
)
func main() {
waitSerial()
link, _ := probe.Probe()
err := link.NetConnect(&netlink.ConnectParams{
Ssid: ssid,
Passphrase: pass,
})
if err != nil {
log.Fatal(err)
}
path := "https://httpbin.org/post"
data := []byte("{\"name\":\"John Doe\",\"occupation\":\"gardener\"}")
resp, err := http.Post(path, "application/json", bytes.NewBuffer(data))
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
log.Fatal(err)
}
fmt.Println(string(body))
link.NetDisconnect()
}
// Wait for user to open serial console
func waitSerial() {
for !machine.Serial.DTR() {
time.Sleep(100 * time.Millisecond)
}
}
-73
View File
@@ -1,73 +0,0 @@
// This example posts an URL using http.PostForm(). URL scheme can be http or https.
//
// Note: It may be necessary to increase the stack size when using "net/http".
// Use the -stack-size=4KB command line option.
//
// Some targets (Arduino Nano33 IoT) don't have enough SRAM to run
// http.PostForm(). Use the following for those targets:
//
// examples/net/webclient (for HTTP)
// examples/net/tlsclient (for HTTPS)
//go:build pyportal || nano_rp2040 || metro_m4_airlift || arduino_mkrwifi1010 || matrixportal_m4 || wioterminal
package main
import (
"fmt"
"io"
"log"
"machine"
"net/http"
"net/url"
"time"
"tinygo.org/x/drivers/netlink"
"tinygo.org/x/drivers/netlink/probe"
)
var (
ssid string
pass string
)
func main() {
waitSerial()
link, _ := probe.Probe()
err := link.NetConnect(&netlink.ConnectParams{
Ssid: ssid,
Passphrase: pass,
})
if err != nil {
log.Fatal(err)
}
path := "https://httpbin.org/post"
data := url.Values{
"name": {"John Doe"},
"occupation": {"gardener"},
}
resp, err := http.PostForm(path, data)
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
log.Fatal(err)
}
fmt.Println(string(body))
}
// Wait for user to open serial console
func waitSerial() {
for !machine.Serial.DTR() {
time.Sleep(100 * time.Millisecond)
}
}
-146
View File
@@ -1,146 +0,0 @@
// This example is an MQTT client built with the natiu-mqtt package. It sends
// machine.CPUFrequency() readings to the broker every second for 10 seconds.
//
// Note: It may be necessary to increase the stack size when using
// paho.mqtt.golang. Use the -stack-size=4KB command line option.
//go:build pyportal || nano_rp2040 || metro_m4_airlift || arduino_mkrwifi1010 || matrixportal_m4 || wioterminal || challenger_rp2040
package main
import (
"context"
"errors"
"fmt"
"io"
"log"
"machine"
"math/rand"
"net"
"time"
mqtt "github.com/soypat/natiu-mqtt"
"tinygo.org/x/drivers/netlink"
"tinygo.org/x/drivers/netlink/probe"
)
var (
ssid string
pass string
broker string = "test.mosquitto.org:1883"
topic string = "cpu/freq"
)
func main() {
waitSerial()
link, _ := probe.Probe()
err := link.NetConnect(&netlink.ConnectParams{
Ssid: ssid,
Passphrase: pass,
})
if err != nil {
log.Fatal(err)
}
clientId := "tinygo-client-" + randomString(10)
fmt.Printf("ClientId: %s\n", clientId)
// Get a transport for MQTT packets
fmt.Printf("Connecting to MQTT broker at %s\n", broker)
conn, err := net.Dial("tcp", broker)
if err != nil {
log.Fatal(err)
}
defer conn.Close()
// Create new client
client := mqtt.NewClient(mqtt.ClientConfig{
Decoder: mqtt.DecoderNoAlloc{make([]byte, 1500)},
OnPub: func(_ mqtt.Header, _ mqtt.VariablesPublish, r io.Reader) error {
message, _ := io.ReadAll(r)
fmt.Printf("Message %s received on topic %s\n", string(message), topic)
return nil
},
})
// Connect client
var varconn mqtt.VariablesConnect
varconn.SetDefaultMQTT([]byte(clientId))
ctx, _ := context.WithTimeout(context.Background(), 10*time.Second)
err = client.Connect(ctx, conn, &varconn)
if err != nil {
log.Fatal("failed to connect: ", err)
}
// Subscribe to topic
ctx, _ = context.WithTimeout(context.Background(), 10*time.Second)
err = client.Subscribe(ctx, mqtt.VariablesSubscribe{
PacketIdentifier: 23,
TopicFilters: []mqtt.SubscribeRequest{
{TopicFilter: []byte(topic), QoS: mqtt.QoS0},
},
})
if err != nil {
log.Fatal("failed to subscribe to", topic, err)
}
fmt.Printf("Subscribed to topic %s\n", topic)
// Publish on topic
pubFlags, _ := mqtt.NewPublishFlags(mqtt.QoS0, false, false)
pubVar := mqtt.VariablesPublish{
TopicName: []byte(topic),
}
for i := 0; i < 10; i++ {
if !client.IsConnected() {
log.Fatal("client disconnected: ", client.Err())
}
freq := float32(machine.CPUFrequency()) / 1000000
payload := fmt.Sprintf("%.02fMhz", freq)
pubVar.PacketIdentifier++
err = client.PublishPayload(pubFlags, pubVar, []byte(payload))
if err != nil {
log.Fatal("error transmitting message: ", err)
}
time.Sleep(time.Second)
conn.SetReadDeadline(time.Now().Add(10 * time.Second))
err = client.HandleNext()
if err != nil {
log.Fatal("handle next: ", err)
}
}
client.Disconnect(errors.New("disconnected gracefully"))
for {
select {}
}
}
// 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)
}
// Wait for user to open serial console
func waitSerial() {
for !machine.Serial.DTR() {
time.Sleep(100 * time.Millisecond)
}
}
-114
View File
@@ -1,114 +0,0 @@
// This example is an MQTT client built with the paho-mqtt package. It sends
// machine.CPUFrequency() readings to the broker every second for 10 seconds.
//
// Note: It may be necessary to increase the stack size when using
// paho.mqtt.golang. Use the -stack-size=4KB command line option.
//go:build pyportal || nano_rp2040 || metro_m4_airlift || arduino_mkrwifi1010 || matrixportal_m4 || wioterminal || challenger_rp2040
package main
import (
"fmt"
"log"
"machine"
"math/rand"
"time"
mqtt "github.com/eclipse/paho.mqtt.golang"
"tinygo.org/x/drivers/netlink"
"tinygo.org/x/drivers/netlink/probe"
)
var (
ssid string
pass string
broker string = "tcp://test.mosquitto.org:1883"
)
var messagePubHandler mqtt.MessageHandler = func(client mqtt.Client, msg mqtt.Message) {
fmt.Printf("Message %s received on topic %s\n", msg.Payload(), msg.Topic())
}
var connectHandler mqtt.OnConnectHandler = func(client mqtt.Client) {
fmt.Println("Connected")
}
var connectionLostHandler mqtt.ConnectionLostHandler = func(client mqtt.Client, err error) {
fmt.Printf("Connection Lost: %s\n", err.Error())
}
func main() {
waitSerial()
link, _ := probe.Probe()
err := link.NetConnect(&netlink.ConnectParams{
Ssid: ssid,
Passphrase: pass,
})
if err != nil {
log.Fatal(err)
}
clientId := "tinygo-client-" + randomString(10)
fmt.Printf("ClientId: %s\n", clientId)
options := mqtt.NewClientOptions()
options.AddBroker(broker)
options.SetClientID(clientId)
options.SetDefaultPublishHandler(messagePubHandler)
options.OnConnect = connectHandler
options.OnConnectionLost = connectionLostHandler
fmt.Printf("Connecting to MQTT broker at %s\n", broker)
client := mqtt.NewClient(options)
token := client.Connect()
if token.Wait() && token.Error() != nil {
panic(token.Error())
}
topic := "cpu/freq"
token = client.Subscribe(topic, 1, nil)
if token.Wait() && token.Error() != nil {
panic(token.Error())
}
fmt.Printf("Subscribed to topic %s\n", topic)
for i := 0; i < 10; i++ {
freq := float32(machine.CPUFrequency()) / 1000000
payload := fmt.Sprintf("%.02fMhz", freq)
token = client.Publish(topic, 0, false, payload)
if token.Wait() && token.Error() != nil {
panic(token.Error())
}
time.Sleep(time.Second)
}
client.Disconnect(100)
for {
select {}
}
}
// 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)
}
// Wait for user to open serial console
func waitSerial() {
for !machine.Serial.DTR() {
time.Sleep(100 * time.Millisecond)
}
}
-115
View File
@@ -1,115 +0,0 @@
// This is an example of an NTP client.
//
// It creates a UDP connection to request the current time and parse the
// response from a NTP server. The system time is set to NTP time.
//go:build pyportal || arduino_nano33 || nano_rp2040 || metro_m4_airlift || arduino_mkrwifi1010 || matrixportal_m4 || wioterminal || challenger_rp2040
package main
import (
"fmt"
"io"
"log"
"machine"
"net"
"runtime"
"time"
"tinygo.org/x/drivers/netlink"
"tinygo.org/x/drivers/netlink/probe"
)
var (
ssid string
pass string
// IP address of the server aka "hub". Replace with your own info.
ntpHost string = "0.pool.ntp.org:123"
)
const NTP_PACKET_SIZE = 48
var response = make([]byte, NTP_PACKET_SIZE)
func main() {
waitSerial()
link, _ := probe.Probe()
err := link.NetConnect(&netlink.ConnectParams{
Ssid: ssid,
Passphrase: pass,
})
if err != nil {
log.Fatal(err)
}
conn, err := net.Dial("udp", ntpHost)
if err != nil {
log.Fatal(err)
}
println("Requesting NTP time...")
t, err := getCurrentTime(conn)
if err != nil {
log.Fatal(fmt.Sprintf("Error getting current time: %v", err))
} else {
message("NTP time: %v", t)
}
conn.Close()
link.NetDisconnect()
runtime.AdjustTimeOffset(-1 * int64(time.Since(t)))
for {
message("Current time: %v", time.Now())
time.Sleep(time.Minute)
}
}
// Wait for user to open serial console
func waitSerial() {
for !machine.Serial.DTR() {
time.Sleep(100 * time.Millisecond)
}
}
func getCurrentTime(conn net.Conn) (time.Time, error) {
if err := sendNTPpacket(conn); err != nil {
return time.Time{}, err
}
n, err := conn.Read(response)
if err != nil && err != io.EOF {
return time.Time{}, err
}
if n != NTP_PACKET_SIZE {
return time.Time{}, fmt.Errorf("expected NTP packet size of %d: %d", NTP_PACKET_SIZE, n)
}
return parseNTPpacket(response), nil
}
func sendNTPpacket(conn net.Conn) error {
var request = [48]byte{
0xe3,
}
_, err := conn.Write(request[:])
return err
}
func parseNTPpacket(r []byte) 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(r[40])<<24 | uint32(r[41])<<16 | uint32(r[42])<<8 | uint32(r[43])
const seventyYears = 2208988800
return time.Unix(int64(t-seventyYears), 0)
}
func message(format string, args ...interface{}) {
println(fmt.Sprintf(format, args...), "\r")
}
-108
View File
@@ -1,108 +0,0 @@
// This example opens a TCP connection and sends some data using netdev sockets.
//
// You can open a server to accept connections from this program using:
//
// nc -lk 8080
//go:build pyportal || arduino_nano33 || nano_rp2040 || metro_m4_airlift || arduino_mkrwifi1010 || matrixportal_m4 || wioterminal || challenger_rp2040
package main
import (
"bytes"
"fmt"
"log"
"machine"
"net/netip"
"time"
"tinygo.org/x/drivers/netdev"
"tinygo.org/x/drivers/netlink"
"tinygo.org/x/drivers/netlink/probe"
)
var (
ssid string
pass string
addr string = "10.0.0.100:8080"
)
var buf = &bytes.Buffer{}
var link netlink.Netlinker
var dev netdev.Netdever
func main() {
waitSerial()
link, dev = probe.Probe()
err := link.NetConnect(&netlink.ConnectParams{
Ssid: ssid,
Passphrase: pass,
})
if err != nil {
log.Fatal(err)
}
for {
sendBatch()
time.Sleep(500 * time.Millisecond)
}
}
func sendBatch() {
addrPort, _ := netip.ParseAddrPort(addr)
// make TCP connection
message("---------------\r\nDialing TCP connection")
fd, _ := dev.Socket(netdev.AF_INET, netdev.SOCK_STREAM, netdev.IPPROTO_TCP)
err := dev.Connect(fd, "", addrPort)
for ; err != nil; err = dev.Connect(fd, "", addrPort) {
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 = dev.Send(fd, buf.Bytes(), 0, time.Time{}); err != nil {
println("error:", err.Error(), "\r")
break
}
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 := dev.Send(fd, buf.Bytes(), 0, time.Time{}); err != nil {
println("error:", err.Error(), "\r")
}
println("Disconnecting TCP...")
dev.Close(fd)
}
func message(msg string) {
println(msg, "\r")
}
// Wait for user to open serial console
func waitSerial() {
for !machine.Serial.DTR() {
time.Sleep(100 * time.Millisecond)
}
}
-70
View File
@@ -1,70 +0,0 @@
// This example listens on port :8080 for client connections. Bytes
// received from the client are echo'ed back to the client. Multiple
// clients can connect as the same time, each consuming a client socket,
// and being serviced by it's own go func.
//
// Example test using nc as client to copy file:
//
// $ nc 10.0.0.2 8080 <file >copy ; cmp file copy
//go:build pyportal || arduino_nano33 || nano_rp2040 || metro_m4_airlift || arduino_mkrwifi1010 || matrixportal_m4 || wioterminal
package main
import (
"io"
"log"
"net"
"time"
"tinygo.org/x/drivers/netlink"
"tinygo.org/x/drivers/netlink/probe"
)
var (
ssid string
pass string
port string = ":8080"
)
var buf [1024]byte
func echo(conn net.Conn) {
println("Client", conn.RemoteAddr(), "connected")
defer conn.Close()
_, err := io.CopyBuffer(conn, conn, buf[:])
if err != nil && err != io.EOF {
log.Fatal(err.Error())
}
println("Client", conn.RemoteAddr(), "closed")
}
func main() {
time.Sleep(2 * time.Second)
link, _ := probe.Probe()
err := link.NetConnect(&netlink.ConnectParams{
Ssid: ssid,
Passphrase: pass,
})
if err != nil {
log.Fatal(err)
}
println("Starting TCP server listening on", port)
l, err := net.Listen("tcp", port)
if err != nil {
log.Fatal(err.Error())
}
defer l.Close()
for {
conn, err := l.Accept()
if err != nil {
log.Fatal(err.Error())
}
go echo(conn)
}
}
-106
View File
@@ -1,106 +0,0 @@
// This example uses TLS to send an HTTPS request to retrieve a webpage
//
// You shall see "strict-transport-security" header in the response,
// this confirms communication is indeed over HTTPS
//
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security
//go:build pyportal || nano_rp2040 || metro_m4_airlift || arduino_mkrwifi1010 || matrixportal_m4 || wioterminal
package main
import (
"bufio"
"crypto/tls"
"fmt"
"io"
"log"
"machine"
"net"
"strings"
"time"
"tinygo.org/x/drivers/netlink"
"tinygo.org/x/drivers/netlink/probe"
)
var (
ssid string
pass string
// HTTPS server address to hit with a GET / request
address string = "httpbin.org:443"
)
var conn net.Conn
// Wait for user to open serial console
func waitSerial() {
for !machine.Serial.DTR() {
time.Sleep(100 * time.Millisecond)
}
}
func check(err error) {
if err != nil {
println("Hit an error:", err.Error())
panic("BYE")
}
}
func readResponse() {
r := bufio.NewReader(conn)
resp, err := io.ReadAll(r)
check(err)
println(string(resp))
}
func closeConnection() {
conn.Close()
}
func dialConnection() {
var err error
println("\r\n---------------\r\nDialing TLS connection")
conn, err = tls.Dial("tcp", address, nil)
for ; err != nil; conn, err = tls.Dial("tcp", address, nil) {
println("Connection failed:", err.Error())
time.Sleep(5 * time.Second)
}
println("Connected!\r")
}
func makeRequest() {
print("Sending HTTPS request...")
w := bufio.NewWriter(conn)
fmt.Fprintln(w, "GET /get HTTP/1.1")
fmt.Fprintln(w, "Host:", strings.Split(address, ":")[0])
fmt.Fprintln(w, "User-Agent: TinyGo")
fmt.Fprintln(w, "Connection: close")
fmt.Fprintln(w)
check(w.Flush())
println("Sent!\r\n\r")
}
func main() {
waitSerial()
link, _ := probe.Probe()
err := link.NetConnect(&netlink.ConnectParams{
Ssid: ssid,
Passphrase: pass,
})
if err != nil {
log.Fatal(err)
}
for i := 0; ; i++ {
dialConnection()
makeRequest()
readResponse()
closeConnection()
println("--------", i, "--------\r\n")
time.Sleep(10 * time.Second)
}
}
-120
View File
@@ -1,120 +0,0 @@
// This example runs on wioterminal. It gets an URL using http.Get() and
// displays the output on the wioterminal LCD screen.
//
// Note: It may be necessary to increase the stack size when using "net/http".
// Use the -stack-size=4KB command line option.
//go:build wioterminal
package main
import (
"fmt"
"image/color"
"io"
"log"
"machine"
"net/http"
"net/url"
"strings"
"time"
"tinygo.org/x/drivers/ili9341"
"tinygo.org/x/drivers/netlink"
"tinygo.org/x/drivers/netlink/probe"
"tinygo.org/x/tinyfont/proggy"
"tinygo.org/x/tinyterm"
)
var (
ssid string
pass string
)
var (
display = ili9341.NewSPI(
machine.SPI3,
machine.LCD_DC,
machine.LCD_SS_PIN,
machine.LCD_RESET,
)
backlight = machine.LCD_BACKLIGHT
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
)
func main() {
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{})
display.FillScreen(black)
backlight.Configure(machine.PinConfig{machine.PinOutput})
backlight.High()
terminal.Configure(&tinyterm.Config{
Font: font,
FontHeight: 10,
FontOffset: 6,
})
fmt.Fprintf(terminal, "Connecting to %s...\r\n", ssid)
link, _ := probe.Probe()
err := link.NetConnect(&netlink.ConnectParams{
Ssid: ssid,
Passphrase: pass,
})
if err != nil {
log.Fatal(err)
}
name := "John Doe"
occupation := "gardener"
params := "name=" + url.QueryEscape(name) + "&" +
"occupation=" + url.QueryEscape(occupation)
path := fmt.Sprintf("https://httpbin.org/get?%s", params)
cnt := 0
for {
fmt.Fprintf(terminal, "Getting %s\r\n\r\n", path)
resp, err := http.Get(path)
if err != nil {
fmt.Fprintf(terminal, "%s\r\n", err.Error())
time.Sleep(10 * time.Second)
continue
}
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.Fprintf(terminal, "\r\n")
body, err := io.ReadAll(resp.Body)
fmt.Fprintf(terminal, string(body))
resp.Body.Close()
cnt++
fmt.Fprintf(terminal, "-------- %d --------\r\n", cnt)
time.Sleep(10 * time.Second)
}
}
-117
View File
@@ -1,117 +0,0 @@
// This example uses TCP to send an HTTP request to retrieve a webpage. The
// HTTP request is hand-rolled to avoid the overhead of using http.Get() from
// the "net/http" package. See example/net/http-get for the full http.Get()
// functionality.
//
// Example HTTP server:
// ---------------------------------------------------------------------------
// package main
//
// import "net/http"
//
// func main() {
// http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
// w.Write([]byte("hello"))
// })
// http.ListenAndServe(":8080", nil)
// }
// ---------------------------------------------------------------------------
//go:build pyportal || arduino_nano33 || nano_rp2040 || metro_m4_airlift || arduino_mkrwifi1010 || matrixportal_m4 || wioterminal
package main
import (
"bufio"
"fmt"
"io"
"log"
"machine"
"net"
"strings"
"time"
"tinygo.org/x/drivers/netlink"
"tinygo.org/x/drivers/netlink/probe"
)
var (
ssid string
pass string
// HTTP server address to hit with a GET / request
address string = "10.0.0.100:8080"
)
var conn net.Conn
// Wait for user to open serial console
func waitSerial() {
for !machine.Serial.DTR() {
time.Sleep(100 * time.Millisecond)
}
}
func dialConnection() {
var err error
println("\r\n---------------\r\nDialing TCP connection")
conn, err = net.Dial("tcp", address)
for ; err != nil; conn, err = net.Dial("tcp", address) {
println("Connection failed:", err.Error())
time.Sleep(5 * time.Second)
}
println("Connected!\r")
}
func check(err error) {
if err != nil {
println("Hit an error:", err.Error())
panic("BYE")
}
}
func makeRequest() {
println("Sending HTTP request...")
w := bufio.NewWriter(conn)
fmt.Fprintln(w, "GET / HTTP/1.1")
fmt.Fprintln(w, "Host:", strings.Split(address, ":")[0])
fmt.Fprintln(w, "User-Agent: TinyGo")
fmt.Fprintln(w, "Connection: close")
fmt.Fprintln(w)
check(w.Flush())
println("Sent!\r\n\r")
}
func readResponse() {
r := bufio.NewReader(conn)
resp, err := io.ReadAll(r)
check(err)
println(string(resp))
}
func closeConnection() {
conn.Close()
}
func main() {
waitSerial()
link, _ := probe.Probe()
err := link.NetConnect(&netlink.ConnectParams{
Ssid: ssid,
Passphrase: pass,
})
if err != nil {
log.Fatal(err)
}
for i := 0; ; i++ {
dialConnection()
makeRequest()
readResponse()
closeConnection()
println("--------", i, "--------\r\n")
time.Sleep(10 * time.Second)
}
}
-64
View File
@@ -1,64 +0,0 @@
// This example is a websocket client. It connects to a websocket server
// which echos messages back to the client. For server, see
//
// https://pkg.go.dev/golang.org/x/net/websocket#example-Handler
//
// Note: It may be necessary to increase the stack size when using
// "golang.org/x/net/websocket". Use the -stack-size=4KB command line option.
//go:build pyportal || nano_rp2040 || metro_m4_airlift || arduino_mkrwifi1010 || matrixportal_m4 || wioterminal
package main
import (
"fmt"
"log"
"machine"
"time"
"golang.org/x/net/websocket"
"tinygo.org/x/drivers/netlink"
"tinygo.org/x/drivers/netlink/probe"
)
var (
ssid string
pass string
url string = "ws://10.0.0.100:8080/echo"
)
// Wait for user to open serial console
func waitSerial() {
for !machine.Serial.DTR() {
time.Sleep(100 * time.Millisecond)
}
}
func main() {
waitSerial()
link, _ := probe.Probe()
err := link.NetConnect(&netlink.ConnectParams{
Ssid: ssid,
Passphrase: pass,
})
if err != nil {
log.Fatal(err)
}
origin := "http://localhost/"
ws, err := websocket.Dial(url, "", origin)
if err != nil {
log.Fatal(err)
}
if _, err := ws.Write([]byte("hello, world!\n")); err != nil {
log.Fatal(err)
}
var msg = make([]byte, 512)
var n int
if n, err = ws.Read(msg); err != nil {
log.Fatal(err)
}
fmt.Printf("Received: %s", msg[:n])
}
-62
View File
@@ -1,62 +0,0 @@
// This example is a websocket server. It listens for websocket clients
// to connect and echos messages back to the client. For client, see
//
// https://pkg.go.dev/golang.org/x/net/websocket#example-Dial
//
// Note: It may be necessary to increase the stack size when using
// "golang.org/x/net/websocket". Use the -stack-size=4KB command line option.
//go:build pyportal || nano_rp2040 || metro_m4_airlift || arduino_mkrwifi1010 || matrixportal_m4 || wioterminal
package main
import (
"io"
"log"
"machine"
"net/http"
"time"
"golang.org/x/net/websocket"
"tinygo.org/x/drivers/netlink"
"tinygo.org/x/drivers/netlink/probe"
)
var (
ssid string
pass string
port string = ":8080"
)
// Echo the data received on the WebSocket.
func EchoServer(ws *websocket.Conn) {
io.Copy(ws, ws)
}
// Wait for user to open serial console
func waitSerial() {
for !machine.Serial.DTR() {
time.Sleep(100 * time.Millisecond)
}
}
// This example demonstrates a trivial echo server.
func main() {
waitSerial()
link, _ := probe.Probe()
err := link.NetConnect(&netlink.ConnectParams{
Ssid: ssid,
Passphrase: pass,
})
if err != nil {
log.Fatal(err)
}
http.Handle("/echo", websocket.Handler(EchoServer))
err = http.ListenAndServe(port, nil)
if err != nil {
panic("ListenAndServe: " + err.Error())
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

-28
View File
@@ -1,28 +0,0 @@
<html>
<head>
<title>TinyGo - A Go Compiler For Small Places</title>
<style>
body {
background-color: rgb(4, 111, 143);
text-align: center;
display: flex;
flex-direction: column;
align-items: center;
}
img {
display: block;
margin: 0 auto;
width: 299px;
height: 255px;
}
h1 {
color: white;
text-align: center;
}
</style>
</head>
<body>
<h1>TinyGo - A Go Compiler For Small Places</h1>
<img src="images/tinygo-logo.png">
</body>
</html>
-49
View File
@@ -1,49 +0,0 @@
// This example is an HTTP server serving up a static file system
//
// Note: It may be necessary to increase the stack size when using "net/http".
// Use the -stack-size=4KB command line option.
//go:build pyportal || nano_rp2040 || metro_m4_airlift || arduino_mkrwifi1010 || matrixportal_m4 || wioterminal
package main
import (
"embed"
"log"
"net/http"
"time"
"tinygo.org/x/drivers/netlink"
"tinygo.org/x/drivers/netlink/probe"
)
var (
ssid string
pass string
port string = ":80"
)
//go:embed index.html main.go images
var fs embed.FS
func main() {
// wait a bit for console
time.Sleep(2 * time.Second)
link, _ := probe.Probe()
err := link.NetConnect(&netlink.ConnectParams{
Ssid: ssid,
Passphrase: pass,
})
if err != nil {
log.Fatal(err)
}
hfs := http.FileServer(http.FS(fs))
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
hfs.ServeHTTP(w, r)
})
log.Fatal(http.ListenAndServe(port, nil))
}
-34
View File
@@ -1,34 +0,0 @@
package main
import (
"machine"
"time"
"tinygo.org/x/drivers/pcf8523"
)
func main() {
machine.I2C0.Configure(machine.I2CConfig{})
dev := pcf8523.New(machine.I2C0)
// make sure the battery takes over if power is lost
err := dev.SetPowerManagement(pcf8523.PowerManagement_SwitchOver_ModeStandard)
if err != nil {
panic(err)
}
// set RTC once, i.e. from `date -u +"%Y-%m-%dT%H:%M:%SZ"`
now, _ := time.Parse(time.RFC3339, "2023-09-18T20:31:38Z")
err = dev.SetTime(now)
if err != nil {
panic(err)
}
for {
ts, err := dev.ReadTime()
if err != nil {
panic(err)
}
println("tick-tock, it's: " + ts.String())
time.Sleep(2 * time.Second)
}
}
+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 (
"machine"
"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"
// pass = "your-password"
// debug = true
// server = "tinygo.org"
// }
var (
ssid string
pass 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.Driver
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 {
// change the UART and pins as needed for platforms other than the WioTerminal.
adaptor = rtl8720dn.New(machine.UART3, machine.PB24, machine.PC24, machine.RTL8720D_CHIP_PU)
adaptor.Debug(debug)
adaptor.Configure()
err := adaptor.ConnectToAccessPoint(ssid, pass, 10*time.Second)
if err != nil {
return err
}
ip, subnet, gateway, err := adaptor.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)
}
}
+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 (
"machine"
"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"
// pass = "your-password"
// debug = true
// server = "tinygo.org"
// }
var (
ssid string
pass 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.Driver
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 {
// change the UART and pins as needed for platforms other than the WioTerminal.
adaptor = rtl8720dn.New(machine.UART3, machine.PB24, machine.PC24, machine.RTL8720D_CHIP_PU)
adaptor.Debug(debug)
adaptor.Configure()
err := adaptor.ConnectToAccessPoint(ssid, pass, 10*time.Second)
if err != nil {
return err
}
ip, subnet, gateway, err := adaptor.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)
}
}
+143
View File
@@ -0,0 +1,143 @@
// 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 (
"machine"
"errors"
"fmt"
"runtime"
"time"
"tinygo.org/x/drivers/net"
"tinygo.org/x/drivers/rtl8720dn"
)
// 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"
// pass = "your-password"
// ntpHost = "129.6.15.29"
// debug = true
// }
var (
ssid string
pass 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 {
adaptor := rtl8720dn.New(machine.UART3, machine.PB24, machine.PC24, machine.RTL8720D_CHIP_PU)
adaptor.Debug(debug)
adaptor.Configure()
err := adaptor.ConnectToAccessPoint(ssid, pass, 10*time.Second)
if err != nil {
return err
}
ip, subnet, gateway, err := adaptor.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 {
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")
}
+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 (
"machine"
"bytes"
"fmt"
"time"
"tinygo.org/x/drivers/net"
"tinygo.org/x/drivers/rtl8720dn"
)
// You can override the setting with the init() in another source code.
// func init() {
// ssid = "your-ssid"
// pass = "your-password"
// serverIP = "192.168.1.119"
// debug = true
// }
var (
ssid string
pass 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 {
adaptor := rtl8720dn.New(machine.UART3, machine.PB24, machine.PC24, machine.RTL8720D_CHIP_PU)
adaptor.Debug(debug)
adaptor.Configure()
err := adaptor.ConnectToAccessPoint(ssid, pass, 10*time.Second)
if err != nil {
return err
}
ip, subnet, gateway, err := adaptor.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")
}
+137
View File
@@ -0,0 +1,137 @@
package main
import (
"machine"
"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"
// pass = "your-password"
// debug = true
// url = "https://www.example.com"
// test_root_ca = "..."
// }
var (
ssid string
pass 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
func main() {
err := run()
for err != nil {
fmt.Printf("error: %s\r\n", err.Error())
time.Sleep(5 * time.Second)
}
}
func run() error {
adaptor := rtl8720dn.New(machine.UART3, machine.PB24, machine.PC24, machine.RTL8720D_CHIP_PU)
adaptor.Debug(debug)
adaptor.Configure()
adaptor.SetRootCA(&test_root_ca)
http.SetBuf(buf[:])
err := adaptor.ConnectToAccessPoint(ssid, pass, 10*time.Second)
if err != nil {
return err
}
ip, subnet, gateway, err := adaptor.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)
}
}
+91
View File
@@ -0,0 +1,91 @@
package main
import (
"machine"
"fmt"
"strconv"
"time"
"tinygo.org/x/drivers/net"
"tinygo.org/x/drivers/net/http"
"tinygo.org/x/drivers/rtl8720dn"
)
// 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"
// pass = "your-password"
// hubIP = "192.168.1.118"
// debug = true
// }
var (
ssid string
pass 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 {
adaptor := rtl8720dn.New(machine.UART3, machine.PB24, machine.PC24, machine.RTL8720D_CHIP_PU)
adaptor.Debug(debug)
adaptor.Configure()
http.SetBuf(buf[:])
err := adaptor.ConnectToAccessPoint(ssid, pass, 10*time.Second)
if err != nil {
return err
}
ip, subnet, gateway, err := adaptor.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")
}
+40
View File
@@ -0,0 +1,40 @@
package main
import (
"machine"
"fmt"
"time"
"tinygo.org/x/drivers/rtl8720dn"
)
var (
debug = false
)
func main() {
err := run()
for err != nil {
fmt.Printf("error: %s\r\n", err.Error())
time.Sleep(5 * time.Second)
}
}
func run() error {
adaptor := rtl8720dn.New(machine.UART3, machine.PB24, machine.PC24, machine.RTL8720D_CHIP_PU)
adaptor.Debug(debug)
adaptor.Configure()
ver, err := adaptor.Version()
if err != nil {
return nil
}
for {
fmt.Printf("RTL8270DN Firmware Version: %s\r\n", ver)
time.Sleep(10 * time.Second)
}
return nil
}
@@ -0,0 +1,162 @@
package main
import (
"machine"
"bufio"
"fmt"
"image/color"
"strings"
"time"
"tinygo.org/x/drivers/ili9341"
"tinygo.org/x/drivers/net/http"
"tinygo.org/x/drivers/rtl8720dn"
"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"
// pass = "your-password"
// debug = false // true
// server = "tinygo.org"
// }
var (
ssid string
pass string
url = "http://tinygo.org/"
debug = false
)
var (
display = ili9341.NewSPI(
machine.SPI3,
machine.LCD_DC,
machine.LCD_SS_PIN,
machine.LCD_RESET,
)
backlight = machine.LCD_BACKLIGHT
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")
}
adaptor := rtl8720dn.New(machine.UART3, machine.PB24, machine.PC24, machine.RTL8720D_CHIP_PU)
adaptor.Debug(debug)
adaptor.Configure()
http.UseDriver(adaptor)
http.SetBuf(buf[:])
fmt.Fprintf(terminal, "ConnectToAP()\r\n")
err := adaptor.ConnectToAccessPoint(ssid, pass, 10*time.Second)
if err != nil {
return err
}
fmt.Fprintf(terminal, "connected\r\n\r\n")
ip, subnet, gateway, err := adaptor.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)
}
}
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})
}
+106
View File
@@ -0,0 +1,106 @@
package main
import (
"machine"
"bufio"
"fmt"
"strings"
"time"
"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"
// pass = "your-password"
// url = "http://tinygo.org/"
// debug = true
// }
var (
ssid string
pass 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 {
adaptor := rtl8720dn.New(machine.UART3, machine.PB24, machine.PC24, machine.RTL8720D_CHIP_PU)
adaptor.Debug(debug)
adaptor.Configure()
http.UseDriver(adaptor)
http.SetBuf(buf[:])
err := adaptor.ConnectToAccessPoint(ssid, pass, 10*time.Second)
if err != nil {
return err
}
ip, subnet, gateway, err := adaptor.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)
}
}
@@ -1,51 +1,61 @@
// This example listens on port :80 serving a web page. Multiple clients
// may connect and be serviced at the same time. IPv4 only. HTTP only.
//
// $ curl http://10.0.0.2
//
// Note: It may be necessary to increase the stack size when using "net/http".
// Use the -stack-size=4KB command line option.
//go:build pyportal || nano_rp2040 || metro_m4_airlift || arduino_mkrwifi1010 || matrixportal_m4 || wioterminal
package main
import (
"fmt"
"log"
"machine"
"net/http"
"strconv"
"time"
"tinygo.org/x/drivers/netlink"
"tinygo.org/x/drivers/netlink/probe"
"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"
// pass = "your-password"
// debug = true
// }
var (
ssid string
pass string
port string = ":80"
ssid string
pass string
debug = false
)
var led = machine.LED
var backlight = machine.LCD_BACKLIGHT
func main() {
// wait a bit for serial
time.Sleep(2 * time.Second)
led.Configure(machine.PinConfig{Mode: machine.PinOutput})
backlight.Configure(machine.PinConfig{Mode: machine.PinOutput})
link, _ := probe.Probe()
err := link.NetConnect(&netlink.ConnectParams{
Ssid: ssid,
Passphrase: pass,
})
if err != nil {
log.Fatal(err)
err := run()
for err != nil {
fmt.Printf("error: %s\r\n", err.Error())
time.Sleep(5 * time.Second)
}
}
func run() error {
adaptor := rtl8720dn.New(machine.UART3, machine.PB24, machine.PC24, machine.RTL8720D_CHIP_PU)
adaptor.Debug(debug)
adaptor.Configure()
http.UseDriver(adaptor)
err := adaptor.ConnectToAccessPoint(ssid, pass, 10*time.Second)
if err != nil {
return err
}
ip, subnet, gateway, err := adaptor.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)
@@ -53,12 +63,10 @@ func main() {
http.HandleFunc("/6", sixlines)
http.HandleFunc("/off", LED_OFF)
http.HandleFunc("/on", LED_ON)
err = http.ListenAndServe(port, nil)
for err != nil {
fmt.Printf("error: %s\r\n", err.Error())
time.Sleep(5 * time.Second)
if err := http.ListenAndServe(":80", nil); err != nil {
message(err.Error())
}
return nil
}
func root(w http.ResponseWriter, r *http.Request) {
@@ -93,8 +101,8 @@ func root(w http.ResponseWriter, r *http.Request) {
<title>TinyGo HTTP Server</title>
<script language="javascript" type="text/javascript">
var counter = 0
function ledOn() { fetch("/on"); }
function ledOff() { fetch("/off"); }
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", {
@@ -133,7 +141,7 @@ func root(w http.ResponseWriter, r *http.Request) {
</p>
</body>
</html>
`, access)
`, access)
}
func sixlines(w http.ResponseWriter, r *http.Request) {
@@ -147,12 +155,14 @@ func sixlines(w http.ResponseWriter, r *http.Request) {
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()")
}
@@ -177,3 +187,7 @@ func cnt(w http.ResponseWriter, r *http.Request) {
w.Header().Set(`Content-Type`, `application/json`)
fmt.Fprintf(w, `{"cnt": %d}`, counter)
}
func message(msg string) {
println(msg, "\r")
}
-33
View File
@@ -1,33 +0,0 @@
package main
import (
"machine"
"strconv"
"time"
"tinygo.org/x/drivers/seesaw"
)
const readDelay = time.Microsecond * 3000
// example reading soil moisture with an Adafruit capacitive soil-sensor (4026) powered by a seesaw
// https://learn.adafruit.com/adafruit-stemma-soil-sensor-i2c-capacitive-moisture-sensor/overview
func main() {
machine.I2C0.Configure(machine.I2CConfig{})
dev := seesaw.New(machine.I2C0)
dev.Address = 0x36
// the soil sensor is especially slow, let's give it some more time
dev.ReadDelay = readDelay
var buf [2]byte
err := dev.Read(seesaw.ModuleTouchBase, seesaw.FunctionTouchChannelOffset, buf[:])
if err != nil {
panic(err)
}
moisture := uint16(buf[0])<<8 | uint16(buf[1])
println("soil moisture: " + strconv.Itoa(int(moisture)))
}
-53
View File
@@ -1,53 +0,0 @@
package main
// Example for the SGP30 to be used on a Raspberry Pi pico.
// Connect the sensor I2C pins to GP26 and GP27 to test.
import (
"machine"
"time"
"tinygo.org/x/drivers/sgp30"
)
func main() {
time.Sleep(time.Second)
println("start")
// Configure the I2C bus.
bus := machine.I2C1
err := bus.Configure(machine.I2CConfig{
SDA: machine.GP26,
SCL: machine.GP27,
Frequency: 400 * machine.KHz,
})
if err != nil {
println("could not configure I2C:", bus)
return
}
// Configure the sensor.
sensor := sgp30.New(bus)
if !sensor.Connected() {
println("sensor not connected")
return
}
err = sensor.Configure(sgp30.Config{})
if err != nil {
println("sensor could not be configured:", err.Error())
return
}
// Measure every second, as recommended by the datasheet.
for {
time.Sleep(time.Second)
err := sensor.Update(0)
if err != nil {
println("could not read sensor:", err.Error())
continue
}
println("CO₂ equivalent:", sensor.CO2())
println("TVOC ", sensor.TVOC())
}
}
-23
View File
@@ -1,23 +0,0 @@
package main
import (
"fmt"
"machine"
"time"
"tinygo.org/x/drivers/sht4x"
)
func main() {
machine.I2C0.Configure(machine.I2CConfig{})
sensor := sht4x.New(machine.I2C0)
for {
temp, humidity, _ := sensor.ReadTemperatureHumidity()
t := fmt.Sprintf("%.2f", float32(temp)/1000)
h := fmt.Sprintf("%.2f", float32(humidity)/100)
println("Temperature: ", t, "°C")
println("Humidity: ", h, "%")
time.Sleep(2 * time.Second)
}
}
+27
View File
@@ -0,0 +1,27 @@
//go:build espat
package main
import (
"machine"
"tinygo.org/x/drivers/espat"
)
// 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 (
uart = machine.UART1
tx = machine.PA22
rx = machine.PA23
adaptor *espat.Device
)
func initAdaptor() *espat.Device {
uart.Configure(machine.UARTConfig{TX: tx, RX: rx})
adaptor = espat.New(uart)
adaptor.Configure()
return adaptor
}
@@ -1,60 +1,52 @@
// This example opens a TCP connection and sends some data, for the purpose of
// testing speed and connectivity.
// This example opens a TCP connection 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 -lk 8080
//go:build pyportal || arduino_nano33 || nano_rp2040 || metro_m4_airlift || arduino_mkrwifi1010 || matrixportal_m4 || wioterminal || challenger_rp2040 || pico
// nc -w 5 -lk 8080
package main
import (
"bytes"
"fmt"
"log"
"machine"
"net"
"time"
"tinygo.org/x/drivers/netlink"
"tinygo.org/x/drivers/netlink/probe"
"tinygo.org/x/drivers/net"
)
var (
// access point info
ssid string
pass string
addr string = "10.0.0.100:8080"
)
// IP address of the server aka "hub". Replace with your own info.
const serverIP = ""
var buf = &bytes.Buffer{}
func main() {
initAdaptor()
waitSerial()
link, _ := probe.Probe()
err := link.NetConnect(&netlink.ConnectParams{
Ssid: ssid,
Passphrase: pass,
})
if err != nil {
log.Fatal(err)
}
connectToAP()
for {
sendBatch()
time.Sleep(500 * time.Millisecond)
}
println("Done.")
}
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.Dial("tcp", addr)
for ; err != nil; conn, err = net.Dial("tcp", addr) {
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)
}
@@ -73,7 +65,7 @@ func sendBatch() {
"\r---------------------------- i == ", i, " ----------------------------")
if w, err = conn.Write(buf.Bytes()); err != nil {
println("error:", err.Error(), "\r")
break
continue
}
n += w
}
@@ -87,17 +79,34 @@ func sendBatch() {
println("error:", err.Error(), "\r")
}
// Right now this code is never reached. Need a way to trigger it...
println("Disconnecting TCP...")
conn.Close()
}
// connect to access point
func connectToAP() {
time.Sleep(2 * time.Second)
println("Connecting to " + ssid)
err := adaptor.ConnectToAccessPoint(ssid, pass, 10*time.Second)
if err != nil { // error connecting to AP
for {
println(err)
time.Sleep(1 * time.Second)
}
}
println("Connected.")
time.Sleep(2 * time.Second)
ip, err := adaptor.GetClientIP()
for ; err != nil; ip, err = adaptor.GetClientIP() {
message(err.Error())
time.Sleep(1 * time.Second)
}
message(ip)
}
func message(msg string) {
println(msg, "\r")
}
// Wait for user to open serial console
func waitSerial() {
for !machine.Serial.DTR() {
time.Sleep(100 * time.Millisecond)
}
}
+76
View File
@@ -0,0 +1,76 @@
//go:build rtl8720dn
package main
import (
"device/sam"
"machine"
"runtime/interrupt"
"time"
"tinygo.org/x/drivers/net"
"tinygo.org/x/drivers/rtl8720dn"
)
var (
adaptor *rtl8720dn.RTL8720DN
uart UARTx
)
func initAdaptor() *rtl8720dn.RTL8720DN {
adaptor, err := setupRTL8720DN()
if err != nil {
return nil
}
net.UseDriver(adaptor)
return adaptor
}
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)
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
}
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)
}
+35
View File
@@ -0,0 +1,35 @@
//go:build wifinina
package main
import (
"machine"
"tinygo.org/x/drivers/wifinina"
)
var (
// default interface for the Arduino Nano33 IoT.
spi = machine.NINA_SPI
// ESP32/ESP8266 chip that has the WIFININA firmware flashed on it
adaptor *wifinina.Device
)
func initAdaptor() *wifinina.Device {
// 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()
return adaptor
}
+156
View File
@@ -0,0 +1,156 @@
// This example connects to Access Point and prints some info
package main
import (
"machine"
"strconv"
"time"
"tinygo.org/x/drivers/wifinina"
)
var (
// access point info
ssid string
pass string
)
// 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
)
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()
waitSerial()
connectToAP()
for {
println("----------------------------------------")
printSSID()
printRSSI()
printMac()
printIPs()
printTime()
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 {
println("IP: Unknown (error: ", err.Error(), ")")
return
}
println("IP: ", ip.String())
println("Subnet: ", subnet.String())
println("Gateway: ", gateway.String())
}
func printTime() {
print("Time: ")
t, err := adaptor.GetTime()
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 printMac() {
print("MAC: ")
mac, err := adaptor.GetMACAddress()
if err != nil {
println("Unknown (", err.Error(), ")")
}
println(mac.String())
}
// Wait for user to open serial console
func waitSerial() {
for !machine.Serial.DTR() {
time.Sleep(100 * time.Millisecond)
}
}
const retriesBeforeFailure = 3
// connect to access point
func connectToAP() {
time.Sleep(2 * time.Second)
var err error
for i := 0; i < retriesBeforeFailure; i++ {
println("Connecting to " + ssid)
err = adaptor.ConnectToAccessPoint(ssid, pass, 10*time.Second)
if err == nil {
println("Connected.")
return
}
}
// error connecting to AP
failMessage(err.Error())
}
func failMessage(msg string) {
for {
println(msg)
time.Sleep(1 * time.Second)
}
}
+173
View File
@@ -0,0 +1,173 @@
// 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"
)
var (
// access point info
ssid string
pass string
)
// 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()
displayIP()
// 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)
}
}
const retriesBeforeFailure = 3
// connect to access point
func connectToAP() {
time.Sleep(2 * time.Second)
var err error
for i := 0; i < retriesBeforeFailure; i++ {
println("Connecting to " + ssid)
err = adaptor.ConnectToAccessPoint(ssid, pass, 10*time.Second)
if err == nil {
println("Connected.")
return
}
}
// error connecting to AP
failMessage(err.Error())
}
func displayIP() {
ip, _, _, err := adaptor.GetIP()
for ; err != nil; ip, _, _, err = adaptor.GetIP() {
message(err.Error())
time.Sleep(1 * time.Second)
}
message("IP address: " + ip.String())
}
func message(msg string) {
println(msg, "\r")
}
func failMessage(msg string) {
for {
println(msg)
time.Sleep(1 * time.Second)
}
}
+153
View File
@@ -0,0 +1,153 @@
// This is a sensor station that uses a ESP8266 or ESP32 running on the device UART1.
// It creates an MQTT connection that publishes a message every second
// to an MQTT broker.
//
// In other words:
// Your computer <--> UART0 <--> MCU <--> UART1 <--> ESP8266 <--> Internet <--> MQTT broker.
//
// You must install the Paho MQTT package to build this program:
//
// go get -u github.com/eclipse/paho.mqtt.golang
package main
import (
"fmt"
"machine"
"math/rand"
"time"
"tinygo.org/x/drivers/net/mqtt"
"tinygo.org/x/drivers/wifinina"
)
var (
// access point info
ssid string
pass string
)
// IP address of the MQTT broker to use. Replace with your own info.
const server = "tcp://test.mosquitto.org:1883"
//const server = "ssl://test.mosquitto.org:8883"
// 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
topic = "tinygo"
)
func main() {
time.Sleep(3000 * time.Millisecond)
rand.Seed(time.Now().UnixNano())
// 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,
})
// Init esp8266/esp32
adaptor = wifinina.New(spi,
machine.NINA_CS,
machine.NINA_ACK,
machine.NINA_GPIO0,
machine.NINA_RESETN)
adaptor.Configure()
connectToAP()
displayIP()
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 {
switch t := err.(type) {
case wifinina.Error:
println(t.Error(), "attempting to reconnect")
if token := cl.Connect(); token.Wait() && token.Error() != nil {
failMessage(token.Error().Error())
}
default:
println(err.Error())
}
}
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.")
}
const retriesBeforeFailure = 3
// connect to access point
func connectToAP() {
time.Sleep(2 * time.Second)
var err error
for i := 0; i < retriesBeforeFailure; i++ {
println("Connecting to " + ssid)
err = adaptor.ConnectToAccessPoint(ssid, pass, 10*time.Second)
if err == nil {
println("Connected.")
return
}
}
// error connecting to AP
failMessage(err.Error())
}
func displayIP() {
ip, _, _, err := adaptor.GetIP()
for ; err != nil; ip, _, _, err = adaptor.GetIP() {
println(err.Error())
time.Sleep(1 * time.Second)
}
println("IP address: " + ip.String())
}
// 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)
}
}
+165
View File
@@ -0,0 +1,165 @@
// This is a sensor station that uses a ESP8266 or ESP32 running on the device UART1.
// It creates an MQTT connection that publishes a message every second
// to an MQTT broker.
//
// In other words:
// Your computer <--> UART0 <--> MCU <--> UART1 <--> ESP8266 <--> Internet <--> MQTT broker.
//
// You must also install the Paho MQTT package to build this program:
//
// go get -u github.com/eclipse/paho.mqtt.golang
package main
import (
"fmt"
"machine"
"math/rand"
"time"
"tinygo.org/x/drivers/net/mqtt"
"tinygo.org/x/drivers/wifinina"
)
var (
// access point info
ssid string
pass string
)
// IP address of the MQTT broker to use. Replace with your own info.
const server = "tcp://test.mosquitto.org:1883"
//const server = "ssl://test.mosquitto.org:8883"
// 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
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 main() {
time.Sleep(3000 * time.Millisecond)
rand.Seed(time.Now().UnixNano())
// 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,
})
// Init esp8266/esp32
adaptor = wifinina.New(spi,
machine.NINA_CS,
machine.NINA_ACK,
machine.NINA_GPIO0,
machine.NINA_RESETN)
adaptor.Configure()
connectToAP()
displayIP()
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.")
}
func publishing() {
for i := 0; ; i++ {
println("Publishing MQTT message...")
data := []byte(fmt.Sprintf(`{"e":[{"n":"hello %d","v":101}]}`, i))
token := cl.Publish(topicRx, 0, false, data)
token.Wait()
if token.Error() != nil {
println(token.Error().Error())
}
time.Sleep(100 * time.Millisecond)
}
}
const retriesBeforeFailure = 3
// connect to access point
func connectToAP() {
time.Sleep(2 * time.Second)
var err error
for i := 0; i < retriesBeforeFailure; i++ {
println("Connecting to " + ssid)
err = adaptor.ConnectToAccessPoint(ssid, pass, 10*time.Second)
if err == nil {
println("Connected.")
return
}
}
// error connecting to AP
failMessage(err.Error())
}
func displayIP() {
ip, _, _, err := adaptor.GetIP()
for ; err != nil; ip, _, _, err = adaptor.GetIP() {
println(err.Error())
time.Sleep(1 * time.Second)
}
println("IP address: " + ip.String())
}
// 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)
}
}
+191
View File
@@ -0,0 +1,191 @@
// This is an example of using the wifinina 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"
"machine"
"runtime"
"time"
"tinygo.org/x/drivers/net"
"tinygo.org/x/drivers/wifinina"
)
var (
// access point info
ssid string
pass string
)
// IP address of the server aka "hub". Replace with your own info.
const ntpHost = "129.6.15.29"
const NTP_PACKET_SIZE = 48
// 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
b = make([]byte, NTP_PACKET_SIZE)
)
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()
waitSerial()
connectToAP()
displayIP()
// now make UDP connection
ip := net.ParseIP(ntpHost)
raddr := &net.UDPAddr{IP: ip, Port: 123}
laddr := &net.UDPAddr{Port: 2390}
conn, err := net.DialUDP("udp", laddr, raddr)
if err != nil {
for {
time.Sleep(time.Second)
println(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)
}
}
}
// Wait for user to open serial console
func waitSerial() {
for !machine.Serial.DTR() {
time.Sleep(100 * time.Millisecond)
}
}
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 {
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
}
}
const retriesBeforeFailure = 3
// connect to access point
func connectToAP() {
time.Sleep(2 * time.Second)
var err error
for i := 0; i < retriesBeforeFailure; i++ {
println("Connecting to " + ssid)
err = adaptor.ConnectToAccessPoint(ssid, pass, 10*time.Second)
if err == nil {
println("Connected.")
return
}
}
// error connecting to AP
failMessage(err.Error())
}
func displayIP() {
ip, _, _, err := adaptor.GetIP()
for ; err != nil; ip, _, _, err = adaptor.GetIP() {
message(err.Error())
time.Sleep(1 * time.Second)
}
message("IP address: " + ip.String())
}
func message(format string, args ...interface{}) {
println(fmt.Sprintf(format, args...), "\r")
}
func failMessage(msg string) {
for {
println(msg)
time.Sleep(1 * time.Second)
}
}
+90
View File
@@ -0,0 +1,90 @@
//go:build nano_rp2040
// This examples shows how to control RGB LED connected to
// NINA-W102 chip on Arduino Nano RP2040 Connect board
// Built-in LED code added for API comparison
package main
import (
"machine"
"time"
"tinygo.org/x/drivers/wifinina"
)
const (
LED = machine.LED
// Arduino Nano RP2040 Connect board RGB LED pins
// See https://docs.arduino.cc/static/3525d638b5c76a2d19588d6b41cd02a0/ABX00053-full-pinout.pdf
LED_R wifinina.Pin = 27
LED_G wifinina.Pin = 25
LED_B wifinina.Pin = 26
)
var (
// these are the default pins for the Arduino Nano-RP2040 Connect
spi = machine.NINA_SPI
// this is the ESP chip that has the WIFININA firmware flashed on it
device *wifinina.Device
)
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,
})
device = wifinina.New(spi,
machine.NINA_CS,
machine.NINA_ACK,
machine.NINA_GPIO0,
machine.NINA_RESETN)
device.Configure()
time.Sleep(time.Second)
LED.Configure(machine.PinConfig{Mode: machine.PinOutput})
LED_R.Configure(wifinina.PinConfig{Mode: wifinina.PinOutput})
LED_G.Configure(wifinina.PinConfig{Mode: wifinina.PinOutput})
LED_B.Configure(wifinina.PinConfig{Mode: wifinina.PinOutput})
}
func main() {
setup()
LED.Low() // OFF
LED_R.High() // OFF
LED_G.High() // OFF
LED_B.High() // OFF
go func() {
for {
LED.Low()
time.Sleep(time.Second)
LED.High()
time.Sleep(time.Second)
}
}()
for {
LED_R.Low() // ON
time.Sleep(time.Second)
LED_R.High() // OFF
LED_G.Low() // ON
time.Sleep(time.Second)
LED_G.High() // OFF
LED_B.Low() // ON
time.Sleep(time.Second)
LED_B.High() // OFF
}
}
+153
View File
@@ -0,0 +1,153 @@
// This example opens a TCP connection using a device with WiFiNINA 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"
"machine"
"time"
"tinygo.org/x/drivers/net"
"tinygo.org/x/drivers/wifinina"
)
var (
// access point info
ssid string
pass string
)
// IP address of the server aka "hub". Replace with your own info.
const serverIP = ""
// 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 = &bytes.Buffer{}
func main() {
// 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()
connectToAP()
displayIP()
for {
sendBatch()
time.Sleep(500 * time.Millisecond)
}
println("Done.")
}
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()
}
const retriesBeforeFailure = 3
// connect to access point
func connectToAP() {
time.Sleep(2 * time.Second)
var err error
for i := 0; i < retriesBeforeFailure; i++ {
println("Connecting to " + ssid)
err = adaptor.ConnectToAccessPoint(ssid, pass, 10*time.Second)
if err == nil {
println("Connected.")
return
}
}
// error connecting to AP
failMessage(err.Error())
}
func displayIP() {
ip, _, _, err := adaptor.GetIP()
for ; err != nil; ip, _, _, err = adaptor.GetIP() {
message(err.Error())
time.Sleep(1 * time.Second)
}
message("IP address: " + ip.String())
}
func message(msg string) {
println(msg, "\r")
}
func failMessage(msg string) {
for {
println(msg)
time.Sleep(1 * time.Second)
}
}
+163
View File
@@ -0,0 +1,163 @@
// This example opens a TCP connection using a device with WiFiNINA firmware
// and sends a HTTPS request to retrieve a webpage
//
// You shall see "strict-transport-security" header in the response,
// this confirms communication is indeed over HTTPS
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security
package main
import (
"fmt"
"machine"
"strings"
"time"
"tinygo.org/x/drivers/net"
"tinygo.org/x/drivers/net/tls"
"tinygo.org/x/drivers/wifinina"
)
var (
// access point info
ssid string
pass string
)
// IP address of the server aka "hub". Replace with your own info.
const server = "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 [256]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()
waitSerial()
connectToAP()
displayIP()
for {
readConnection()
if time.Now().Sub(lastRequestTime).Milliseconds() >= 10000 {
makeHTTPSRequest()
}
}
}
// Wait for user to open serial console
func waitSerial() {
for !machine.Serial.DTR() {
time.Sleep(100 * time.Millisecond)
}
}
func readConnection() {
if conn != nil {
for n, err := conn.Read(buf[:]); n > 0; n, err = conn.Read(buf[:]) {
if err != nil {
println("Read error: " + err.Error())
} else {
print(string(buf[0:n]))
}
}
}
}
func makeHTTPSRequest() {
var err error
if conn != nil {
conn.Close()
}
message("\r\n---------------\r\nDialing TCP connection")
conn, err = tls.Dial("tcp", server, nil)
for ; err != nil; conn, err = tls.Dial("tcp", server, nil) {
message("Connection failed: " + err.Error())
time.Sleep(5 * time.Second)
}
println("Connected!\r")
print("Sending HTTPS request...")
fmt.Fprintln(conn, "GET / HTTP/1.1")
fmt.Fprintln(conn, "Host:", strings.Split(server, ":")[0])
fmt.Fprintln(conn, "User-Agent: TinyGo")
fmt.Fprintln(conn, "Connection: close")
fmt.Fprintln(conn)
println("Sent!\r\n\r")
lastRequestTime = time.Now()
}
const retriesBeforeFailure = 3
// connect to access point
func connectToAP() {
time.Sleep(2 * time.Second)
var err error
for i := 0; i < retriesBeforeFailure; i++ {
println("Connecting to " + ssid)
err = adaptor.ConnectToAccessPoint(ssid, pass, 10*time.Second)
if err == nil {
println("Connected.")
return
}
}
// error connecting to AP
failMessage(err.Error())
}
func displayIP() {
ip, _, _, err := adaptor.GetIP()
for ; err != nil; ip, _, _, err = adaptor.GetIP() {
message(err.Error())
time.Sleep(1 * time.Second)
}
message("IP address: " + ip.String())
}
func message(msg string) {
println(msg, "\r")
}
func failMessage(msg string) {
for {
println(msg)
time.Sleep(1 * time.Second)
}
}
+119
View File
@@ -0,0 +1,119 @@
// This is a sensor station that uses a ESP32 running nina-fw over SPI.
// It creates a UDP connection you can use to get info to/from your computer via the microcontroller.
//
// In other words:
// Your computer <--> UART0 <--> MCU <--> SPI <--> ESP32
package main
import (
"machine"
"strconv"
"time"
"tinygo.org/x/drivers/net"
"tinygo.org/x/drivers/wifinina"
)
var (
// access point info
ssid string
pass string
)
// IP address of the server aka "hub". Replace with your own info.
const hubIP = ""
var (
// this is the ESP chip that has the WIFININA firmware flashed on it
adaptor *wifinina.Device
)
func main() {
// Init esp8266/esp32
// Configure SPI for 8Mhz, Mode 0, MSB First
machine.NINA_SPI.Configure(machine.SPIConfig{
Frequency: 8 * 1e6,
SDO: machine.NINA_SDO,
SDI: machine.NINA_SDI,
SCK: machine.NINA_SCK,
})
// these are the default pins for the Arduino Nano33 IoT.
// change these to connect to a different UART or pins for the ESP8266/ESP32
adaptor = wifinina.New(machine.NINA_SPI,
machine.NINA_CS,
machine.NINA_ACK,
machine.NINA_GPIO0,
machine.NINA_RESETN)
adaptor.Configure()
// connect to access point
connectToAP()
displayIP()
// now make UDP connection
ip := net.ParseIP(hubIP)
raddr := &net.UDPAddr{IP: ip, Port: 2222}
laddr := &net.UDPAddr{Port: 2222}
println("Dialing UDP connection...")
conn, err := net.DialUDP("udp", laddr, raddr)
if err != nil {
failMessage(err.Error())
}
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.")
}
const retriesBeforeFailure = 3
// connect to access point
func connectToAP() {
time.Sleep(2 * time.Second)
var err error
for i := 0; i < retriesBeforeFailure; i++ {
println("Connecting to " + ssid)
err = adaptor.ConnectToAccessPoint(ssid, pass, 10*time.Second)
if err == nil {
println("Connected.")
return
}
}
// error connecting to AP
failMessage(err.Error())
}
func displayIP() {
ip, _, _, err := adaptor.GetIP()
for ; err != nil; ip, _, _, err = adaptor.GetIP() {
message(err.Error())
time.Sleep(1 * time.Second)
}
message("IP address: " + ip.String())
}
func message(msg string) {
println(msg, "\r")
}
func failMessage(msg string) {
for {
println(msg)
time.Sleep(1 * time.Second)
}
}
+165
View File
@@ -0,0 +1,165 @@
// 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/
package main
import (
"fmt"
"machine"
"time"
"tinygo.org/x/drivers/net"
"tinygo.org/x/drivers/wifinina"
)
var (
// access point info
ssid string
pass string
)
// IP address of the "example.com" server. Replace with your own info.
const server = "93.184.216.34"
// 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 [256]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()
waitSerial()
connectToAP()
displayIP()
for {
readConnection()
if time.Now().Sub(lastRequestTime).Milliseconds() >= 10000 {
makeHTTPRequest()
}
}
}
// Wait for user to open serial console
func waitSerial() {
for !machine.Serial.DTR() {
time.Sleep(100 * time.Millisecond)
}
}
func readConnection() {
if conn != nil {
for n, err := conn.Read(buf[:]); n > 0; n, err = conn.Read(buf[:]) {
if err != nil {
println("Read error: " + err.Error())
} else {
print(string(buf[0:n]))
}
}
}
}
func makeHTTPRequest() {
var err error
if conn != nil {
conn.Close()
}
// make TCP connection
ip := net.ParseIP(server)
raddr := &net.TCPAddr{IP: ip, Port: 80}
laddr := &net.TCPAddr{Port: 8080}
message("\r\n---------------\r\nDialing TCP connection")
conn, err = net.DialTCP("tcp", laddr, raddr)
for ; err != nil; conn, err = net.DialTCP("tcp", laddr, raddr) {
message("Connection failed: " + err.Error())
time.Sleep(5 * time.Second)
}
println("Connected!\r")
print("Sending HTTP request...")
fmt.Fprintln(conn, "GET / HTTP/1.1")
fmt.Fprintln(conn, "Host:", server)
fmt.Fprintln(conn, "User-Agent: TinyGo")
fmt.Fprintln(conn, "Connection: close")
fmt.Fprintln(conn)
println("Sent!\r\n\r")
lastRequestTime = time.Now()
}
const retriesBeforeFailure = 3
// connect to access point
func connectToAP() {
time.Sleep(2 * time.Second)
var err error
for i := 0; i < retriesBeforeFailure; i++ {
println("Connecting to " + ssid)
err = adaptor.ConnectToAccessPoint(ssid, pass, 10*time.Second)
if err == nil {
println("Connected.")
return
}
}
// error connecting to AP
failMessage(err.Error())
}
func displayIP() {
ip, _, _, err := adaptor.GetIP()
for ; err != nil; ip, _, _, err = adaptor.GetIP() {
message(err.Error())
time.Sleep(1 * time.Second)
}
message("IP address: " + ip.String())
}
func message(msg string) {
println(msg, "\r")
}
func failMessage(msg string) {
for {
println(msg)
time.Sleep(1 * time.Second)
}
}
+234
View File
@@ -0,0 +1,234 @@
package main
import (
"fmt"
"machine"
"strconv"
"time"
"tinygo.org/x/drivers/net/http"
"tinygo.org/x/drivers/wifinina"
)
// You can override the settings with the init() in another source code:
//
// func init() {
// ssid = "your-ssid"
// pass = "your-password"
// }
//
// Or use -ldflags option on tinygo command to set at compile-time:
//
// tinygo flash ... -ldflags '-X "main.ssid=xxx" -X "main.pass=xxx"' ...
//
var (
ssid string
pass string
)
var (
// this is the ESP chip that has the WIFININA firmware flashed on it
adaptor *wifinina.Device
)
var led = machine.LED
func main() {
led.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 {
spi := machine.NINA_SPI
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()
connectToAP()
displayIP()
http.UseDriver(adaptor)
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)
return http.ListenAndServe(":80", 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"); }
function ledOff() { fetch("/off"); }
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()
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()
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)
}
const retriesBeforeFailure = 3
// connect to access point
func connectToAP() {
time.Sleep(2 * time.Second)
var err error
for i := 0; i < retriesBeforeFailure; i++ {
println("Connecting to " + ssid)
err = adaptor.ConnectToAccessPoint(ssid, pass, 10*time.Second)
if err == nil {
println("Connected.")
return
}
}
// error connecting to AP
failMessage(err.Error())
}
func displayIP() {
ip, _, _, err := adaptor.GetIP()
for ; err != nil; ip, _, _, err = adaptor.GetIP() {
message(err.Error())
time.Sleep(1 * time.Second)
}
message("IP address: " + ip.String())
}
func message(msg string) {
println(msg, "\r")
}
func failMessage(msg string) {
for {
println(msg)
time.Sleep(1 * time.Second)
}
}
+1 -1
View File
@@ -19,7 +19,7 @@ func main() {
neo.Configure(machine.PinConfig{Mode: machine.PinOutput})
ws := ws2812.NewWS2812(neo)
ws := ws2812.New(neo)
rg := false
for {
+2 -10
View File
@@ -1,20 +1,12 @@
module tinygo.org/x/drivers
go 1.18
go 1.15
require (
github.com/eclipse/paho.mqtt.golang v1.2.0
github.com/frankban/quicktest v1.10.2
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
github.com/soypat/natiu-mqtt v0.5.1
golang.org/x/net v0.7.0
tinygo.org/x/tinyfont v0.3.0
tinygo.org/x/tinyterm v0.1.0
)
require (
github.com/google/go-cmp v0.5.2 // indirect
github.com/kr/pretty v0.2.1 // indirect
github.com/kr/text v0.1.0 // indirect
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 // indirect
)
)
+37 -3
View File
@@ -7,22 +7,56 @@ github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
github.com/hajimehoshi/go-jisx0208 v1.0.0/go.mod h1:yYxEStHL7lt9uL+AbdWgW9gBumwieDoZCiB1f/0X0as=
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
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/soypat/natiu-mqtt v0.5.1 h1:rwaDmlvjzD2+3MCOjMZc4QEkDkNwDzbct2TJbpz+TPc=
github.com/soypat/natiu-mqtt v0.5.1/go.mod h1:xEta+cwop9izVCW7xOx2W+ct9PRMqr0gNVkvBPnQTc4=
github.com/sago35/go-bdf v0.0.0-20200313142241-6c17821c91c4/go.mod h1:rOebXGuMLsXhZAC6mF/TjxONsm45498ZyzVhel++6KM=
github.com/valyala/fastjson v1.6.3/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
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/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-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/drivers v0.19.0/go.mod h1:uJD/l1qWzxzLx+vcxaW0eY464N5RAgFi1zTVzASFdqI=
tinygo.org/x/tinyfont v0.2.1/go.mod h1:eLqnYSrFRjt5STxWaMeOWJTzrKhXqpWw7nU3bPfKOAM=
tinygo.org/x/tinyfont v0.3.0 h1:HIRLQoI3oc+2CMhPcfv+Ig88EcTImE/5npjqOnMD4lM=
tinygo.org/x/tinyfont v0.3.0/go.mod h1:+TV5q0KpwSGRWnN+ITijsIhrWYJkoUCp9MYELjKpAXk=
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=
tinygo.org/x/tinyterm v0.1.0/go.mod h1:/DDhNnGwNF2/tNgHywvyZuCGnbH3ov49Z/6e8LPLRR4=
-15
View File
@@ -7,7 +7,6 @@ import (
"time"
"tinygo.org/x/drivers"
"tinygo.org/x/drivers/pixel"
)
type Config struct {
@@ -32,9 +31,6 @@ type Device struct {
rd machine.Pin
}
// Image buffer type used in the ili9341.
type Image = pixel.Image[pixel.RGB565BE]
var cmdBuf [6]byte
var initCmd = []byte{
@@ -177,8 +173,6 @@ func (d *Device) EnableTEOutput(on bool) {
}
// DrawRGBBitmap copies an RGB bitmap to the internal buffer at given coordinates
//
// Deprecated: use DrawBitmap instead.
func (d *Device) DrawRGBBitmap(x, y int16, data []uint16, w, h int16) error {
k, i := d.Size()
if x < 0 || y < 0 || w <= 0 || h <= 0 ||
@@ -193,8 +187,6 @@ func (d *Device) DrawRGBBitmap(x, y int16, data []uint16, w, h int16) error {
}
// DrawRGBBitmap8 copies an RGB bitmap to the internal buffer at given coordinates
//
// Deprecated: use DrawBitmap instead.
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 ||
@@ -208,13 +200,6 @@ func (d *Device) DrawRGBBitmap8(x, y int16, data []uint8, w, h int16) error {
return nil
}
// DrawBitmap copies the bitmap to the internal buffer on the screen at the
// given coordinates. It returns once the image data has been sent completely.
func (d *Device) DrawBitmap(x, y int16, bitmap Image) error {
width, height := bitmap.Size()
return d.DrawRGBBitmap8(x, y, bitmap.RawBuffer(), int16(width), int16(height))
}
// 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()
-2
View File
@@ -81,8 +81,6 @@ const (
MHz_868_1 = 868100000
MHz_868_5 = 868500000
MHz_902_3 = 902300000
Mhz_903_0 = 903000000
MHZ_915_0 = 915000000
MHz_916_8 = 916800000
MHz_923_3 = 923300000
)
+25 -31
View File
@@ -30,11 +30,11 @@ const (
var (
ActiveRadio lora.Radio
Retries = 15
regionSettings region.Settings
regionSettings region.RegionSettings
)
// UseRegionSettings sets current Lorawan Regional parameters
func UseRegionSettings(rs region.Settings) {
func UseRegionSettings(rs region.RegionSettings) {
regionSettings = rs
}
@@ -52,13 +52,13 @@ func SetPublicNetwork(enabled bool) {
}
// ApplyChannelConfig sets current Lora modulation according to current regional settings
func applyChannelConfig(ch region.Channel) {
ActiveRadio.SetFrequency(ch.Frequency())
ActiveRadio.SetBandwidth(ch.Bandwidth())
ActiveRadio.SetCodingRate(ch.CodingRate())
ActiveRadio.SetSpreadingFactor(ch.SpreadingFactor())
ActiveRadio.SetPreambleLength(ch.PreambleLength())
ActiveRadio.SetTxPower(ch.TxPowerDBm())
func applyChannelConfig(ch *region.Channel) {
ActiveRadio.SetFrequency(ch.Frequency)
ActiveRadio.SetBandwidth(ch.Bandwidth)
ActiveRadio.SetCodingRate(ch.CodingRate)
ActiveRadio.SetSpreadingFactor(ch.SpreadingFactor)
ActiveRadio.SetPreambleLength(ch.PreambleLength)
ActiveRadio.SetTxPower(ch.TxPowerDBm)
// Lorawan defaults to explicit headers
ActiveRadio.SetHeaderType(lora.HeaderExplicit)
ActiveRadio.SetCrc(true)
@@ -84,30 +84,24 @@ func Join(otaa *Otaa, session *Session) error {
return err
}
for {
joinRequestChannel := regionSettings.JoinRequestChannel()
joinAcceptChannel := regionSettings.JoinAcceptChannel()
// Prepare radio for Join Tx
applyChannelConfig(regionSettings.JoinRequestChannel())
ActiveRadio.SetIqMode(lora.IQStandard)
ActiveRadio.Tx(payload, LORA_TX_TIMEOUT)
if err != nil {
return err
}
// Prepare radio for Join Tx
applyChannelConfig(joinRequestChannel)
ActiveRadio.SetIqMode(lora.IQStandard)
ActiveRadio.Tx(payload, LORA_TX_TIMEOUT)
if err != nil {
return err
}
// Wait for JoinAccept
applyChannelConfig(regionSettings.JoinAcceptChannel())
ActiveRadio.SetIqMode(lora.IQInverted)
resp, err = ActiveRadio.Rx(LORA_RX_TIMEOUT)
if err != nil {
return err
}
// Wait for JoinAccept
if joinAcceptChannel.Frequency() != 0 {
applyChannelConfig(joinAcceptChannel)
}
ActiveRadio.SetIqMode(lora.IQInverted)
resp, err = ActiveRadio.Rx(LORA_RX_TIMEOUT)
if err == nil && resp != nil {
break
}
if !joinAcceptChannel.Next() {
return ErrNoJoinAcceptReceived
}
if resp == nil {
return ErrNoJoinAcceptReceived
}
err = otaa.DecodeJoinAccept(resp, session)
+23 -21
View File
@@ -7,41 +7,43 @@ const (
AU915_DEFAULT_TX_POWER_DBM = 20
)
type ChannelAU struct {
channel
type RegionSettingsAU915 struct {
joinRequestChannel *Channel
joinAcceptChannel *Channel
uplinkChannel *Channel
}
func (c *ChannelAU) Next() bool {
return false
}
type SettingsAU915 struct {
settings
}
func AU915() *SettingsAU915 {
return &SettingsAU915{settings: settings{
joinRequestChannel: &ChannelAU{channel: channel{lora.MHz_916_8,
func AU915() *RegionSettingsAU915 {
return &RegionSettingsAU915{
joinRequestChannel: &Channel{lora.MHz_916_8,
lora.Bandwidth_125_0,
lora.SpreadingFactor9,
lora.CodingRate4_5,
AU915_DEFAULT_PREAMBLE_LEN,
AU915_DEFAULT_TX_POWER_DBM}},
joinAcceptChannel: &ChannelAU{channel: channel{lora.MHz_923_3,
AU915_DEFAULT_TX_POWER_DBM},
joinAcceptChannel: &Channel{lora.MHz_923_3,
lora.Bandwidth_500_0,
lora.SpreadingFactor9,
lora.CodingRate4_5,
AU915_DEFAULT_PREAMBLE_LEN,
AU915_DEFAULT_TX_POWER_DBM}},
uplinkChannel: &ChannelAU{channel: channel{lora.MHz_916_8,
AU915_DEFAULT_TX_POWER_DBM},
uplinkChannel: &Channel{lora.MHz_916_8,
lora.Bandwidth_125_0,
lora.SpreadingFactor9,
lora.CodingRate4_5,
AU915_DEFAULT_PREAMBLE_LEN,
AU915_DEFAULT_TX_POWER_DBM}},
}}
AU915_DEFAULT_TX_POWER_DBM},
}
}
func Next(c *ChannelAU) bool {
return false
func (r *RegionSettingsAU915) JoinRequestChannel() *Channel {
return r.joinRequestChannel
}
func (r *RegionSettingsAU915) JoinAcceptChannel() *Channel {
return r.joinAcceptChannel
}
func (r *RegionSettingsAU915) UplinkChannel() *Channel {
return r.uplinkChannel
}
-42
View File
@@ -1,42 +0,0 @@
package region
type Channel interface {
Next() bool
Frequency() uint32
Bandwidth() uint8
SpreadingFactor() uint8
CodingRate() uint8
PreambleLength() uint16
TxPowerDBm() int8
SetFrequency(v uint32)
SetBandwidth(v uint8)
SetSpreadingFactor(v uint8)
SetCodingRate(v uint8)
SetPreambleLength(v uint16)
SetTxPowerDBm(v int8)
}
type channel struct {
frequency uint32
bandwidth uint8
spreadingFactor uint8
codingRate uint8
preambleLength uint16
txPowerDBm int8
}
// Getter functions
func (c *channel) Frequency() uint32 { return c.frequency }
func (c *channel) Bandwidth() uint8 { return c.bandwidth }
func (c *channel) SpreadingFactor() uint8 { return c.spreadingFactor }
func (c *channel) CodingRate() uint8 { return c.codingRate }
func (c *channel) PreambleLength() uint16 { return c.preambleLength }
func (c *channel) TxPowerDBm() int8 { return c.txPowerDBm }
// Set functions
func (c *channel) SetFrequency(v uint32) { c.frequency = v }
func (c *channel) SetBandwidth(v uint8) { c.bandwidth = v }
func (c *channel) SetSpreadingFactor(v uint8) { c.spreadingFactor = v }
func (c *channel) SetCodingRate(v uint8) { c.codingRate = v }
func (c *channel) SetPreambleLength(v uint16) { c.preambleLength = v }
func (c *channel) SetTxPowerDBm(v int8) { c.txPowerDBm = v }
+25 -19
View File
@@ -7,37 +7,43 @@ const (
EU868_DEFAULT_TX_POWER_DBM = 20
)
type ChannelEU struct {
channel
type RegionSettingsEU868 struct {
joinRequestChannel *Channel
joinAcceptChannel *Channel
uplinkChannel *Channel
}
func (c *ChannelEU) Next() bool {
return false
}
type SettingsEU868 struct {
settings
}
func EU868() *SettingsEU868 {
return &SettingsEU868{settings: settings{
joinRequestChannel: &ChannelEU{channel: channel{lora.MHz_868_1,
func EU868() *RegionSettingsEU868 {
return &RegionSettingsEU868{
joinRequestChannel: &Channel{lora.MHz_868_1,
lora.Bandwidth_125_0,
lora.SpreadingFactor9,
lora.CodingRate4_7,
EU868_DEFAULT_PREAMBLE_LEN,
EU868_DEFAULT_TX_POWER_DBM}},
joinAcceptChannel: &ChannelEU{channel: channel{lora.MHz_868_1,
EU868_DEFAULT_TX_POWER_DBM},
joinAcceptChannel: &Channel{lora.MHz_868_1,
lora.Bandwidth_125_0,
lora.SpreadingFactor9,
lora.CodingRate4_7,
EU868_DEFAULT_PREAMBLE_LEN,
EU868_DEFAULT_TX_POWER_DBM}},
uplinkChannel: &ChannelEU{channel: channel{lora.MHz_868_1,
EU868_DEFAULT_TX_POWER_DBM},
uplinkChannel: &Channel{lora.MHz_868_1,
lora.Bandwidth_125_0,
lora.SpreadingFactor9,
lora.CodingRate4_7,
EU868_DEFAULT_PREAMBLE_LEN,
EU868_DEFAULT_TX_POWER_DBM}},
}}
EU868_DEFAULT_TX_POWER_DBM},
}
}
func (r *RegionSettingsEU868) JoinRequestChannel() *Channel {
return r.joinRequestChannel
}
func (r *RegionSettingsEU868) JoinAcceptChannel() *Channel {
return r.joinAcceptChannel
}
func (r *RegionSettingsEU868) UplinkChannel() *Channel {
return r.uplinkChannel
}
+16
View File
@@ -0,0 +1,16 @@
package region
type Channel struct {
Frequency uint32
Bandwidth uint8
SpreadingFactor uint8
CodingRate uint8
PreambleLength uint16
TxPowerDBm int8
}
type RegionSettings interface {
JoinRequestChannel() *Channel
JoinAcceptChannel() *Channel
UplinkChannel() *Channel
}
-25
View File
@@ -1,25 +0,0 @@
package region
type Settings interface {
JoinRequestChannel() Channel
JoinAcceptChannel() Channel
UplinkChannel() Channel
}
type settings struct {
joinRequestChannel Channel
joinAcceptChannel Channel
uplinkChannel Channel
}
func (r *settings) JoinRequestChannel() Channel {
return r.joinRequestChannel
}
func (r *settings) JoinAcceptChannel() Channel {
return r.joinAcceptChannel
}
func (r *settings) UplinkChannel() Channel {
return r.uplinkChannel
}
+31 -62
View File
@@ -3,80 +3,49 @@ package region
import "tinygo.org/x/drivers/lora"
const (
US915_DEFAULT_PREAMBLE_LEN = 8
US915_DEFAULT_TX_POWER_DBM = 20
US915_FREQUENCY_INCREMENT_DR_0 = 200000 // only for 125 kHz Bandwidth
US915_FREQUENCY_INCREMENT_DR_4 = 1600000 // only for 500 kHz Bandwidth
US915_DEFAULT_PREAMBLE_LEN = 8
US915_DEFAULT_TX_POWER_DBM = 20
)
type ChannelUS struct {
channel
type RegionSettingsUS915 struct {
joinRequestChannel *Channel
joinAcceptChannel *Channel
uplinkChannel *Channel
}
func (c *ChannelUS) Next() bool {
switch c.Bandwidth() {
case lora.Bandwidth_125_0:
freq, ok := stepFrequency125(c.frequency)
if ok {
c.frequency = freq
} else {
c.frequency = lora.Mhz_903_0
c.bandwidth = lora.Bandwidth_500_0
}
case lora.Bandwidth_500_0:
freq, ok := stepFrequency500(c.frequency)
if ok {
c.frequency = freq
} else {
// there are no more frequencies to check after sweeping all 8 500 kHz channels
return false
}
}
// see https://www.thethingsnetwork.org/docs/lorawan/regional-parameters/#us902-928-ism-band
return true
}
func stepFrequency125(freq uint32) (uint32, bool) {
f := freq + US915_FREQUENCY_INCREMENT_DR_0
if f >= lora.MHZ_915_0 {
return 0, false
}
return f, true
}
func stepFrequency500(freq uint32) (uint32, bool) {
f := freq + US915_FREQUENCY_INCREMENT_DR_4
if f >= lora.MHZ_915_0 {
return 0, false
}
return f, true
}
type SettingsUS915 struct {
settings
}
func US915() *SettingsUS915 {
return &SettingsUS915{settings: settings{
joinRequestChannel: &ChannelUS{channel: channel{lora.MHz_902_3,
func US915() *RegionSettingsUS915 {
return &RegionSettingsUS915{
joinRequestChannel: &Channel{lora.MHz_902_3,
lora.Bandwidth_125_0,
lora.SpreadingFactor10,
lora.CodingRate4_5,
US915_DEFAULT_PREAMBLE_LEN,
US915_DEFAULT_TX_POWER_DBM}},
joinAcceptChannel: &ChannelUS{channel: channel{0,
US915_DEFAULT_TX_POWER_DBM},
joinAcceptChannel: &Channel{lora.MHz_923_3,
lora.Bandwidth_500_0,
lora.SpreadingFactor9,
lora.SpreadingFactor7,
lora.CodingRate4_5,
US915_DEFAULT_PREAMBLE_LEN,
US915_DEFAULT_TX_POWER_DBM}},
uplinkChannel: &ChannelUS{channel: channel{lora.Mhz_903_0,
lora.Bandwidth_500_0,
lora.SpreadingFactor9,
US915_DEFAULT_TX_POWER_DBM},
uplinkChannel: &Channel{lora.MHz_902_3,
lora.Bandwidth_125_0,
lora.SpreadingFactor7,
lora.CodingRate4_5,
US915_DEFAULT_PREAMBLE_LEN,
US915_DEFAULT_TX_POWER_DBM}},
}}
US915_DEFAULT_TX_POWER_DBM},
}
}
func (r *RegionSettingsUS915) JoinRequestChannel() *Channel {
return r.joinRequestChannel
}
func (r *RegionSettingsUS915) JoinAcceptChannel() *Channel {
return r.joinAcceptChannel
}
func (r *RegionSettingsUS915) UplinkChannel() *Channel {
return r.uplinkChannel
}
+1 -112
View File
@@ -236,83 +236,16 @@ func (d *Device) getMode() (byte, error) {
}
func (d *Device) configRate(speed, clock byte) error {
// TODO: add another baudrate
var cfg1, cfg2, cfg3 byte
set := true
switch clock {
case Clock16MHz:
switch speed {
case CAN5kBps:
cfg1 = mcp16mHz5kBpsCfg1
cfg2 = mcp16mHz5kBpsCfg2
cfg3 = mcp16mHz5kBpsCfg3
case CAN10kBps:
cfg1 = mcp16mHz10kBpsCfg1
cfg2 = mcp16mHz10kBpsCfg2
cfg3 = mcp16mHz10kBpsCfg3
case CAN20kBps:
cfg1 = mcp16mHz20kBpsCfg1
cfg2 = mcp16mHz20kBpsCfg2
cfg3 = mcp16mHz20kBpsCfg3
case CAN25kBps:
cfg1 = mcp16mHz25kBpsCfg1
cfg2 = mcp16mHz25kBpsCfg2
cfg3 = mcp16mHz25kBpsCfg3
case CAN31k25Bps:
cfg1 = mcp16mHz31k25BpsCfg1
cfg2 = mcp16mHz31k25BpsCfg2
cfg3 = mcp16mHz31k25BpsCfg3
case CAN33kBps:
cfg1 = mcp16mHz33kBpsCfg1
cfg2 = mcp16mHz33kBpsCfg2
cfg3 = mcp16mHz33kBpsCfg3
case CAN40kBps:
cfg1 = mcp16mHz40kBpsCfg1
cfg2 = mcp16mHz40kBpsCfg2
cfg3 = mcp16mHz40kBpsCfg3
case CAN47kBps:
cfg1 = mcp16mHz47kBpsCfg1
cfg2 = mcp16mHz47kBpsCfg2
cfg3 = mcp16mHz47kBpsCfg3
case CAN50kBps:
cfg1 = mcp16mHz50kBpsCfg1
cfg2 = mcp16mHz50kBpsCfg2
cfg3 = mcp16mHz50kBpsCfg3
case CAN80kBps:
cfg1 = mcp16mHz80kBpsCfg1
cfg2 = mcp16mHz80kBpsCfg2
cfg3 = mcp16mHz80kBpsCfg3
case CAN83k3Bps:
cfg1 = mcp16mHz83k3BpsCfg1
cfg2 = mcp16mHz83k3BpsCfg2
cfg3 = mcp16mHz83k3BpsCfg3
case CAN95kBps:
cfg1 = mcp16mHz95kBpsCfg1
cfg2 = mcp16mHz95kBpsCfg2
cfg3 = mcp16mHz95kBpsCfg3
case CAN100kBps:
cfg1 = mcp16mHz100kBpsCfg1
cfg2 = mcp16mHz100kBpsCfg2
cfg3 = mcp16mHz100kBpsCfg3
case CAN125kBps:
cfg1 = mcp16mHz125kBpsCfg1
cfg2 = mcp16mHz125kBpsCfg2
cfg3 = mcp16mHz125kBpsCfg3
case CAN200kBps:
cfg1 = mcp16mHz200kBpsCfg1
cfg2 = mcp16mHz200kBpsCfg2
cfg3 = mcp16mHz200kBpsCfg3
case CAN250kBps:
cfg1 = mcp16mHz250kBpsCfg1
cfg2 = mcp16mHz250kBpsCfg2
cfg3 = mcp16mHz250kBpsCfg3
case CAN500kBps:
cfg1 = mcp16mHz500kBpsCfg1
cfg2 = mcp16mHz500kBpsCfg2
cfg3 = mcp16mHz500kBpsCfg3
case CAN666kBps:
cfg1 = mcp16mHz666kBpsCfg1
cfg2 = mcp16mHz666kBpsCfg2
cfg3 = mcp16mHz666kBpsCfg3
case CAN1000kBps:
cfg1 = mcp16mHz1000kBpsCfg1
cfg2 = mcp16mHz1000kBpsCfg2
@@ -322,50 +255,6 @@ func (d *Device) configRate(speed, clock byte) error {
}
case Clock8MHz:
switch speed {
case CAN5kBps:
cfg1 = mcp8mHz5kBpsCfg1
cfg2 = mcp8mHz5kBpsCfg2
cfg3 = mcp8mHz5kBpsCfg3
case CAN10kBps:
cfg1 = mcp8mHz10kBpsCfg1
cfg2 = mcp8mHz10kBpsCfg2
cfg3 = mcp8mHz10kBpsCfg3
case CAN20kBps:
cfg1 = mcp8mHz20kBpsCfg1
cfg2 = mcp8mHz20kBpsCfg2
cfg3 = mcp8mHz20kBpsCfg3
case CAN31k25Bps:
cfg1 = mcp8mHz31k25BpsCfg1
cfg2 = mcp8mHz31k25BpsCfg2
cfg3 = mcp8mHz31k25BpsCfg3
case CAN40kBps:
cfg1 = mcp8mHz40kBpsCfg1
cfg2 = mcp8mHz40kBpsCfg2
cfg3 = mcp8mHz40kBpsCfg3
case CAN50kBps:
cfg1 = mcp8mHz50kBpsCfg1
cfg2 = mcp8mHz50kBpsCfg2
cfg3 = mcp8mHz50kBpsCfg3
case CAN80kBps:
cfg1 = mcp8mHz80kBpsCfg1
cfg2 = mcp8mHz80kBpsCfg2
cfg3 = mcp8mHz80kBpsCfg3
case CAN100kBps:
cfg1 = mcp8mHz100kBpsCfg1
cfg2 = mcp8mHz100kBpsCfg2
cfg3 = mcp8mHz100kBpsCfg3
case CAN125kBps:
cfg1 = mcp8mHz125kBpsCfg1
cfg2 = mcp8mHz125kBpsCfg2
cfg3 = mcp8mHz125kBpsCfg3
case CAN200kBps:
cfg1 = mcp8mHz200kBpsCfg1
cfg2 = mcp8mHz200kBpsCfg2
cfg3 = mcp8mHz200kBpsCfg3
case CAN250kBps:
cfg1 = mcp8mHz250kBpsCfg1
cfg2 = mcp8mHz250kBpsCfg2
cfg3 = mcp8mHz250kBpsCfg3
case CAN500kBps:
cfg1 = mcp8mHz500kBpsCfg1
cfg2 = mcp8mHz500kBpsCfg2
+44
View File
@@ -0,0 +1,44 @@
package net
import (
"errors"
"time"
)
var (
ErrWiFiMissingSSID = errors.New("missing SSID")
ErrWiFiConnectTimeout = errors.New("WiFi connect timeout")
)
// Adapter interface is used to communicate with the network adapter.
type Adapter interface {
// functions used to connect/disconnect to/from an access point
ConnectToAccessPoint(ssid, pass string, timeout time.Duration) error
Disconnect() error
GetClientIP() (string, error)
// these functions are used once the adapter is connected to the network
GetDNS(domain string) (string, error)
ConnectTCPSocket(addr, port string) error
ConnectSSLSocket(addr, port string) error
ConnectUDPSocket(addr, sendport, listenport string) error
DisconnectSocket() error
StartSocketSend(size int) error
Write(b []byte) (n int, err error)
ReadSocket(b []byte) (n int, err error)
IsSocketDataAvailable() bool
// FIXME: this is really specific to espat, and maybe shouldn't be part
// of the driver interface
Response(timeout int) ([]byte, error)
}
var ActiveDevice Adapter
func UseDriver(a Adapter) {
// TODO: rethink and refactor this
if ActiveDevice != nil {
panic("net.ActiveDevice is already set")
}
ActiveDevice = a
}
+253
View File
@@ -0,0 +1,253 @@
package http
import (
"io"
"net/url"
"strings"
"time"
)
// A Client is an HTTP client. Its zero value (DefaultClient) is a
// usable client that uses DefaultTransport.
//
// The Client's Transport typically has internal state (cached TCP
// connections), so Clients should be reused instead of created as
// needed. Clients are safe for concurrent use by multiple goroutines.
//
// A Client is higher-level than a RoundTripper (such as Transport)
// and additionally handles HTTP details such as cookies and
// redirects.
//
// When following redirects, the Client will forward all headers set on the
// initial Request except:
//
// • when forwarding sensitive headers like "Authorization",
// "WWW-Authenticate", and "Cookie" to untrusted targets.
// These headers will be ignored when following a redirect to a domain
// that is not a subdomain match or exact match of the initial domain.
// For example, a redirect from "foo.com" to either "foo.com" or "sub.foo.com"
// will forward the sensitive headers, but a redirect to "bar.com" will not.
//
// • when forwarding the "Cookie" header with a non-nil cookie Jar.
// Since each redirect may mutate the state of the cookie jar,
// a redirect may possibly alter a cookie set in the initial request.
// When forwarding the "Cookie" header, any mutated cookies will be omitted,
// with the expectation that the Jar will insert those mutated cookies
// with the updated values (assuming the origin matches).
// If Jar is nil, the initial cookies are forwarded without change.
type Client struct {
// Transport specifies the mechanism by which individual
// HTTP requests are made.
// If nil, DefaultTransport is used.
Transport RoundTripper
// CheckRedirect specifies the policy for handling redirects.
// If CheckRedirect is not nil, the client calls it before
// following an HTTP redirect. The arguments req and via are
// the upcoming request and the requests made already, oldest
// first. If CheckRedirect returns an error, the Client's Get
// method returns both the previous Response (with its Body
// closed) and CheckRedirect's error (wrapped in a url.Error)
// instead of issuing the Request req.
// As a special case, if CheckRedirect returns ErrUseLastResponse,
// then the most recent response is returned with its body
// unclosed, along with a nil error.
//
// If CheckRedirect is nil, the Client uses its default policy,
// which is to stop after 10 consecutive requests.
CheckRedirect func(req *Request, via []*Request) error
// Jar specifies the cookie jar.
//
// The Jar is used to insert relevant cookies into every
// outbound Request and is updated with the cookie values
// of every inbound Response. The Jar is consulted for every
// redirect that the Client follows.
//
// If Jar is nil, cookies are only sent if they are explicitly
// set on the Request.
Jar CookieJar
// Timeout specifies a time limit for requests made by this
// Client. The timeout includes connection time, any
// redirects, and reading the response body. The timer remains
// running after Get, Head, Post, or Do return and will
// interrupt reading of the Response.Body.
//
// A Timeout of zero means no timeout.
//
// The Client cancels requests to the underlying Transport
// as if the Request's Context ended.
//
// For compatibility, the Client will also use the deprecated
// CancelRequest method on Transport if found. New
// RoundTripper implementations should use the Request's Context
// for cancellation instead of implementing CancelRequest.
Timeout time.Duration
}
// DefaultClient is the default Client and is used by Get, Head, and Post.
var DefaultClient = &Client{}
// RoundTripper is an interface representing the ability to execute a
// single HTTP transaction, obtaining the Response for a given Request.
//
// A RoundTripper must be safe for concurrent use by multiple
// goroutines.
type RoundTripper interface {
// RoundTrip executes a single HTTP transaction, returning
// a Response for the provided Request.
//
// RoundTrip should not attempt to interpret the response. In
// particular, RoundTrip must return err == nil if it obtained
// a response, regardless of the response's HTTP status code.
// A non-nil err should be reserved for failure to obtain a
// response. Similarly, RoundTrip should not attempt to
// handle higher-level protocol details such as redirects,
// authentication, or cookies.
//
// RoundTrip should not modify the request, except for
// consuming and closing the Request's Body. RoundTrip may
// read fields of the request in a separate goroutine. Callers
// should not mutate or reuse the request until the Response's
// Body has been closed.
//
// RoundTrip must always close the body, including on errors,
// but depending on the implementation may do so in a separate
// goroutine even after RoundTrip returns. This means that
// callers wanting to reuse the body for subsequent requests
// must arrange to wait for the Close call before doing so.
//
// The Request's URL and Header fields must be initialized.
RoundTrip(*Request) (*Response, error)
}
// Get issues a GET to the specified URL. If the response is one of
// the following redirect codes, Get follows the redirect, up to a
// maximum of 10 redirects:
//
// 301 (Moved Permanently)
// 302 (Found)
// 303 (See Other)
// 307 (Temporary Redirect)
// 308 (Permanent Redirect)
//
// An error is returned if there were too many redirects or if there
// was an HTTP protocol error. A non-2xx response doesn't cause an
// error. Any returned error will be of type *url.Error. The url.Error
// value's Timeout method will report true if request timed out or was
// canceled.
//
// When err is nil, resp always contains a non-nil resp.Body.
// Caller should close resp.Body when done reading from it.
//
// Get is a wrapper around DefaultClient.Get.
//
// To make a request with custom headers, use NewRequest and
// DefaultClient.Do.
func Get(url string) (resp *Response, err error) {
return DefaultClient.Get(url)
}
// Get issues a GET to the specified URL. If the response is one of the
// following redirect codes, Get follows the redirect after calling the
// Client's CheckRedirect function:
//
// 301 (Moved Permanently)
// 302 (Found)
// 303 (See Other)
// 307 (Temporary Redirect)
// 308 (Permanent Redirect)
//
// An error is returned if the Client's CheckRedirect function fails
// or if there was an HTTP protocol error. A non-2xx response doesn't
// cause an error. Any returned error will be of type *url.Error. The
// url.Error value's Timeout method will report true if the request
// timed out.
//
// When err is nil, resp always contains a non-nil resp.Body.
// Caller should close resp.Body when done reading from it.
//
// To make a request with custom headers, use NewRequest and Client.Do.
func (c *Client) Get(url string) (resp *Response, err error) {
req, err := NewRequest("GET", url, nil)
if err != nil {
return nil, err
}
return c.Do(req)
}
// Post issues a POST to the specified URL.
//
// Caller should close resp.Body when done reading from it.
//
// If the provided body is an io.Closer, it is closed after the
// request.
//
// Post is a wrapper around DefaultClient.Post.
//
// To set custom headers, use NewRequest and DefaultClient.Do.
//
// See the Client.Do method documentation for details on how redirects
// are handled.
func Post(url, contentType string, body io.Reader) (resp *Response, err error) {
return DefaultClient.Post(url, contentType, body)
}
// Post issues a POST to the specified URL.
//
// Caller should close resp.Body when done reading from it.
//
// If the provided body is an io.Closer, it is closed after the
// request.
//
// To set custom headers, use NewRequest and Client.Do.
//
// See the Client.Do method documentation for details on how redirects
// are handled.
func (c *Client) Post(url, contentType string, body io.Reader) (resp *Response, err error) {
req, err := NewRequest("POST", url, body)
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", contentType)
return c.Do(req)
}
// PostForm issues a POST to the specified URL, with data's keys and
// values URL-encoded as the request body.
//
// The Content-Type header is set to application/x-www-form-urlencoded.
// To set other headers, use NewRequest and DefaultClient.Do.
//
// When err is nil, resp always contains a non-nil resp.Body.
// Caller should close resp.Body when done reading from it.
//
// PostForm is a wrapper around DefaultClient.PostForm.
//
// See the Client.Do method documentation for details on how redirects
// are handled.
//
// To make a request with a specified context.Context, use NewRequestWithContext
// and DefaultClient.Do.
func PostForm(url string, data url.Values) (resp *Response, err error) {
return DefaultClient.PostForm(url, data)
}
// PostForm issues a POST to the specified URL,
// with data's keys and values URL-encoded as the request body.
//
// The Content-Type header is set to application/x-www-form-urlencoded.
// To set other headers, use NewRequest and Client.Do.
//
// When err is nil, resp always contains a non-nil resp.Body.
// Caller should close resp.Body when done reading from it.
//
// See the Client.Do method documentation for details on how redirects
// are handled.
//
// To make a request with a specified context.Context, use NewRequestWithContext
// and Client.Do.
func (c *Client) PostForm(url string, data url.Values) (resp *Response, err error) {
return c.Post(url, "application/x-www-form-urlencoded", strings.NewReader(data.Encode()))
}
+435
View File
@@ -0,0 +1,435 @@
// 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 http
import (
"log"
"net"
"net/textproto"
"strconv"
"strings"
"time"
)
// A Cookie represents an HTTP cookie as sent in the Set-Cookie header of an
// HTTP response or the Cookie header of an HTTP request.
//
// See https://tools.ietf.org/html/rfc6265 for details.
type Cookie struct {
Name string
Value string
Path string // optional
Domain string // optional
Expires time.Time // optional
RawExpires string // for reading cookies only
// MaxAge=0 means no 'Max-Age' attribute specified.
// MaxAge<0 means delete cookie now, equivalently 'Max-Age: 0'
// MaxAge>0 means Max-Age attribute present and given in seconds
MaxAge int
Secure bool
HttpOnly bool
SameSite SameSite
Raw string
Unparsed []string // Raw text of unparsed attribute-value pairs
}
// SameSite allows a server to define a cookie attribute making it impossible for
// the browser to send this cookie along with cross-site requests. The main
// goal is to mitigate the risk of cross-origin information leakage, and provide
// some protection against cross-site request forgery attacks.
//
// See https://tools.ietf.org/html/draft-ietf-httpbis-cookie-same-site-00 for details.
type SameSite int
const (
SameSiteDefaultMode SameSite = iota + 1
SameSiteLaxMode
SameSiteStrictMode
SameSiteNoneMode
)
// readSetCookies parses all "Set-Cookie" values from
// the header h and returns the successfully parsed Cookies.
func readSetCookies(h Header) []*Cookie {
cookieCount := len(h["Set-Cookie"])
if cookieCount == 0 {
return []*Cookie{}
}
cookies := make([]*Cookie, 0, cookieCount)
for _, line := range h["Set-Cookie"] {
parts := strings.Split(textproto.TrimString(line), ";")
if len(parts) == 1 && parts[0] == "" {
continue
}
parts[0] = textproto.TrimString(parts[0])
j := strings.Index(parts[0], "=")
if j < 0 {
continue
}
name, value := parts[0][:j], parts[0][j+1:]
if !isCookieNameValid(name) {
continue
}
value, ok := parseCookieValue(value, true)
if !ok {
continue
}
c := &Cookie{
Name: name,
Value: value,
Raw: line,
}
for i := 1; i < len(parts); i++ {
parts[i] = textproto.TrimString(parts[i])
if len(parts[i]) == 0 {
continue
}
attr, val := parts[i], ""
if j := strings.Index(attr, "="); j >= 0 {
attr, val = attr[:j], attr[j+1:]
}
lowerAttr := strings.ToLower(attr)
val, ok = parseCookieValue(val, false)
if !ok {
c.Unparsed = append(c.Unparsed, parts[i])
continue
}
switch lowerAttr {
case "samesite":
lowerVal := strings.ToLower(val)
switch lowerVal {
case "lax":
c.SameSite = SameSiteLaxMode
case "strict":
c.SameSite = SameSiteStrictMode
case "none":
c.SameSite = SameSiteNoneMode
default:
c.SameSite = SameSiteDefaultMode
}
continue
case "secure":
c.Secure = true
continue
case "httponly":
c.HttpOnly = true
continue
case "domain":
c.Domain = val
continue
case "max-age":
secs, err := strconv.Atoi(val)
if err != nil || secs != 0 && val[0] == '0' {
break
}
if secs <= 0 {
secs = -1
}
c.MaxAge = secs
continue
case "expires":
c.RawExpires = val
exptime, err := time.Parse(time.RFC1123, val)
if err != nil {
exptime, err = time.Parse("Mon, 02-Jan-2006 15:04:05 MST", val)
if err != nil {
c.Expires = time.Time{}
break
}
}
c.Expires = exptime.UTC()
continue
case "path":
c.Path = val
continue
}
c.Unparsed = append(c.Unparsed, parts[i])
}
cookies = append(cookies, c)
}
return cookies
}
// SetCookie adds a Set-Cookie header to the provided ResponseWriter's headers.
// The provided cookie must have a valid Name. Invalid cookies may be
// silently dropped.
func SetCookie(w ResponseWriter, cookie *Cookie) {
if v := cookie.String(); v != "" {
w.Header().Add("Set-Cookie", v)
}
}
// String returns the serialization of the cookie for use in a Cookie
// header (if only Name and Value are set) or a Set-Cookie response
// header (if other fields are set).
// If c is nil or c.Name is invalid, the empty string is returned.
func (c *Cookie) String() string {
if c == nil || !isCookieNameValid(c.Name) {
return ""
}
// extraCookieLength derived from typical length of cookie attributes
// see RFC 6265 Sec 4.1.
const extraCookieLength = 110
var b strings.Builder
b.Grow(len(c.Name) + len(c.Value) + len(c.Domain) + len(c.Path) + extraCookieLength)
b.WriteString(c.Name)
b.WriteRune('=')
b.WriteString(sanitizeCookieValue(c.Value))
if len(c.Path) > 0 {
b.WriteString("; Path=")
b.WriteString(sanitizeCookiePath(c.Path))
}
if len(c.Domain) > 0 {
if validCookieDomain(c.Domain) {
// A c.Domain containing illegal characters is not
// sanitized but simply dropped which turns the cookie
// into a host-only cookie. A leading dot is okay
// but won't be sent.
d := c.Domain
if d[0] == '.' {
d = d[1:]
}
b.WriteString("; Domain=")
b.WriteString(d)
} else {
log.Printf("net/http: invalid Cookie.Domain %q; dropping domain attribute", c.Domain)
}
}
var buf [len(TimeFormat)]byte
if validCookieExpires(c.Expires) {
b.WriteString("; Expires=")
b.Write(c.Expires.UTC().AppendFormat(buf[:0], TimeFormat))
}
if c.MaxAge > 0 {
b.WriteString("; Max-Age=")
b.Write(strconv.AppendInt(buf[:0], int64(c.MaxAge), 10))
} else if c.MaxAge < 0 {
b.WriteString("; Max-Age=0")
}
if c.HttpOnly {
b.WriteString("; HttpOnly")
}
if c.Secure {
b.WriteString("; Secure")
}
switch c.SameSite {
case SameSiteDefaultMode:
// Skip, default mode is obtained by not emitting the attribute.
case SameSiteNoneMode:
b.WriteString("; SameSite=None")
case SameSiteLaxMode:
b.WriteString("; SameSite=Lax")
case SameSiteStrictMode:
b.WriteString("; SameSite=Strict")
}
return b.String()
}
// readCookies parses all "Cookie" values from the header h and
// returns the successfully parsed Cookies.
//
// if filter isn't empty, only cookies of that name are returned
func readCookies(h Header, filter string) []*Cookie {
lines := h["Cookie"]
if len(lines) == 0 {
return []*Cookie{}
}
cookies := make([]*Cookie, 0, len(lines)+strings.Count(lines[0], ";"))
for _, line := range lines {
line = textproto.TrimString(line)
var part string
for len(line) > 0 { // continue since we have rest
if splitIndex := strings.Index(line, ";"); splitIndex > 0 {
part, line = line[:splitIndex], line[splitIndex+1:]
} else {
part, line = line, ""
}
part = textproto.TrimString(part)
if len(part) == 0 {
continue
}
name, val := part, ""
if j := strings.Index(part, "="); j >= 0 {
name, val = name[:j], name[j+1:]
}
if !isCookieNameValid(name) {
continue
}
if filter != "" && filter != name {
continue
}
val, ok := parseCookieValue(val, true)
if !ok {
continue
}
cookies = append(cookies, &Cookie{Name: name, Value: val})
}
}
return cookies
}
// validCookieDomain reports whether v is a valid cookie domain-value.
func validCookieDomain(v string) bool {
if isCookieDomainName(v) {
return true
}
if net.ParseIP(v) != nil && !strings.Contains(v, ":") {
return true
}
return false
}
// validCookieExpires reports whether v is a valid cookie expires-value.
func validCookieExpires(t time.Time) bool {
// IETF RFC 6265 Section 5.1.1.5, the year must not be less than 1601
return t.Year() >= 1601
}
// isCookieDomainName reports whether s is a valid domain name or a valid
// domain name with a leading dot '.'. It is almost a direct copy of
// package net's isDomainName.
func isCookieDomainName(s string) bool {
if len(s) == 0 {
return false
}
if len(s) > 255 {
return false
}
if s[0] == '.' {
// A cookie a domain attribute may start with a leading dot.
s = s[1:]
}
last := byte('.')
ok := false // Ok once we've seen a letter.
partlen := 0
for i := 0; i < len(s); i++ {
c := s[i]
switch {
default:
return false
case 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z':
// No '_' allowed here (in contrast to package net).
ok = true
partlen++
case '0' <= c && c <= '9':
// fine
partlen++
case c == '-':
// Byte before dash cannot be dot.
if last == '.' {
return false
}
partlen++
case c == '.':
// Byte before dot cannot be dot, dash.
if last == '.' || last == '-' {
return false
}
if partlen > 63 || partlen == 0 {
return false
}
partlen = 0
}
last = c
}
if last == '-' || partlen > 63 {
return false
}
return ok
}
var cookieNameSanitizer = strings.NewReplacer("\n", "-", "\r", "-")
func sanitizeCookieName(n string) string {
return cookieNameSanitizer.Replace(n)
}
// sanitizeCookieValue produces a suitable cookie-value from v.
// https://tools.ietf.org/html/rfc6265#section-4.1.1
// cookie-value = *cookie-octet / ( DQUOTE *cookie-octet DQUOTE )
// cookie-octet = %x21 / %x23-2B / %x2D-3A / %x3C-5B / %x5D-7E
//
// ; US-ASCII characters excluding CTLs,
// ; whitespace DQUOTE, comma, semicolon,
// ; and backslash
//
// We loosen this as spaces and commas are common in cookie values
// but we produce a quoted cookie-value if and only if v contains
// commas or spaces.
// See https://golang.org/issue/7243 for the discussion.
func sanitizeCookieValue(v string) string {
v = sanitizeOrWarn("Cookie.Value", validCookieValueByte, v)
if len(v) == 0 {
return v
}
if strings.IndexByte(v, ' ') >= 0 || strings.IndexByte(v, ',') >= 0 {
return `"` + v + `"`
}
return v
}
func validCookieValueByte(b byte) bool {
return 0x20 <= b && b < 0x7f && b != '"' && b != ';' && b != '\\'
}
// path-av = "Path=" path-value
// path-value = <any CHAR except CTLs or ";">
func sanitizeCookiePath(v string) string {
return sanitizeOrWarn("Cookie.Path", validCookiePathByte, v)
}
func validCookiePathByte(b byte) bool {
return 0x20 <= b && b < 0x7f && b != ';'
}
func sanitizeOrWarn(fieldName string, valid func(byte) bool, v string) string {
ok := true
for i := 0; i < len(v); i++ {
if valid(v[i]) {
continue
}
log.Printf("net/http: invalid byte %q in %s; dropping invalid bytes", v[i], fieldName)
ok = false
break
}
if ok {
return v
}
buf := make([]byte, 0, len(v))
for i := 0; i < len(v); i++ {
if b := v[i]; valid(b) {
buf = append(buf, b)
}
}
return string(buf)
}
func parseCookieValue(raw string, allowDoubleQuote bool) (string, bool) {
// Strip the quotes, if present.
if allowDoubleQuote && len(raw) > 1 && raw[0] == '"' && raw[len(raw)-1] == '"' {
raw = raw[1 : len(raw)-1]
}
for i := 0; i < len(raw); i++ {
if !validCookieValueByte(raw[i]) {
return "", false
}
}
return raw, true
}
func isCookieNameValid(raw string) bool {
if raw == "" {
return false
}
return strings.IndexFunc(raw, isNotToken) < 0
}
+504
View File
@@ -0,0 +1,504 @@
// Copyright 2012 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 cookiejar implements an in-memory RFC 6265-compliant http.CookieJar.
package cookiejar
import (
"errors"
"fmt"
"net/url"
"sort"
"strings"
"sync"
"time"
"tinygo.org/x/drivers/net"
"tinygo.org/x/drivers/net/http"
)
// PublicSuffixList provides the public suffix of a domain. For example:
// - the public suffix of "example.com" is "com",
// - the public suffix of "foo1.foo2.foo3.co.uk" is "co.uk", and
// - the public suffix of "bar.pvt.k12.ma.us" is "pvt.k12.ma.us".
//
// Implementations of PublicSuffixList must be safe for concurrent use by
// multiple goroutines.
//
// An implementation that always returns "" is valid and may be useful for
// testing but it is not secure: it means that the HTTP server for foo.com can
// set a cookie for bar.com.
//
// A public suffix list implementation is in the package
// golang.org/x/net/publicsuffix.
type PublicSuffixList interface {
// PublicSuffix returns the public suffix of domain.
//
// TODO: specify which of the caller and callee is responsible for IP
// addresses, for leading and trailing dots, for case sensitivity, and
// for IDN/Punycode.
PublicSuffix(domain string) string
// String returns a description of the source of this public suffix
// list. The description will typically contain something like a time
// stamp or version number.
String() string
}
// Options are the options for creating a new Jar.
type Options struct {
// PublicSuffixList is the public suffix list that determines whether
// an HTTP server can set a cookie for a domain.
//
// A nil value is valid and may be useful for testing but it is not
// secure: it means that the HTTP server for foo.co.uk can set a cookie
// for bar.co.uk.
PublicSuffixList PublicSuffixList
}
// Jar implements the http.CookieJar interface from the net/http package.
type Jar struct {
psList PublicSuffixList
// mu locks the remaining fields.
mu sync.Mutex
// entries is a set of entries, keyed by their eTLD+1 and subkeyed by
// their name/domain/path.
entries map[string]map[string]entry
// nextSeqNum is the next sequence number assigned to a new cookie
// created SetCookies.
nextSeqNum uint64
}
// New returns a new cookie jar. A nil *Options is equivalent to a zero
// Options.
func New(o *Options) (*Jar, error) {
jar := &Jar{
entries: make(map[string]map[string]entry),
}
if o != nil {
jar.psList = o.PublicSuffixList
}
return jar, nil
}
// entry is the internal representation of a cookie.
//
// This struct type is not used outside of this package per se, but the exported
// fields are those of RFC 6265.
type entry struct {
Name string
Value string
Domain string
Path string
SameSite string
Secure bool
HttpOnly bool
Persistent bool
HostOnly bool
Expires time.Time
Creation time.Time
LastAccess time.Time
// seqNum is a sequence number so that Cookies returns cookies in a
// deterministic order, even for cookies that have equal Path length and
// equal Creation time. This simplifies testing.
seqNum uint64
}
// id returns the domain;path;name triple of e as an id.
func (e *entry) id() string {
return fmt.Sprintf("%s;%s;%s", e.Domain, e.Path, e.Name)
}
// shouldSend determines whether e's cookie qualifies to be included in a
// request to host/path. It is the caller's responsibility to check if the
// cookie is expired.
func (e *entry) shouldSend(https bool, host, path string) bool {
return e.domainMatch(host) && e.pathMatch(path) && (https || !e.Secure)
}
// domainMatch implements "domain-match" of RFC 6265 section 5.1.3.
func (e *entry) domainMatch(host string) bool {
if e.Domain == host {
return true
}
return !e.HostOnly && hasDotSuffix(host, e.Domain)
}
// pathMatch implements "path-match" according to RFC 6265 section 5.1.4.
func (e *entry) pathMatch(requestPath string) bool {
if requestPath == e.Path {
return true
}
if strings.HasPrefix(requestPath, e.Path) {
if e.Path[len(e.Path)-1] == '/' {
return true // The "/any/" matches "/any/path" case.
} else if requestPath[len(e.Path)] == '/' {
return true // The "/any" matches "/any/path" case.
}
}
return false
}
// hasDotSuffix reports whether s ends in "."+suffix.
func hasDotSuffix(s, suffix string) bool {
return len(s) > len(suffix) && s[len(s)-len(suffix)-1] == '.' && s[len(s)-len(suffix):] == suffix
}
// Cookies implements the Cookies method of the http.CookieJar interface.
//
// It returns an empty slice if the URL's scheme is not HTTP or HTTPS.
func (j *Jar) Cookies(u *url.URL) (cookies []*http.Cookie) {
return j.cookies(u, time.Now())
}
// cookies is like Cookies but takes the current time as a parameter.
func (j *Jar) cookies(u *url.URL, now time.Time) (cookies []*http.Cookie) {
if u.Scheme != "http" && u.Scheme != "https" {
return cookies
}
host, err := canonicalHost(u.Host)
if err != nil {
return cookies
}
key := jarKey(host, j.psList)
j.mu.Lock()
defer j.mu.Unlock()
submap := j.entries[key]
if submap == nil {
return cookies
}
https := u.Scheme == "https"
path := u.Path
if path == "" {
path = "/"
}
modified := false
var selected []entry
for id, e := range submap {
if e.Persistent && !e.Expires.After(now) {
delete(submap, id)
modified = true
continue
}
if !e.shouldSend(https, host, path) {
continue
}
e.LastAccess = now
submap[id] = e
selected = append(selected, e)
modified = true
}
if modified {
if len(submap) == 0 {
delete(j.entries, key)
} else {
j.entries[key] = submap
}
}
// sort according to RFC 6265 section 5.4 point 2: by longest
// path and then by earliest creation time.
sort.Slice(selected, func(i, j int) bool {
s := selected
if len(s[i].Path) != len(s[j].Path) {
return len(s[i].Path) > len(s[j].Path)
}
if !s[i].Creation.Equal(s[j].Creation) {
return s[i].Creation.Before(s[j].Creation)
}
return s[i].seqNum < s[j].seqNum
})
for _, e := range selected {
cookies = append(cookies, &http.Cookie{Name: e.Name, Value: e.Value})
}
return cookies
}
// SetCookies implements the SetCookies method of the http.CookieJar interface.
//
// It does nothing if the URL's scheme is not HTTP or HTTPS.
func (j *Jar) SetCookies(u *url.URL, cookies []*http.Cookie) {
j.setCookies(u, cookies, time.Now())
}
// setCookies is like SetCookies but takes the current time as parameter.
func (j *Jar) setCookies(u *url.URL, cookies []*http.Cookie, now time.Time) {
if len(cookies) == 0 {
return
}
if u.Scheme != "http" && u.Scheme != "https" {
return
}
host, err := canonicalHost(u.Host)
if err != nil {
return
}
key := jarKey(host, j.psList)
defPath := defaultPath(u.Path)
j.mu.Lock()
defer j.mu.Unlock()
submap := j.entries[key]
modified := false
for _, cookie := range cookies {
e, remove, err := j.newEntry(cookie, now, defPath, host)
if err != nil {
continue
}
id := e.id()
if remove {
if submap != nil {
if _, ok := submap[id]; ok {
delete(submap, id)
modified = true
}
}
continue
}
if submap == nil {
submap = make(map[string]entry)
}
if old, ok := submap[id]; ok {
e.Creation = old.Creation
e.seqNum = old.seqNum
} else {
e.Creation = now
e.seqNum = j.nextSeqNum
j.nextSeqNum++
}
e.LastAccess = now
submap[id] = e
modified = true
}
if modified {
if len(submap) == 0 {
delete(j.entries, key)
} else {
j.entries[key] = submap
}
}
}
// canonicalHost strips port from host if present and returns the canonicalized
// host name.
func canonicalHost(host string) (string, error) {
var err error
host = strings.ToLower(host)
if hasPort(host) {
host, _, err = net.SplitHostPort(host)
if err != nil {
return "", err
}
}
if strings.HasSuffix(host, ".") {
// Strip trailing dot from fully qualified domain names.
host = host[:len(host)-1]
}
return toASCII(host)
}
// hasPort reports whether host contains a port number. host may be a host
// name, an IPv4 or an IPv6 address.
func hasPort(host string) bool {
colons := strings.Count(host, ":")
if colons == 0 {
return false
}
if colons == 1 {
return true
}
return host[0] == '[' && strings.Contains(host, "]:")
}
// jarKey returns the key to use for a jar.
func jarKey(host string, psl PublicSuffixList) string {
if isIP(host) {
return host
}
var i int
if psl == nil {
i = strings.LastIndex(host, ".")
if i <= 0 {
return host
}
} else {
suffix := psl.PublicSuffix(host)
if suffix == host {
return host
}
i = len(host) - len(suffix)
if i <= 0 || host[i-1] != '.' {
// The provided public suffix list psl is broken.
// Storing cookies under host is a safe stopgap.
return host
}
// Only len(suffix) is used to determine the jar key from
// here on, so it is okay if psl.PublicSuffix("www.buggy.psl")
// returns "com" as the jar key is generated from host.
}
prevDot := strings.LastIndex(host[:i-1], ".")
return host[prevDot+1:]
}
// isIP reports whether host is an IP address.
func isIP(host string) bool {
return net.ParseIP(host) != nil
}
// defaultPath returns the directory part of an URL's path according to
// RFC 6265 section 5.1.4.
func defaultPath(path string) string {
if len(path) == 0 || path[0] != '/' {
return "/" // Path is empty or malformed.
}
i := strings.LastIndex(path, "/") // Path starts with "/", so i != -1.
if i == 0 {
return "/" // Path has the form "/abc".
}
return path[:i] // Path is either of form "/abc/xyz" or "/abc/xyz/".
}
// newEntry creates an entry from a http.Cookie c. now is the current time and
// is compared to c.Expires to determine deletion of c. defPath and host are the
// default-path and the canonical host name of the URL c was received from.
//
// remove records whether the jar should delete this cookie, as it has already
// expired with respect to now. In this case, e may be incomplete, but it will
// be valid to call e.id (which depends on e's Name, Domain and Path).
//
// A malformed c.Domain will result in an error.
func (j *Jar) newEntry(c *http.Cookie, now time.Time, defPath, host string) (e entry, remove bool, err error) {
e.Name = c.Name
if c.Path == "" || c.Path[0] != '/' {
e.Path = defPath
} else {
e.Path = c.Path
}
e.Domain, e.HostOnly, err = j.domainAndType(host, c.Domain)
if err != nil {
return e, false, err
}
// MaxAge takes precedence over Expires.
if c.MaxAge < 0 {
return e, true, nil
} else if c.MaxAge > 0 {
e.Expires = now.Add(time.Duration(c.MaxAge) * time.Second)
e.Persistent = true
} else {
if c.Expires.IsZero() {
e.Expires = endOfTime
e.Persistent = false
} else {
if !c.Expires.After(now) {
return e, true, nil
}
e.Expires = c.Expires
e.Persistent = true
}
}
e.Value = c.Value
e.Secure = c.Secure
e.HttpOnly = c.HttpOnly
switch c.SameSite {
case http.SameSiteDefaultMode:
e.SameSite = "SameSite"
case http.SameSiteStrictMode:
e.SameSite = "SameSite=Strict"
case http.SameSiteLaxMode:
e.SameSite = "SameSite=Lax"
}
return e, false, nil
}
var (
errIllegalDomain = errors.New("cookiejar: illegal cookie domain attribute")
errMalformedDomain = errors.New("cookiejar: malformed cookie domain attribute")
errNoHostname = errors.New("cookiejar: no host name available (IP only)")
)
// endOfTime is the time when session (non-persistent) cookies expire.
// This instant is representable in most date/time formats (not just
// Go's time.Time) and should be far enough in the future.
var endOfTime = time.Date(9999, 12, 31, 23, 59, 59, 0, time.UTC)
// domainAndType determines the cookie's domain and hostOnly attribute.
func (j *Jar) domainAndType(host, domain string) (string, bool, error) {
if domain == "" {
// No domain attribute in the SetCookie header indicates a
// host cookie.
return host, true, nil
}
if isIP(host) {
// According to RFC 6265 domain-matching includes not being
// an IP address.
// TODO: This might be relaxed as in common browsers.
return "", false, errNoHostname
}
// From here on: If the cookie is valid, it is a domain cookie (with
// the one exception of a public suffix below).
// See RFC 6265 section 5.2.3.
if domain[0] == '.' {
domain = domain[1:]
}
if len(domain) == 0 || domain[0] == '.' {
// Received either "Domain=." or "Domain=..some.thing",
// both are illegal.
return "", false, errMalformedDomain
}
domain = strings.ToLower(domain)
if domain[len(domain)-1] == '.' {
// We received stuff like "Domain=www.example.com.".
// Browsers do handle such stuff (actually differently) but
// RFC 6265 seems to be clear here (e.g. section 4.1.2.3) in
// requiring a reject. 4.1.2.3 is not normative, but
// "Domain Matching" (5.1.3) and "Canonicalized Host Names"
// (5.1.2) are.
return "", false, errMalformedDomain
}
// See RFC 6265 section 5.3 #5.
if j.psList != nil {
if ps := j.psList.PublicSuffix(domain); ps != "" && !hasDotSuffix(domain, ps) {
if host == domain {
// This is the one exception in which a cookie
// with a domain attribute is a host cookie.
return host, true, nil
}
return "", false, errIllegalDomain
}
}
// The domain must domain-match host: www.mycompany.com cannot
// set cookies for .ourcompetitors.com.
if host != domain && !hasDotSuffix(host, domain) {
return "", false, errIllegalDomain
}
return domain, false, nil
}
+159
View File
@@ -0,0 +1,159 @@
// Copyright 2012 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 cookiejar
// This file implements the Punycode algorithm from RFC 3492.
import (
"fmt"
"strings"
"unicode/utf8"
)
// These parameter values are specified in section 5.
//
// All computation is done with int32s, so that overflow behavior is identical
// regardless of whether int is 32-bit or 64-bit.
const (
base int32 = 36
damp int32 = 700
initialBias int32 = 72
initialN int32 = 128
skew int32 = 38
tmax int32 = 26
tmin int32 = 1
)
// encode encodes a string as specified in section 6.3 and prepends prefix to
// the result.
//
// The "while h < length(input)" line in the specification becomes "for
// remaining != 0" in the Go code, because len(s) in Go is in bytes, not runes.
func encode(prefix, s string) (string, error) {
output := make([]byte, len(prefix), len(prefix)+1+2*len(s))
copy(output, prefix)
delta, n, bias := int32(0), initialN, initialBias
b, remaining := int32(0), int32(0)
for _, r := range s {
if r < utf8.RuneSelf {
b++
output = append(output, byte(r))
} else {
remaining++
}
}
h := b
if b > 0 {
output = append(output, '-')
}
for remaining != 0 {
m := int32(0x7fffffff)
for _, r := range s {
if m > r && r >= n {
m = r
}
}
delta += (m - n) * (h + 1)
if delta < 0 {
return "", fmt.Errorf("cookiejar: invalid label %q", s)
}
n = m
for _, r := range s {
if r < n {
delta++
if delta < 0 {
return "", fmt.Errorf("cookiejar: invalid label %q", s)
}
continue
}
if r > n {
continue
}
q := delta
for k := base; ; k += base {
t := k - bias
if t < tmin {
t = tmin
} else if t > tmax {
t = tmax
}
if q < t {
break
}
output = append(output, encodeDigit(t+(q-t)%(base-t)))
q = (q - t) / (base - t)
}
output = append(output, encodeDigit(q))
bias = adapt(delta, h+1, h == b)
delta = 0
h++
remaining--
}
delta++
n++
}
return string(output), nil
}
func encodeDigit(digit int32) byte {
switch {
case 0 <= digit && digit < 26:
return byte(digit + 'a')
case 26 <= digit && digit < 36:
return byte(digit + ('0' - 26))
}
panic("cookiejar: internal error in punycode encoding")
}
// adapt is the bias adaptation function specified in section 6.1.
func adapt(delta, numPoints int32, firstTime bool) int32 {
if firstTime {
delta /= damp
} else {
delta /= 2
}
delta += delta / numPoints
k := int32(0)
for delta > ((base-tmin)*tmax)/2 {
delta /= base - tmin
k += base
}
return k + (base-tmin+1)*delta/(delta+skew)
}
// Strictly speaking, the remaining code below deals with IDNA (RFC 5890 and
// friends) and not Punycode (RFC 3492) per se.
// acePrefix is the ASCII Compatible Encoding prefix.
const acePrefix = "xn--"
// toASCII converts a domain or domain label to its ASCII form. For example,
// toASCII("bücher.example.com") is "xn--bcher-kva.example.com", and
// toASCII("golang") is "golang".
func toASCII(s string) (string, error) {
if ascii(s) {
return s, nil
}
labels := strings.Split(s, ".")
for i, label := range labels {
if !ascii(label) {
a, err := encode(acePrefix, label)
if err != nil {
return "", err
}
labels[i] = a
}
}
return strings.Join(labels, "."), nil
}
func ascii(s string) bool {
for i := 0; i < len(s); i++ {
if s[i] >= utf8.RuneSelf {
return false
}
}
return true
}
+15
View File
@@ -0,0 +1,15 @@
package http
type DeviceDriver interface {
ListenAndServe(addr string, handler Handler) error
}
var ActiveDevice DeviceDriver
func UseDriver(driver DeviceDriver) {
// TODO: rethink and refactor this
if ActiveDevice != nil {
panic("net.ActiveDevice is already set")
}
ActiveDevice = driver
}
+259
View File
@@ -0,0 +1,259 @@
package http
import (
"io"
"net/http/httptrace"
"net/textproto"
"sort"
"strings"
"sync"
"time"
)
// A Header represents the key-value pairs in an HTTP header.
//
// The keys should be in canonical form, as returned by
// CanonicalHeaderKey.
type Header map[string][]string
// Add adds the key, value pair to the header.
// It appends to any existing values associated with key.
// The key is case insensitive; it is canonicalized by
// CanonicalHeaderKey.
func (h Header) Add(key, value string) {
textproto.MIMEHeader(h).Add(key, value)
}
// Set sets the header entries associated with key to the
// single element value. It replaces any existing values
// associated with key. The key is case insensitive; it is
// canonicalized by textproto.CanonicalMIMEHeaderKey.
// To use non-canonical keys, assign to the map directly.
func (h Header) Set(key, value string) {
textproto.MIMEHeader(h).Set(key, value)
}
// Get gets the first value associated with the given key. If
// there are no values associated with the key, Get returns "".
// It is case insensitive; textproto.CanonicalMIMEHeaderKey is
// used to canonicalize the provided key. To use non-canonical keys,
// access the map directly.
func (h Header) Get(key string) string {
return textproto.MIMEHeader(h).Get(key)
}
// Values returns all values associated with the given key.
// It is case insensitive; textproto.CanonicalMIMEHeaderKey is
// used to canonicalize the provided key. To use non-canonical
// keys, access the map directly.
// The returned slice is not a copy.
func (h Header) Values(key string) []string {
return textproto.MIMEHeader(h).Values(key)
}
// get is like Get, but key must already be in CanonicalHeaderKey form.
func (h Header) get(key string) string {
if v := h[key]; len(v) > 0 {
return v[0]
}
return ""
}
// has reports whether h has the provided key defined, even if it's
// set to 0-length slice.
func (h Header) has(key string) bool {
_, ok := h[key]
return ok
}
// Del deletes the values associated with key.
// The key is case insensitive; it is canonicalized by
// CanonicalHeaderKey.
func (h Header) Del(key string) {
textproto.MIMEHeader(h).Del(key)
}
// Write writes a header in wire format.
func (h Header) Write(w io.Writer) error {
return h.write(w, nil)
}
func (h Header) write(w io.Writer, trace *httptrace.ClientTrace) error {
return h.writeSubset(w, nil, trace)
}
// Clone returns a copy of h or nil if h is nil.
func (h Header) Clone() Header {
if h == nil {
return nil
}
// Find total number of values.
nv := 0
for _, vv := range h {
nv += len(vv)
}
sv := make([]string, nv) // shared backing array for headers' values
h2 := make(Header, len(h))
for k, vv := range h {
n := copy(sv, vv)
h2[k] = sv[:n:n]
sv = sv[n:]
}
return h2
}
var timeFormats = []string{
TimeFormat,
time.RFC850,
time.ANSIC,
}
// ParseTime parses a time header (such as the Date: header),
// trying each of the three formats allowed by HTTP/1.1:
// TimeFormat, time.RFC850, and time.ANSIC.
func ParseTime(text string) (t time.Time, err error) {
for _, layout := range timeFormats {
t, err = time.Parse(layout, text)
if err == nil {
return
}
}
return
}
var headerNewlineToSpace = strings.NewReplacer("\n", " ", "\r", " ")
// stringWriter implements WriteString on a Writer.
type stringWriter struct {
w io.Writer
}
func (w stringWriter) WriteString(s string) (n int, err error) {
return w.w.Write([]byte(s))
}
type keyValues struct {
key string
values []string
}
// A headerSorter implements sort.Interface by sorting a []keyValues
// by key. It's used as a pointer, so it can fit in a sort.Interface
// interface value without allocation.
type headerSorter struct {
kvs []keyValues
}
func (s *headerSorter) Len() int { return len(s.kvs) }
func (s *headerSorter) Swap(i, j int) { s.kvs[i], s.kvs[j] = s.kvs[j], s.kvs[i] }
func (s *headerSorter) Less(i, j int) bool { return s.kvs[i].key < s.kvs[j].key }
var headerSorterPool = sync.Pool{
New: func() interface{} { return new(headerSorter) },
}
// sortedKeyValues returns h's keys sorted in the returned kvs
// slice. The headerSorter used to sort is also returned, for possible
// return to headerSorterCache.
func (h Header) sortedKeyValues(exclude map[string]bool) (kvs []keyValues, hs *headerSorter) {
hs = headerSorterPool.Get().(*headerSorter)
if cap(hs.kvs) < len(h) {
hs.kvs = make([]keyValues, 0, len(h))
}
kvs = hs.kvs[:0]
for k, vv := range h {
if !exclude[k] {
kvs = append(kvs, keyValues{k, vv})
}
}
hs.kvs = kvs
sort.Sort(hs)
return kvs, hs
}
// WriteSubset writes a header in wire format.
// If exclude is not nil, keys where exclude[key] == true are not written.
// Keys are not canonicalized before checking the exclude map.
func (h Header) WriteSubset(w io.Writer, exclude map[string]bool) error {
return h.writeSubset(w, exclude, nil)
}
func (h Header) writeSubset(w io.Writer, exclude map[string]bool, trace *httptrace.ClientTrace) error {
ws, ok := w.(io.StringWriter)
if !ok {
ws = stringWriter{w}
}
kvs, sorter := h.sortedKeyValues(exclude)
var formattedVals []string
for _, kv := range kvs {
for _, v := range kv.values {
v = headerNewlineToSpace.Replace(v)
v = textproto.TrimString(v)
for _, s := range []string{kv.key, ": ", v, "\r\n"} {
if _, err := ws.WriteString(s); err != nil {
headerSorterPool.Put(sorter)
return err
}
}
if trace != nil && trace.WroteHeaderField != nil {
formattedVals = append(formattedVals, v)
}
}
if trace != nil && trace.WroteHeaderField != nil {
trace.WroteHeaderField(kv.key, formattedVals)
formattedVals = nil
}
}
headerSorterPool.Put(sorter)
return nil
}
// CanonicalHeaderKey returns the canonical format of the
// header key s. The canonicalization converts the first
// letter and any letter following a hyphen to upper case;
// the rest are converted to lowercase. For example, the
// canonical key for "accept-encoding" is "Accept-Encoding".
// If s contains a space or invalid header field bytes, it is
// returned without modifications.
func CanonicalHeaderKey(s string) string { return textproto.CanonicalMIMEHeaderKey(s) }
// hasToken reports whether token appears with v, ASCII
// case-insensitive, with space or comma boundaries.
// token must be all lowercase.
// v may contain mixed cased.
func hasToken(v, token string) bool {
if len(token) > len(v) || token == "" {
return false
}
if v == token {
return true
}
for sp := 0; sp <= len(v)-len(token); sp++ {
// Check that first character is good.
// The token is ASCII, so checking only a single byte
// is sufficient. We skip this potential starting
// position if both the first byte and its potential
// ASCII uppercase equivalent (b|0x20) don't match.
// False positives ('^' => '~') are caught by EqualFold.
if b := v[sp]; b != token[0] && b|0x20 != token[0] {
continue
}
// Check that start pos is on a valid token boundary.
if sp > 0 && !isTokenBoundary(v[sp-1]) {
continue
}
// Check that end pos is on a valid token boundary.
if endPos := sp + len(token); endPos != len(v) && !isTokenBoundary(v[endPos]) {
continue
}
if strings.EqualFold(v[sp:sp+len(token)], token) {
return true
}
}
return false
}
func isTokenBoundary(b byte) bool {
return b == ' ' || b == ',' || b == '\t'
}
+162
View File
@@ -0,0 +1,162 @@
package http
import (
"io"
"strconv"
"strings"
"time"
"unicode/utf8"
"golang.org/x/net/http/httpguts"
)
// incomparable is a zero-width, non-comparable type. Adding it to a struct
// makes that struct also non-comparable, and generally doesn't add
// any size (as long as it's first).
type incomparable [0]func()
// maxInt64 is the effective "infinite" value for the Server and
// Transport's byte-limiting readers.
const maxInt64 = 1<<63 - 1
// aLongTimeAgo is a non-zero time, far in the past, used for
// immediate cancellation of network operations.
var aLongTimeAgo = time.Unix(1, 0)
// omitBundledHTTP2 is set by omithttp2.go when the nethttpomithttp2
// build tag is set. That means h2_bundle.go isn't compiled in and we
// shouldn't try to use it.
var omitBundledHTTP2 bool
// TODO(bradfitz): move common stuff here. The other files have accumulated
// generic http stuff in random places.
// contextKey is a value for use with context.WithValue. It's used as
// a pointer so it fits in an interface{} without allocation.
type contextKey struct {
name string
}
func (k *contextKey) String() string { return "net/http context value " + k.name }
// Given a string of the form "host", "host:port", or "[ipv6::address]:port",
// return true if the string includes a port.
func hasPort(s string) bool { return strings.LastIndex(s, ":") > strings.LastIndex(s, "]") }
// removeEmptyPort strips the empty port in ":port" to ""
// as mandated by RFC 3986 Section 6.2.3.
func removeEmptyPort(host string) string {
if hasPort(host) {
return strings.TrimSuffix(host, ":")
}
return host
}
func isNotToken(r rune) bool {
return !httpguts.IsTokenRune(r)
}
func isASCII(s string) bool {
for i := 0; i < len(s); i++ {
if s[i] >= utf8.RuneSelf {
return false
}
}
return true
}
// stringContainsCTLByte reports whether s contains any ASCII control character.
func stringContainsCTLByte(s string) bool {
for i := 0; i < len(s); i++ {
b := s[i]
if b < ' ' || b == 0x7f {
return true
}
}
return false
}
func hexEscapeNonASCII(s string) string {
newLen := 0
for i := 0; i < len(s); i++ {
if s[i] >= utf8.RuneSelf {
newLen += 3
} else {
newLen++
}
}
if newLen == len(s) {
return s
}
b := make([]byte, 0, newLen)
for i := 0; i < len(s); i++ {
if s[i] >= utf8.RuneSelf {
b = append(b, '%')
b = strconv.AppendInt(b, int64(s[i]), 16)
} else {
b = append(b, s[i])
}
}
return string(b)
}
// NoBody is an io.ReadCloser with no bytes. Read always returns EOF
// and Close always returns nil. It can be used in an outgoing client
// request to explicitly signal that a request has zero bytes.
// An alternative, however, is to simply set Request.Body to nil.
var NoBody = noBody{}
type noBody struct{}
func (noBody) Read([]byte) (int, error) { return 0, io.EOF }
func (noBody) Close() error { return nil }
func (noBody) WriteTo(io.Writer) (int64, error) { return 0, nil }
var (
// verify that an io.Copy from NoBody won't require a buffer:
_ io.WriterTo = NoBody
_ io.ReadCloser = NoBody
)
// PushOptions describes options for Pusher.Push.
type PushOptions struct {
// Method specifies the HTTP method for the promised request.
// If set, it must be "GET" or "HEAD". Empty means "GET".
Method string
// Header specifies additional promised request headers. This cannot
// include HTTP/2 pseudo header fields like ":path" and ":scheme",
// which will be added automatically.
Header Header
}
// Pusher is the interface implemented by ResponseWriters that support
// HTTP/2 server push. For more background, see
// https://tools.ietf.org/html/rfc7540#section-8.2.
type Pusher interface {
// Push initiates an HTTP/2 server push. This constructs a synthetic
// request using the given target and options, serializes that request
// into a PUSH_PROMISE frame, then dispatches that request using the
// server's request handler. If opts is nil, default options are used.
//
// The target must either be an absolute path (like "/path") or an absolute
// URL that contains a valid host and the same scheme as the parent request.
// If the target is a path, it will inherit the scheme and host of the
// parent request.
//
// The HTTP/2 spec disallows recursive pushes and cross-authority pushes.
// Push may or may not detect these invalid pushes; however, invalid
// pushes will be detected and canceled by conforming clients.
//
// Handlers that wish to push URL X should call Push before sending any
// data that may trigger a request for URL X. This avoids a race where the
// client issues requests for X before receiving the PUSH_PROMISE for X.
//
// Push will run in a separate goroutine making the order of arrival
// non-deterministic. Any required synchronization needs to be implemented
// by the caller.
//
// Push returns ErrNotSupported if the client has disabled push or if push
// is not supported on the underlying connection.
Push(target string, opts *PushOptions) error
}
+27
View File
@@ -0,0 +1,27 @@
// Copyright 2011 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 http
import (
"net/url"
)
// A CookieJar manages storage and use of cookies in HTTP requests.
//
// Implementations of CookieJar must be safe for concurrent use by multiple
// goroutines.
//
// The net/http/cookiejar package provides a CookieJar implementation.
type CookieJar interface {
// SetCookies handles the receipt of the cookies in a reply for the
// given URL. It may or may not choose to save the cookies, depending
// on the jar's policy and implementation.
SetCookies(u *url.URL, cookies []*Cookie)
// Cookies returns the cookies to send in a request for the given URL.
// It is up to the implementation to honor the standard cookie use
// restrictions such as in RFC 6265.
Cookies(u *url.URL) []*Cookie
}
+769
View File
@@ -0,0 +1,769 @@
package http
import (
"bufio"
"bytes"
"context"
"crypto/tls"
"errors"
"fmt"
"io"
"mime"
"mime/multipart"
"net/textproto"
"net/url"
urlpkg "net/url"
"strconv"
"strings"
"sync"
)
func badStringError(what, val string) error { return fmt.Errorf("%s %q", what, val) }
type Request struct {
// Method specifies the HTTP method (GET, POST, PUT, etc.).
// For client requests, an empty string means GET.
//
// Go's HTTP client does not support sending a request with
// the CONNECT method. See the documentation on Transport for
// details.
Method string
// URL specifies either the URI being requested (for server
// requests) or the URL to access (for client requests).
//
// For server requests, the URL is parsed from the URI
// supplied on the Request-Line as stored in RequestURI. For
// most requests, fields other than Path and RawQuery will be
// empty. (See RFC 7230, Section 5.3)
//
// For client requests, the URL's Host specifies the server to
// connect to, while the Request's Host field optionally
// specifies the Host header value to send in the HTTP
// request.
URL *url.URL
// The protocol version for incoming server requests.
//
// For client requests, these fields are ignored. The HTTP
// client code always uses either HTTP/1.1 or HTTP/2.
// See the docs on Transport for details.
Proto string // "HTTP/1.0"
ProtoMajor int // 1
ProtoMinor int // 0
// Header contains the request header fields either received
// by the server or to be sent by the client.
//
// If a server received a request with header lines,
//
// Host: example.com
// accept-encoding: gzip, deflate
// Accept-Language: en-us
// fOO: Bar
// foo: two
//
// then
//
// Header = map[string][]string{
// "Accept-Encoding": {"gzip, deflate"},
// "Accept-Language": {"en-us"},
// "Foo": {"Bar", "two"},
// }
//
// For incoming requests, the Host header is promoted to the
// Request.Host field and removed from the Header map.
//
// HTTP defines that header names are case-insensitive. The
// request parser implements this by using CanonicalHeaderKey,
// making the first character and any characters following a
// hyphen uppercase and the rest lowercase.
//
// For client requests, certain headers such as Content-Length
// and Connection are automatically written when needed and
// values in Header may be ignored. See the documentation
// for the Request.Write method.
Header Header
// Body is the request's body.
//
// For client requests, a nil body means the request has no
// body, such as a GET request. The HTTP Client's Transport
// is responsible for calling the Close method.
//
// For server requests, the Request Body is always non-nil
// but will return EOF immediately when no body is present.
// The Server will close the request body. The ServeHTTP
// Handler does not need to.
//
// Body must allow Read to be called concurrently with Close.
// In particular, calling Close should unblock a Read waiting
// for input.
Body io.ReadCloser
// GetBody defines an optional func to return a new copy of
// Body. It is used for client requests when a redirect requires
// reading the body more than once. Use of GetBody still
// requires setting Body.
//
// For server requests, it is unused.
GetBody func() (io.ReadCloser, error)
// ContentLength records the length of the associated content.
// The value -1 indicates that the length is unknown.
// Values >= 0 indicate that the given number of bytes may
// be read from Body.
//
// For client requests, a value of 0 with a non-nil Body is
// also treated as unknown.
ContentLength int64
// TransferEncoding lists the transfer encodings from outermost to
// innermost. An empty list denotes the "identity" encoding.
// TransferEncoding can usually be ignored; chunked encoding is
// automatically added and removed as necessary when sending and
// receiving requests.
TransferEncoding []string
// Close indicates whether to close the connection after
// replying to this request (for servers) or after sending this
// request and reading its response (for clients).
//
// For server requests, the HTTP server handles this automatically
// and this field is not needed by Handlers.
//
// For client requests, setting this field prevents re-use of
// TCP connections between requests to the same hosts, as if
// Transport.DisableKeepAlives were set.
Close bool
// For server requests, Host specifies the host on which the
// URL is sought. For HTTP/1 (per RFC 7230, section 5.4), this
// is either the value of the "Host" header or the host name
// given in the URL itself. For HTTP/2, it is the value of the
// ":authority" pseudo-header field.
// It may be of the form "host:port". For international domain
// names, Host may be in Punycode or Unicode form. Use
// golang.org/x/net/idna to convert it to either format if
// needed.
// To prevent DNS rebinding attacks, server Handlers should
// validate that the Host header has a value for which the
// Handler considers itself authoritative. The included
// ServeMux supports patterns registered to particular host
// names and thus protects its registered Handlers.
//
// For client requests, Host optionally overrides the Host
// header to send. If empty, the Request.Write method uses
// the value of URL.Host. Host may contain an international
// domain name.
Host string
// Form contains the parsed form data, including both the URL
// field's query parameters and the PATCH, POST, or PUT form data.
// This field is only available after ParseForm is called.
// The HTTP client ignores Form and uses Body instead.
Form url.Values
// PostForm contains the parsed form data from PATCH, POST
// or PUT body parameters.
//
// This field is only available after ParseForm is called.
// The HTTP client ignores PostForm and uses Body instead.
PostForm url.Values
// MultipartForm is the parsed multipart form, including file uploads.
// This field is only available after ParseMultipartForm is called.
// The HTTP client ignores MultipartForm and uses Body instead.
MultipartForm *multipart.Form
// Trailer specifies additional headers that are sent after the request
// body.
//
// For server requests, the Trailer map initially contains only the
// trailer keys, with nil values. (The client declares which trailers it
// will later send.) While the handler is reading from Body, it must
// not reference Trailer. After reading from Body returns EOF, Trailer
// can be read again and will contain non-nil values, if they were sent
// by the client.
//
// For client requests, Trailer must be initialized to a map containing
// the trailer keys to later send. The values may be nil or their final
// values. The ContentLength must be 0 or -1, to send a chunked request.
// After the HTTP request is sent the map values can be updated while
// the request body is read. Once the body returns EOF, the caller must
// not mutate Trailer.
//
// Few HTTP clients, servers, or proxies support HTTP trailers.
Trailer Header
// RemoteAddr allows HTTP servers and other software to record
// the network address that sent the request, usually for
// logging. This field is not filled in by ReadRequest and
// has no defined format. The HTTP server in this package
// sets RemoteAddr to an "IP:port" address before invoking a
// handler.
// This field is ignored by the HTTP client.
RemoteAddr string
// RequestURI is the unmodified request-target of the
// Request-Line (RFC 7230, Section 3.1.1) as sent by the client
// to a server. Usually the URL field should be used instead.
// It is an error to set this field in an HTTP client request.
RequestURI string
// TLS allows HTTP servers and other software to record
// information about the TLS connection on which the request
// was received. This field is not filled in by ReadRequest.
// The HTTP server in this package sets the field for
// TLS-enabled connections before invoking a handler;
// otherwise it leaves the field nil.
// This field is ignored by the HTTP client.
TLS *tls.ConnectionState
// Cancel is an optional channel whose closure indicates that the client
// request should be regarded as canceled. Not all implementations of
// RoundTripper may support Cancel.
//
// For server requests, this field is not applicable.
//
// Deprecated: Set the Request's context with NewRequestWithContext
// instead. If a Request's Cancel field and context are both
// set, it is undefined whether Cancel is respected.
Cancel <-chan struct{}
// Response is the redirect response which caused this request
// to be created. This field is only populated during client
// redirects.
Response *Response
// ctx is either the client or server context. It should only
// be modified via copying the whole Request using WithContext.
// It is unexported to prevent people from using Context wrong
// and mutating the contexts held by callers of the same request.
ctx context.Context
}
// ProtoAtLeast reports whether the HTTP protocol used
// in the request is at least major.minor.
func (r *Request) ProtoAtLeast(major, minor int) bool {
return r.ProtoMajor > major ||
r.ProtoMajor == major && r.ProtoMinor >= minor
}
// UserAgent returns the client's User-Agent, if sent in the request.
func (r *Request) UserAgent() string {
return r.Header.Get("User-Agent")
}
// Cookies parses and returns the HTTP cookies sent with the request.
func (r *Request) Cookies() []*Cookie {
return readCookies(r.Header, "")
}
// ErrNoCookie is returned by Request's Cookie method when a cookie is not found.
var ErrNoCookie = errors.New("http: named cookie not present")
// Cookie returns the named cookie provided in the request or
// ErrNoCookie if not found.
// If multiple cookies match the given name, only one cookie will
// be returned.
func (r *Request) Cookie(name string) (*Cookie, error) {
for _, c := range readCookies(r.Header, name) {
return c, nil
}
return nil, ErrNoCookie
}
// AddCookie adds a cookie to the request. Per RFC 6265 section 5.4,
// AddCookie does not attach more than one Cookie header field. That
// means all cookies, if any, are written into the same line,
// separated by semicolon.
// AddCookie only sanitizes c's name and value, and does not sanitize
// a Cookie header already present in the request.
func (r *Request) AddCookie(c *Cookie) {
s := fmt.Sprintf("%s=%s", sanitizeCookieName(c.Name), sanitizeCookieValue(c.Value))
if c := r.Header.Get("Cookie"); c != "" {
r.Header.Set("Cookie", c+"; "+s)
} else {
r.Header.Set("Cookie", s)
}
}
// Referer returns the referring URL, if sent in the request.
//
// Referer is misspelled as in the request itself, a mistake from the
// earliest days of HTTP. This value can also be fetched from the
// Header map as Header["Referer"]; the benefit of making it available
// as a method is that the compiler can diagnose programs that use the
// alternate (correct English) spelling req.Referrer() but cannot
// diagnose programs that use Header["Referrer"].
func (r *Request) Referer() string {
return r.Header.Get("Referer")
}
// isH2Upgrade reports whether r represents the http2 "client preface"
// magic string.
func (r *Request) isH2Upgrade() bool {
return r.Method == "PRI" && len(r.Header) == 0 && r.URL.Path == "*" && r.Proto == "HTTP/2.0"
}
// ParseHTTPVersion parses an HTTP version string.
// "HTTP/1.0" returns (1, 0, true).
func ParseHTTPVersion(vers string) (major, minor int, ok bool) {
const Big = 1000000 // arbitrary upper bound
switch vers {
case "HTTP/1.1":
return 1, 1, true
case "HTTP/1.0":
return 1, 0, true
}
if !strings.HasPrefix(vers, "HTTP/") {
return 0, 0, false
}
dot := strings.Index(vers, ".")
if dot < 0 {
return 0, 0, false
}
major, err := strconv.Atoi(vers[5:dot])
if err != nil || major < 0 || major > Big {
return 0, 0, false
}
minor, err = strconv.Atoi(vers[dot+1:])
if err != nil || minor < 0 || minor > Big {
return 0, 0, false
}
return major, minor, true
}
func validMethod(method string) bool {
/*
Method = "OPTIONS" ; Section 9.2
| "GET" ; Section 9.3
| "HEAD" ; Section 9.4
| "POST" ; Section 9.5
| "PUT" ; Section 9.6
| "DELETE" ; Section 9.7
| "TRACE" ; Section 9.8
| "CONNECT" ; Section 9.9
| extension-method
extension-method = token
token = 1*<any CHAR except CTLs or separators>
*/
return len(method) > 0 && strings.IndexFunc(method, isNotToken) == -1
}
// NewRequest wraps NewRequestWithContext using the background context.
func NewRequest(method, url string, body io.Reader) (*Request, error) {
return NewRequestWithContext(context.Background(), method, url, body)
}
// NewRequestWithContext returns a new Request given a method, URL, and
// optional body.
//
// If the provided body is also an io.Closer, the returned
// Request.Body is set to body and will be closed by the Client
// methods Do, Post, and PostForm, and Transport.RoundTrip.
//
// NewRequestWithContext returns a Request suitable for use with
// Client.Do or Transport.RoundTrip. To create a request for use with
// testing a Server Handler, either use the NewRequest function in the
// net/http/httptest package, use ReadRequest, or manually update the
// Request fields. For an outgoing client request, the context
// controls the entire lifetime of a request and its response:
// obtaining a connection, sending the request, and reading the
// response headers and body. See the Request type's documentation for
// the difference between inbound and outbound request fields.
//
// If body is of type *bytes.Buffer, *bytes.Reader, or
// *strings.Reader, the returned request's ContentLength is set to its
// exact value (instead of -1), GetBody is populated (so 307 and 308
// redirects can replay the body), and Body is set to NoBody if the
// ContentLength is 0.
func NewRequestWithContext(ctx context.Context, method, url string, body io.Reader) (*Request, error) {
if method == "" {
// We document that "" means "GET" for Request.Method, and people have
// relied on that from NewRequest, so keep that working.
// We still enforce validMethod for non-empty methods.
method = "GET"
}
if !validMethod(method) {
return nil, fmt.Errorf("net/http: invalid method %q", method)
}
if ctx == nil {
return nil, errors.New("net/http: nil Context")
}
u, err := urlpkg.Parse(url)
if err != nil {
return nil, err
}
rc, ok := body.(io.ReadCloser)
if !ok && body != nil {
rc = io.NopCloser(body)
}
// The host's colon:port should be normalized. See Issue 14836.
u.Host = removeEmptyPort(u.Host)
req := &Request{
ctx: ctx,
Method: method,
URL: u,
Proto: "HTTP/1.1",
ProtoMajor: 1,
ProtoMinor: 1,
Header: make(Header),
Body: rc,
Host: u.Host,
}
if body != nil {
switch v := body.(type) {
case *bytes.Buffer:
req.ContentLength = int64(v.Len())
buf := v.Bytes()
req.GetBody = func() (io.ReadCloser, error) {
r := bytes.NewReader(buf)
return io.NopCloser(r), nil
}
case *bytes.Reader:
req.ContentLength = int64(v.Len())
snapshot := *v
req.GetBody = func() (io.ReadCloser, error) {
r := snapshot
return io.NopCloser(&r), nil
}
case *strings.Reader:
req.ContentLength = int64(v.Len())
snapshot := *v
req.GetBody = func() (io.ReadCloser, error) {
r := snapshot
return io.NopCloser(&r), nil
}
default:
// This is where we'd set it to -1 (at least
// if body != NoBody) to mean unknown, but
// that broke people during the Go 1.8 testing
// period. People depend on it being 0 I
// guess. Maybe retry later. See Issue 18117.
}
// For client requests, Request.ContentLength of 0
// means either actually 0, or unknown. The only way
// to explicitly say that the ContentLength is zero is
// to set the Body to nil. But turns out too much code
// depends on NewRequest returning a non-nil Body,
// so we use a well-known ReadCloser variable instead
// and have the http package also treat that sentinel
// variable to mean explicitly zero.
if req.GetBody != nil && req.ContentLength == 0 {
req.Body = NoBody
req.GetBody = func() (io.ReadCloser, error) { return NoBody, nil }
}
}
return req, nil
}
// parseRequestLine parses "GET /foo HTTP/1.1" into its three parts.
func parseRequestLine(line string) (method, requestURI, proto string, ok bool) {
s1 := strings.Index(line, " ")
s2 := strings.Index(line[s1+1:], " ")
if s1 < 0 || s2 < 0 {
return
}
s2 += s1 + 1
return line[:s1], line[s1+1 : s2], line[s2+1:], true
}
var textprotoReaderPool sync.Pool
func newTextprotoReader(br *bufio.Reader) *textproto.Reader {
if v := textprotoReaderPool.Get(); v != nil {
tr := v.(*textproto.Reader)
tr.R = br
return tr
}
return textproto.NewReader(br)
}
func putTextprotoReader(r *textproto.Reader) {
r.R = nil
textprotoReaderPool.Put(r)
}
// ReadRequest reads and parses an incoming request from b.
//
// ReadRequest is a low-level function and should only be used for
// specialized applications; most code should use the Server to read
// requests and handle them via the Handler interface. ReadRequest
// only supports HTTP/1.x requests. For HTTP/2, use golang.org/x/net/http2.
func ReadRequest(b *bufio.Reader) (*Request, error) {
return readRequest(b, deleteHostHeader)
}
// Constants for readRequest's deleteHostHeader parameter.
const (
deleteHostHeader = true
keepHostHeader = false
)
func readRequest(b *bufio.Reader, deleteHostHeader bool) (req *Request, err error) {
tp := newTextprotoReader(b)
req = new(Request)
// First line: GET /index.html HTTP/1.0
var s string
if s, err = tp.ReadLine(); err != nil {
return nil, err
}
defer func() {
putTextprotoReader(tp)
if err == io.EOF {
err = io.ErrUnexpectedEOF
}
}()
var ok bool
req.Method, req.RequestURI, req.Proto, ok = parseRequestLine(s)
if !ok {
return nil, badStringError("malformed HTTP request", s)
}
if !validMethod(req.Method) {
return nil, badStringError("invalid method", req.Method)
}
rawurl := req.RequestURI
if req.ProtoMajor, req.ProtoMinor, ok = ParseHTTPVersion(req.Proto); !ok {
return nil, badStringError("malformed HTTP version", req.Proto)
}
// CONNECT requests are used two different ways, and neither uses a full URL:
// The standard use is to tunnel HTTPS through an HTTP proxy.
// It looks like "CONNECT www.google.com:443 HTTP/1.1", and the parameter is
// just the authority section of a URL. This information should go in req.URL.Host.
//
// The net/rpc package also uses CONNECT, but there the parameter is a path
// that starts with a slash. It can be parsed with the regular URL parser,
// and the path will end up in req.URL.Path, where it needs to be in order for
// RPC to work.
justAuthority := req.Method == "CONNECT" && !strings.HasPrefix(rawurl, "/")
if justAuthority {
rawurl = "http://" + rawurl
}
if req.URL, err = url.ParseRequestURI(rawurl); err != nil {
return nil, err
}
if justAuthority {
// Strip the bogus "http://" back off.
req.URL.Scheme = ""
}
// Subsequent lines: Key: value.
mimeHeader, err := tp.ReadMIMEHeader()
if err != nil {
return nil, err
}
req.Header = Header(mimeHeader)
// RFC 7230, section 5.3: Must treat
// GET /index.html HTTP/1.1
// Host: www.google.com
// and
// GET http://www.google.com/index.html HTTP/1.1
// Host: doesntmatter
// the same. In the second case, any Host line is ignored.
req.Host = req.URL.Host
if req.Host == "" {
req.Host = req.Header.get("Host")
}
if deleteHostHeader {
delete(req.Header, "Host")
}
fixPragmaCacheControl(req.Header)
req.Close = shouldClose(req.ProtoMajor, req.ProtoMinor, req.Header, false)
err = readTransfer(req, b)
if err != nil {
return nil, err
}
if req.isH2Upgrade() {
// Because it's neither chunked, nor declared:
req.ContentLength = -1
// We want to give handlers a chance to hijack the
// connection, but we need to prevent the Server from
// dealing with the connection further if it's not
// hijacked. Set Close to ensure that:
req.Close = true
}
return req, nil
}
// MaxBytesReader is similar to io.LimitReader but is intended for
// limiting the size of incoming request bodies. In contrast to
// io.LimitReader, MaxBytesReader's result is a ReadCloser, returns a
// non-EOF error for a Read beyond the limit, and closes the
// underlying reader when its Close method is called.
//
// MaxBytesReader prevents clients from accidentally or maliciously
// sending a large request and wasting server resources.
func MaxBytesReader(w ResponseWriter, r io.ReadCloser, n int64) io.ReadCloser {
return &maxBytesReader{w: w, r: r, n: n}
}
type maxBytesReader struct {
w ResponseWriter
r io.ReadCloser // underlying reader
n int64 // max bytes remaining
err error // sticky error
}
func (l *maxBytesReader) Read(p []byte) (n int, err error) {
if l.err != nil {
return 0, l.err
}
if len(p) == 0 {
return 0, nil
}
// If they asked for a 32KB byte read but only 5 bytes are
// remaining, no need to read 32KB. 6 bytes will answer the
// question of the whether we hit the limit or go past it.
if int64(len(p)) > l.n+1 {
p = p[:l.n+1]
}
n, err = l.r.Read(p)
if int64(n) <= l.n {
l.n -= int64(n)
l.err = err
return n, err
}
n = int(l.n)
l.n = 0
// The server code and client code both use
// maxBytesReader. This "requestTooLarge" check is
// only used by the server code. To prevent binaries
// which only using the HTTP Client code (such as
// cmd/go) from also linking in the HTTP server, don't
// use a static type assertion to the server
// "*response" type. Check this interface instead:
type requestTooLarger interface {
requestTooLarge()
}
if res, ok := l.w.(requestTooLarger); ok {
res.requestTooLarge()
}
l.err = errors.New("http: request body too large")
return n, l.err
}
func (l *maxBytesReader) Close() error {
return l.r.Close()
}
func copyValues(dst, src url.Values) {
for k, vs := range src {
dst[k] = append(dst[k], vs...)
}
}
func parsePostForm(r *Request) (vs url.Values, err error) {
if r.Body == nil {
err = errors.New("missing form body")
return
}
ct := r.Header.Get("Content-Type")
// RFC 7231, section 3.1.1.5 - empty type
// MAY be treated as application/octet-stream
if ct == "" {
ct = "application/octet-stream"
}
ct, _, err = mime.ParseMediaType(ct)
switch {
case ct == "application/x-www-form-urlencoded":
var reader io.Reader = r.Body
maxFormSize := int64(1<<63 - 1)
if _, ok := r.Body.(*maxBytesReader); !ok {
maxFormSize = int64(10 << 20) // 10 MB is a lot of text.
reader = io.LimitReader(r.Body, maxFormSize+1)
}
b, e := io.ReadAll(reader)
if e != nil {
if err == nil {
err = e
}
break
}
if int64(len(b)) > maxFormSize {
err = errors.New("http: POST too large")
return
}
vs, e = url.ParseQuery(string(b))
if err == nil {
err = e
}
case ct == "multipart/form-data":
// handled by ParseMultipartForm (which is calling us, or should be)
// TODO(bradfitz): there are too many possible
// orders to call too many functions here.
// Clean this up and write more tests.
// request_test.go contains the start of this,
// in TestParseMultipartFormOrder and others.
}
return
}
// ParseForm populates r.Form and r.PostForm.
//
// For all requests, ParseForm parses the raw query from the URL and updates
// r.Form.
//
// For POST, PUT, and PATCH requests, it also reads the request body, parses it
// as a form and puts the results into both r.PostForm and r.Form. Request body
// parameters take precedence over URL query string values in r.Form.
//
// If the request Body's size has not already been limited by MaxBytesReader,
// the size is capped at 10MB.
//
// For other HTTP methods, or when the Content-Type is not
// application/x-www-form-urlencoded, the request Body is not read, and
// r.PostForm is initialized to a non-nil, empty value.
//
// ParseMultipartForm calls ParseForm automatically.
// ParseForm is idempotent.
func (r *Request) ParseForm() error {
var err error
if r.PostForm == nil {
if r.Method == "POST" || r.Method == "PUT" || r.Method == "PATCH" {
r.PostForm, err = parsePostForm(r)
}
if r.PostForm == nil {
r.PostForm = make(url.Values)
}
}
if r.Form == nil {
if len(r.PostForm) > 0 {
r.Form = make(url.Values)
copyValues(r.Form, r.PostForm)
}
var newValues url.Values
if r.URL != nil {
var e error
newValues, e = url.ParseQuery(r.URL.RawQuery)
if err == nil {
err = e
}
}
if newValues == nil {
newValues = make(url.Values)
}
if r.Form == nil {
r.Form = newValues
} else {
copyValues(r.Form, newValues)
}
}
return err
}
+120
View File
@@ -0,0 +1,120 @@
package http
import (
"crypto/tls"
"io"
)
// Response represents the response from an HTTP request.
//
// The Client and Transport return Responses from servers once
// the response headers have been received. The response body
// is streamed on demand as the Body field is read.
type Response struct {
Status string // e.g. "200 OK"
StatusCode int // e.g. 200
Proto string // e.g. "HTTP/1.0"
ProtoMajor int // e.g. 1
ProtoMinor int // e.g. 0
// Header maps header keys to values. If the response had multiple
// headers with the same key, they may be concatenated, with comma
// delimiters. (RFC 7230, section 3.2.2 requires that multiple headers
// be semantically equivalent to a comma-delimited sequence.) When
// Header values are duplicated by other fields in this struct (e.g.,
// ContentLength, TransferEncoding, Trailer), the field values are
// authoritative.
//
// Keys in the map are canonicalized (see CanonicalHeaderKey).
Header Header
// Body represents the response body.
//
// The response body is streamed on demand as the Body field
// is read. If the network connection fails or the server
// terminates the response, Body.Read calls return an error.
//
// The http Client and Transport guarantee that Body is always
// non-nil, even on responses without a body or responses with
// a zero-length body. It is the caller's responsibility to
// close Body. The default HTTP client's Transport may not
// reuse HTTP/1.x "keep-alive" TCP connections if the Body is
// not read to completion and closed.
//
// The Body is automatically dechunked if the server replied
// with a "chunked" Transfer-Encoding.
//
// As of Go 1.12, the Body will also implement io.Writer
// on a successful "101 Switching Protocols" response,
// as used by WebSockets and HTTP/2's "h2c" mode.
Body io.ReadCloser
// ContentLength records the length of the associated content. The
// value -1 indicates that the length is unknown. Unless Request.Method
// is "HEAD", values >= 0 indicate that the given number of bytes may
// be read from Body.
ContentLength int64
// Contains transfer encodings from outer-most to inner-most. Value is
// nil, means that "identity" encoding is used.
TransferEncoding []string
// Close records whether the header directed that the connection be
// closed after reading Body. The value is advice for clients: neither
// ReadResponse nor Response.Write ever closes a connection.
Close bool
// Uncompressed reports whether the response was sent compressed but
// was decompressed by the http package. When true, reading from
// Body yields the uncompressed content instead of the compressed
// content actually set from the server, ContentLength is set to -1,
// and the "Content-Length" and "Content-Encoding" fields are deleted
// from the responseHeader. To get the original response from
// the server, set Transport.DisableCompression to true.
Uncompressed bool
// Trailer maps trailer keys to values in the same
// format as Header.
//
// The Trailer initially contains only nil values, one for
// each key specified in the server's "Trailer" header
// value. Those values are not added to Header.
//
// Trailer must not be accessed concurrently with Read calls
// on the Body.
//
// After Body.Read has returned io.EOF, Trailer will contain
// any trailer values sent by the server.
Trailer Header
// Request is the request that was sent to obtain this Response.
// Request's Body is nil (having already been consumed).
// This is only populated for Client requests.
Request *Request
// TLS contains information about the TLS connection on which the
// response was received. It is nil for unencrypted responses.
// The pointer is shared between responses and should not be
// modified.
TLS *tls.ConnectionState
}
// Cookies parses and returns the cookies set in the Set-Cookie headers.
func (r *Response) Cookies() []*Cookie {
return readSetCookies(r.Header)
}
// RFC 7234, section 5.4: Should treat
//
// Pragma: no-cache
//
// like
//
// Cache-Control: no-cache
func fixPragmaCacheControl(header Header) {
if hp, ok := header["Pragma"]; ok && len(hp) > 0 && hp[0] == "no-cache" {
if _, presentcc := header["Cache-Control"]; !presentcc {
header["Cache-Control"] = []string{"no-cache"}
}
}
}
+581
View File
@@ -0,0 +1,581 @@
package http
import (
"fmt"
"net"
"net/url"
urlpkg "net/url"
"path"
"sort"
"strings"
"sync"
)
// A Handler responds to an HTTP request.
//
// ServeHTTP should write reply headers and data to the ResponseWriter
// and then return. Returning signals that the request is finished; it
// is not valid to use the ResponseWriter or read from the
// Request.Body after or concurrently with the completion of the
// ServeHTTP call.
//
// Depending on the HTTP client software, HTTP protocol version, and
// any intermediaries between the client and the Go server, it may not
// be possible to read from the Request.Body after writing to the
// ResponseWriter. Cautious handlers should read the Request.Body
// first, and then reply.
//
// Except for reading the body, handlers should not modify the
// provided Request.
//
// If ServeHTTP panics, the server (the caller of ServeHTTP) assumes
// that the effect of the panic was isolated to the active request.
// It recovers the panic, logs a stack trace to the server error log,
// and either closes the network connection or sends an HTTP/2
// RST_STREAM, depending on the HTTP protocol. To abort a handler so
// the client sees an interrupted response but the server doesn't log
// an error, panic with the value ErrAbortHandler.
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
// A ResponseWriter interface is used by an HTTP handler to
// construct an HTTP response.
//
// A ResponseWriter may not be used after the Handler.ServeHTTP method
// has returned.
type ResponseWriter interface {
// Header returns the header map that will be sent by
// WriteHeader. The Header map also is the mechanism with which
// Handlers can set HTTP trailers.
//
// Changing the header map after a call to WriteHeader (or
// Write) has no effect unless the modified headers are
// trailers.
//
// There are two ways to set Trailers. The preferred way is to
// predeclare in the headers which trailers you will later
// send by setting the "Trailer" header to the names of the
// trailer keys which will come later. In this case, those
// keys of the Header map are treated as if they were
// trailers. See the example. The second way, for trailer
// keys not known to the Handler until after the first Write,
// is to prefix the Header map keys with the TrailerPrefix
// constant value. See TrailerPrefix.
//
// To suppress automatic response headers (such as "Date"), set
// their value to nil.
Header() Header
// Write writes the data to the connection as part of an HTTP reply.
//
// If WriteHeader has not yet been called, Write calls
// WriteHeader(http.StatusOK) before writing the data. If the Header
// does not contain a Content-Type line, Write adds a Content-Type set
// to the result of passing the initial 512 bytes of written data to
// DetectContentType. Additionally, if the total size of all written
// data is under a few KB and there are no Flush calls, the
// Content-Length header is added automatically.
//
// Depending on the HTTP protocol version and the client, calling
// Write or WriteHeader may prevent future reads on the
// Request.Body. For HTTP/1.x requests, handlers should read any
// needed request body data before writing the response. Once the
// headers have been flushed (due to either an explicit Flusher.Flush
// call or writing enough data to trigger a flush), the request body
// may be unavailable. For HTTP/2 requests, the Go HTTP server permits
// handlers to continue to read the request body while concurrently
// writing the response. However, such behavior may not be supported
// by all HTTP/2 clients. Handlers should read before writing if
// possible to maximize compatibility.
Write([]byte) (int, error)
// WriteHeader sends an HTTP response header with the provided
// status code.
//
// If WriteHeader is not called explicitly, the first call to Write
// will trigger an implicit WriteHeader(http.StatusOK).
// Thus explicit calls to WriteHeader are mainly used to
// send error codes.
//
// The provided code must be a valid HTTP 1xx-5xx status code.
// Only one header may be written. Go does not currently
// support sending user-defined 1xx informational headers,
// with the exception of 100-continue response header that the
// Server sends automatically when the Request.Body is read.
WriteHeader(statusCode int)
}
// TimeFormat is the time format to use when generating times in HTTP
// headers. It is like time.RFC1123 but hard-codes GMT as the time
// zone. The time being formatted must be in UTC for Format to
// generate the correct format.
//
// For parsing this time format, see ParseTime.
const TimeFormat = "Mon, 02 Jan 2006 15:04:05 GMT"
// The HandlerFunc type is an adapter to allow the use of
// ordinary functions as HTTP handlers. If f is a function
// with the appropriate signature, HandlerFunc(f) is a
// Handler that calls f.
type HandlerFunc func(ResponseWriter, *Request)
// ServeHTTP calls f(w, r).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}
// Helper handlers
// Error replies to the request with the specified error message and HTTP code.
// It does not otherwise end the request; the caller should ensure no further
// writes are done to w.
// The error message should be plain text.
func Error(w ResponseWriter, error string, code int) {
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
w.Header().Set("X-Content-Type-Options", "nosniff")
w.WriteHeader(code)
fmt.Fprintln(w, error)
}
// NotFound replies to the request with an HTTP 404 not found error.
func NotFound(w ResponseWriter, r *Request) { Error(w, "404 page not found", StatusNotFound) }
// NotFoundHandler returns a simple request handler
// that replies to each request with a “404 page not found” reply.
func NotFoundHandler() Handler { return HandlerFunc(NotFound) }
// StripPrefix returns a handler that serves HTTP requests by removing the
// given prefix from the request URL's Path (and RawPath if set) and invoking
// the handler h. StripPrefix handles a request for a path that doesn't begin
// with prefix by replying with an HTTP 404 not found error. The prefix must
// match exactly: if the prefix in the request contains escaped characters
// the reply is also an HTTP 404 not found error.
func StripPrefix(prefix string, h Handler) Handler {
if prefix == "" {
return h
}
return HandlerFunc(func(w ResponseWriter, r *Request) {
p := strings.TrimPrefix(r.URL.Path, prefix)
rp := strings.TrimPrefix(r.URL.RawPath, prefix)
if len(p) < len(r.URL.Path) && (r.URL.RawPath == "" || len(rp) < len(r.URL.RawPath)) {
r2 := new(Request)
*r2 = *r
r2.URL = new(url.URL)
*r2.URL = *r.URL
r2.URL.Path = p
r2.URL.RawPath = rp
h.ServeHTTP(w, r2)
} else {
NotFound(w, r)
}
})
}
// Redirect replies to the request with a redirect to url,
// which may be a path relative to the request path.
//
// The provided code should be in the 3xx range and is usually
// StatusMovedPermanently, StatusFound or StatusSeeOther.
//
// If the Content-Type header has not been set, Redirect sets it
// to "text/html; charset=utf-8" and writes a small HTML body.
// Setting the Content-Type header to any value, including nil,
// disables that behavior.
func Redirect(w ResponseWriter, r *Request, url string, code int) {
if u, err := urlpkg.Parse(url); err == nil {
// If url was relative, make its path absolute by
// combining with request path.
// The client would probably do this for us,
// but doing it ourselves is more reliable.
// See RFC 7231, section 7.1.2
if u.Scheme == "" && u.Host == "" {
oldpath := r.URL.Path
if oldpath == "" { // should not happen, but avoid a crash if it does
oldpath = "/"
}
// no leading http://server
if url == "" || url[0] != '/' {
// make relative path absolute
olddir, _ := path.Split(oldpath)
url = olddir + url
}
var query string
if i := strings.Index(url, "?"); i != -1 {
url, query = url[:i], url[i:]
}
// clean up but preserve trailing slash
trailing := strings.HasSuffix(url, "/")
url = path.Clean(url)
if trailing && !strings.HasSuffix(url, "/") {
url += "/"
}
url += query
}
}
h := w.Header()
// RFC 7231 notes that a short HTML body is usually included in
// the response because older user agents may not understand 301/307.
// Do it only if the request didn't already have a Content-Type header.
_, hadCT := h["Content-Type"]
h.Set("Location", hexEscapeNonASCII(url))
if !hadCT && (r.Method == "GET" || r.Method == "HEAD") {
h.Set("Content-Type", "text/html; charset=utf-8")
}
w.WriteHeader(code)
// Shouldn't send the body for POST or HEAD; that leaves GET.
if !hadCT && r.Method == "GET" {
body := "<a href=\"" + htmlEscape(url) + "\">" + statusText[code] + "</a>.\n"
fmt.Fprintln(w, body)
}
}
var htmlReplacer = strings.NewReplacer(
"&", "&amp;",
"<", "&lt;",
">", "&gt;",
// "&#34;" is shorter than "&quot;".
`"`, "&#34;",
// "&#39;" is shorter than "&apos;" and apos was not in HTML until HTML5.
"'", "&#39;",
)
func htmlEscape(s string) string {
return htmlReplacer.Replace(s)
}
// Redirect to a fixed URL
type redirectHandler struct {
url string
code int
}
func (rh *redirectHandler) ServeHTTP(w ResponseWriter, r *Request) {
Redirect(w, r, rh.url, rh.code)
}
// RedirectHandler returns a request handler that redirects
// each request it receives to the given url using the given
// status code.
//
// The provided code should be in the 3xx range and is usually
// StatusMovedPermanently, StatusFound or StatusSeeOther.
func RedirectHandler(url string, code int) Handler {
return &redirectHandler{url, code}
}
// ServeMux is an HTTP request multiplexer.
// It matches the URL of each incoming request against a list of registered
// patterns and calls the handler for the pattern that
// most closely matches the URL.
//
// Patterns name fixed, rooted paths, like "/favicon.ico",
// or rooted subtrees, like "/images/" (note the trailing slash).
// Longer patterns take precedence over shorter ones, so that
// if there are handlers registered for both "/images/"
// and "/images/thumbnails/", the latter handler will be
// called for paths beginning "/images/thumbnails/" and the
// former will receive requests for any other paths in the
// "/images/" subtree.
//
// Note that since a pattern ending in a slash names a rooted subtree,
// the pattern "/" matches all paths not matched by other registered
// patterns, not just the URL with Path == "/".
//
// If a subtree has been registered and a request is received naming the
// subtree root without its trailing slash, ServeMux redirects that
// request to the subtree root (adding the trailing slash). This behavior can
// be overridden with a separate registration for the path without
// the trailing slash. For example, registering "/images/" causes ServeMux
// to redirect a request for "/images" to "/images/", unless "/images" has
// been registered separately.
//
// Patterns may optionally begin with a host name, restricting matches to
// URLs on that host only. Host-specific patterns take precedence over
// general patterns, so that a handler might register for the two patterns
// "/codesearch" and "codesearch.google.com/" without also taking over
// requests for "http://www.google.com/".
//
// ServeMux also takes care of sanitizing the URL request path and the Host
// header, stripping the port number and redirecting any request containing . or
// .. elements or repeated slashes to an equivalent, cleaner URL.
type ServeMux struct {
mu sync.RWMutex
m map[string]muxEntry
es []muxEntry // slice of entries sorted from longest to shortest.
hosts bool // whether any patterns contain hostnames
}
type muxEntry struct {
h Handler
pattern string
}
// NewServeMux allocates and returns a new ServeMux.
func NewServeMux() *ServeMux { return new(ServeMux) }
// DefaultServeMux is the default ServeMux used by Serve.
var DefaultServeMux = &defaultServeMux
var defaultServeMux ServeMux
// cleanPath returns the canonical path for p, eliminating . and .. elements.
func cleanPath(p string) string {
if p == "" {
return "/"
}
if p[0] != '/' {
p = "/" + p
}
np := path.Clean(p)
// path.Clean removes trailing slash except for root;
// put the trailing slash back if necessary.
if p[len(p)-1] == '/' && np != "/" {
// Fast path for common case of p being the string we want:
if len(p) == len(np)+1 && strings.HasPrefix(p, np) {
np = p
} else {
np += "/"
}
}
return np
}
// stripHostPort returns h without any trailing ":<port>".
func stripHostPort(h string) string {
// If no port on host, return unchanged
if strings.IndexByte(h, ':') == -1 {
return h
}
host, _, err := net.SplitHostPort(h)
if err != nil {
return h // on error, return unchanged
}
return host
}
// Find a handler on a handler map given a path string.
// Most-specific (longest) pattern wins.
func (mux *ServeMux) match(path string) (h Handler, pattern string) {
// Check for exact match first.
v, ok := mux.m[path]
if ok {
return v.h, v.pattern
}
// Check for longest valid match. mux.es contains all patterns
// that end in / sorted from longest to shortest.
for _, e := range mux.es {
if strings.HasPrefix(path, e.pattern) {
return e.h, e.pattern
}
}
return nil, ""
}
// redirectToPathSlash determines if the given path needs appending "/" to it.
// This occurs when a handler for path + "/" was already registered, but
// not for path itself. If the path needs appending to, it creates a new
// URL, setting the path to u.Path + "/" and returning true to indicate so.
func (mux *ServeMux) redirectToPathSlash(host, path string, u *url.URL) (*url.URL, bool) {
mux.mu.RLock()
shouldRedirect := mux.shouldRedirectRLocked(host, path)
mux.mu.RUnlock()
if !shouldRedirect {
return u, false
}
path = path + "/"
u = &url.URL{Path: path, RawQuery: u.RawQuery}
return u, true
}
// shouldRedirectRLocked reports whether the given path and host should be redirected to
// path+"/". This should happen if a handler is registered for path+"/" but
// not path -- see comments at ServeMux.
func (mux *ServeMux) shouldRedirectRLocked(host, path string) bool {
p := []string{path, host + path}
for _, c := range p {
if _, exist := mux.m[c]; exist {
return false
}
}
n := len(path)
if n == 0 {
return false
}
for _, c := range p {
if _, exist := mux.m[c+"/"]; exist {
return path[n-1] != '/'
}
}
return false
}
// Handler returns the handler to use for the given request,
// consulting r.Method, r.Host, and r.URL.Path. It always returns
// a non-nil handler. If the path is not in its canonical form, the
// handler will be an internally-generated handler that redirects
// to the canonical path. If the host contains a port, it is ignored
// when matching handlers.
//
// The path and host are used unchanged for CONNECT requests.
//
// Handler also returns the registered pattern that matches the
// request or, in the case of internally-generated redirects,
// the pattern that will match after following the redirect.
//
// If there is no registered handler that applies to the request,
// Handler returns a “page not found” handler and an empty pattern.
func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) {
// CONNECT requests are not canonicalized.
if r.Method == "CONNECT" {
// If r.URL.Path is /tree and its handler is not registered,
// the /tree -> /tree/ redirect applies to CONNECT requests
// but the path canonicalization does not.
if u, ok := mux.redirectToPathSlash(r.URL.Host, r.URL.Path, r.URL); ok {
return RedirectHandler(u.String(), StatusMovedPermanently), u.Path
}
return mux.handler(r.Host, r.URL.Path)
}
// All other requests have any port stripped and path cleaned
// before passing to mux.handler.
host := stripHostPort(r.Host)
path := cleanPath(r.URL.Path)
// If the given path is /tree and its handler is not registered,
// redirect for /tree/.
if u, ok := mux.redirectToPathSlash(host, path, r.URL); ok {
return RedirectHandler(u.String(), StatusMovedPermanently), u.Path
}
if path != r.URL.Path {
_, pattern = mux.handler(host, path)
url := *r.URL
url.Path = path
return RedirectHandler(url.String(), StatusMovedPermanently), pattern
}
return mux.handler(host, r.URL.Path)
}
// handler is the main implementation of Handler.
// The path is known to be in canonical form, except for CONNECT methods.
func (mux *ServeMux) handler(host, path string) (h Handler, pattern string) {
mux.mu.RLock()
defer mux.mu.RUnlock()
// Host-specific pattern takes precedence over generic ones
if mux.hosts {
h, pattern = mux.match(host + path)
}
if h == nil {
h, pattern = mux.match(path)
}
if h == nil {
h, pattern = NotFoundHandler(), ""
}
return
}
// ServeHTTP dispatches the request to the handler whose
// pattern most closely matches the request URL.
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
if r.RequestURI == "*" {
if r.ProtoAtLeast(1, 1) {
w.Header().Set("Connection", "close")
}
w.WriteHeader(StatusBadRequest)
return
}
h, _ := mux.Handler(r)
h.ServeHTTP(w, r)
}
// Handle registers the handler for the given pattern.
// If a handler already exists for pattern, Handle panics.
func (mux *ServeMux) Handle(pattern string, handler Handler) {
mux.mu.Lock()
defer mux.mu.Unlock()
if pattern == "" {
panic("http: invalid pattern")
}
if handler == nil {
panic("http: nil handler")
}
if _, exist := mux.m[pattern]; exist {
panic("http: multiple registrations for " + pattern)
}
if mux.m == nil {
mux.m = make(map[string]muxEntry)
}
e := muxEntry{h: handler, pattern: pattern}
mux.m[pattern] = e
if pattern[len(pattern)-1] == '/' {
mux.es = appendSorted(mux.es, e)
}
if pattern[0] != '/' {
mux.hosts = true
}
}
func appendSorted(es []muxEntry, e muxEntry) []muxEntry {
n := len(es)
i := sort.Search(n, func(i int) bool {
return len(es[i].pattern) < len(e.pattern)
})
if i == n {
return append(es, e)
}
// we now know that i points at where we want to insert
es = append(es, muxEntry{}) // try to grow the slice in place, any entry works.
copy(es[i+1:], es[i:]) // Move shorter entries down
es[i] = e
return es
}
// HandleFunc registers the handler function for the given pattern.
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
if handler == nil {
panic("http: nil handler")
}
mux.Handle(pattern, HandlerFunc(handler))
}
// Handle registers the handler for the given pattern
// in the DefaultServeMux.
// The documentation for ServeMux explains how patterns are matched.
func Handle(pattern string, handler Handler) { DefaultServeMux.Handle(pattern, handler) }
// HandleFunc registers the handler function for the given pattern
// in the DefaultServeMux.
// The documentation for ServeMux explains how patterns are matched.
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
DefaultServeMux.HandleFunc(pattern, handler)
}
// ListenAndServe listens on the TCP network address addr and then calls
// Serve with handler to handle requests on incoming connections.
// Accepted connections are configured to enable TCP keep-alives.
//
// The handler is typically nil, in which case the DefaultServeMux is used.
//
// ListenAndServe always returns a non-nil error.
func ListenAndServe(addr string, handler Handler) error {
return ActiveDevice.ListenAndServe(addr, handler)
}

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