Compare commits

...

27 Commits

Author SHA1 Message Date
Ron Evans d64069a517 release: update CHANGELOG with 0.3.0 release info
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-07-16 12:14:05 -07:00
Ron Evans e4b80d8e0e espat: add firmware install info for Arduino Nano33 IoT NINA-W102 chip
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-07-16 12:12:53 -07:00
Ron Evans 50633f3e86 espat: refactor net and tls interface compatible code into separate sub-packages
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-07-15 17:54:26 +02:00
Ron Evans 7e78e2c998 espat: add built-in support for MQTT publish using the Paho library packets, alongside some modifications needed for the AT protocol.
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-07-15 17:54:26 +02:00
Ron Evans 40d9287ac4 espat: add README with information on how to flash ESP32 or ESP8266 with AT command set firmware.
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-07-15 17:54:26 +02:00
Ron Evans 59aece351a espat: change Response() method to use a passed-in timeout value instead of fixed pauses.
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-07-15 17:54:26 +02:00
Ron Evans 1b81b992c2 espat: add DialTLS and Dial methods, update MQTT example to allow both MQTT and MQTTS connections
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-07-15 17:54:26 +02:00
Ron Evans 506819c93c espat: update MQTT example for greater stability
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-07-15 17:54:26 +02:00
Ron Evans 1d9e393948 espat: add example that uses MQTT publish to open server
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-07-15 17:54:26 +02:00
Ron Evans 9c88d1fab4 espat: add ResolveUDPAddr and ResolveTCPAddr implementations using AT command for DNS lookup
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-07-15 17:54:26 +02:00
Ron Evans a267fdb8ce espat: improve error handling for key TCP functions
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-07-15 17:54:26 +02:00
Ron Evans 500f3d9813 espat: implement TCPConn using AT command set
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-07-15 17:54:26 +02:00
Ron Evans fe58e9b762 espat: use only AT commands that work on both ESP8266 and ESP32
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-07-15 17:54:26 +02:00
Daniel Esteban d8675515bc epd2in13: added rotation 2019-07-15 01:35:33 +02:00
Daniel Esteban 28dbbe2e65 typo in package name of examples 2019-07-14 22:21:37 +02:00
Ayke van Laethem 78fdaad9c0 mpu6050: properly scale the outputs of the accel/gyro
Units were unspecified before but were in practice the raw output from
the sensor. They have now been changed to sensible outputs
understandable to humans, like the other accelerometers.
2019-07-14 22:20:51 +02:00
Daniel Esteban cb49783f18 Error strings should not be capitalized (unless beginning with proper
nouns or acronyms) or end with punctuation, since they are usually
printed following other context.
2019-07-08 18:52:31 +02:00
Ron Evans 8534e67c83 buzzer: buzzer timbres sound better with more bass
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-07-06 16:35:09 +02:00
Ron Evans 95755ebae7 buzzer: add simple buzzer implementation
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-07-06 16:35:09 +02:00
Ron Evans f4583f5144 microphone: PDM MEMS microphone support using I2S interface
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-07-05 14:52:54 +02:00
Ron Evans 1e59a3970e docs: add note to current/future contributors to please start by opening a GH issue to avoid duplication of efforts
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-06-06 12:12:24 +02:00
Ron Evans e4f6fbcb52 release: update changelog for v0.2.0
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-06-04 07:47:44 +02:00
Ron Evans 7487c6b3a2 docs: correct badge and link in README for godocs to use custom domain
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-06-03 19:45:18 +02:00
Ron Evans d1553458f5 bme280: add package docs, and add bme280 to README
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-06-03 19:39:30 +02:00
Daniel Esteban 79d3609f76 Support for AT24C32/64 2-wire serial EEPROM 2019-06-03 19:38:04 +02:00
Ayke van Laethem 00a9b9db77 ws2812: better support the nrf52832
This has cost me _hours_ to find. There are many clones of the ws2812
and they have slightly different timing characteristics. Some don't work
so well when you get close to the minimum T1H time: a 1-bit may be
interpreted as a 0-bit.
2019-06-03 19:25:24 +02:00
Martin Treml f68388702d Implementation of the BME280 Sensor (#38)
* Driver for bme280
2019-06-03 19:03:53 +02:00
42 changed files with 2511 additions and 293 deletions
+34
View File
@@ -1,3 +1,37 @@
0.3.0
---
- **new devices**
- Buzzer for piezo or small speaker
- PDM MEMS microphone support using I2S interface
- **enhancements**
- epd2in13: added rotation
- espat
- add built-in support for MQTT publish using the Paho library packets, alongside some modifications needed for the AT protocol.
- add DialTLS and Dial methods, update MQTT example to allow both MQTT and MQTTS connections
- add example that uses MQTT publish to open server
- add README with information on how to flash ESP32 or ESP8266 with AT command set firmware.
- add ResolveUDPAddr and ResolveTCPAddr implementations using AT command for DNS lookup
- change Response() method to use a passed-in timeout value instead of fixed pauses.
- implement TCPConn using AT command set
- improve error handling for key TCP functions
- refactor net and tls interface compatible code into separate sub-packages
- update MQTT example for greater stability
- use only AT commands that work on both ESP8266 and ESP32
- add documentation on how to use Arduino Nano33 IoT built-in WiFi NINA-W102 chip.
- **bugfixes**
- core: Error strings should not be capitalized (unless beginning with proper nouns or acronyms) or end with punctuation, since they are usually printed following other context.
- docs: add note to current/future contributors to please start by opening a GH issue to avoid duplication of efforts
- examples: typo in package name of examples
- mpu6050: properly scale the outputs of the accel/gyro
0.2.0
---
- **new devices**
- AT24C32/64 2-wire serial EEPROM
- BME280 humidity/pressure sensor
- **bugfixes**
- ws2812: better support for nrf52832
0.1.0
---
- **first release**
+2
View File
@@ -16,6 +16,8 @@ Please open a Github issue with your problem, and we will be happy to assist.
We probably have not implemented it yet. Your contribution adding the hardware support to TinyGo would be greatly appreciated.
Please first open a Github issue. We want to help, and also make sure that there is no duplications of efforts. Sometimes what you need is already being worked on by someone else.
## How to use our Github repository
The `master` branch of this repo will always have the latest released version of the TinyGo drivers. All of the active development work for the next release will take place in the `dev` branch. The TinyGo drivers will use semantic versioning and will create a tag/release for each release.
+4
View File
@@ -11,6 +11,7 @@ smoke-test:
@mkdir -p build
tinygo build -size short -o ./build/test.elf -target=itsybitsy-m0 ./examples/adxl345/main.go
tinygo build -size short -o ./build/test.elf -target=itsybitsy-m0 ./examples/apa102/main.go
tinygo build -size short -o ./build/test.elf -target=microbit ./examples/at24cx/main.go
tinygo build -size short -o ./build/test.elf -target=itsybitsy-m0 ./examples/bh1750/main.go
tinygo build -size short -o ./build/test.elf -target=itsybitsy-m0 ./examples/blinkm/main.go
tinygo build -size short -o ./build/test.elf -target=itsybitsy-m0 ./examples/bmp180/main.go
@@ -41,5 +42,8 @@ smoke-test:
tinygo build -size short -o ./build/test.elf -target=microbit ./examples/waveshare-epd/epd2in13/main.go
tinygo build -size short -o ./build/test.elf -target=microbit ./examples/waveshare-epd/epd2in13x/main.go
tinygo build -size short -o ./build/test.elf -target=circuitplay-express ./examples/ws2812/main.go
tinygo build -size short -o ./build/test.elf -target=trinket-m0 ./examples/bme280/main.go
tinygo build -size short -o ./build/test.elf -target=circuitplay-express ./examples/microphone/main.go
tinygo build -size short -o ./build/test.elf -target=circuitplay-express ./examples/buzzer/main.go
test: clean fmt-check smoke-test
+5 -1
View File
@@ -1,6 +1,6 @@
# TinyGo Drivers
[![GoDoc](https://godoc.org/github.com/tinygo-org/drivers?status.svg)](https://godoc.org/github.com/tinygo-org/drivers) [![CircleCI](https://circleci.com/gh/tinygo-org/drivers/tree/dev.svg?style=svg)](https://circleci.com/gh/tinygo-org/drivers/tree/dev)
[![GoDoc](https://godoc.org/tinygo.org/x/drivers?status.svg)](https://godoc.org/tinygo.org/x/drivers) [![CircleCI](https://circleci.com/gh/tinygo-org/drivers/tree/dev.svg?style=svg)](https://circleci.com/gh/tinygo-org/drivers/tree/dev)
This package provides a collection of hardware drivers for devices that can be used together with [TinyGo](https://tinygo.org).
@@ -56,9 +56,12 @@ func main() {
|----------|-------------|
| [ADXL345 accelerometer](http://www.analog.com/media/en/technical-documentation/data-sheets/ADXL345.pdf) | I2C |
| [APA102 RGB LED](https://cdn-shop.adafruit.com/product-files/2343/APA102C.pdf) | SPI |
| [AT24CX 2-wire serial EEPROM](https://www.openimpulse.com/blog/wp-content/uploads/wpsc/downloadables/24C32-Datasheet.pdf) | I2C |
| [BH1750 ambient light sensor](https://www.mouser.com/ds/2/348/bh1750fvi-e-186247.pdf) | I2C |
| [BlinkM RGB LED](http://thingm.com/fileadmin/thingm/downloads/BlinkM_datasheet.pdf) | I2C |
| [BME280 humidity/pressure sensor](https://cdn-shop.adafruit.com/datasheets/BST-BME280_DS001-10.pdf) | I2C |
| [BMP180 barometer](https://cdn-shop.adafruit.com/datasheets/BST-BMP180-DS000-09.pdf) | I2C |
| [Buzzer](https://en.wikipedia.org/wiki/Buzzer#Piezoelectric) | GPIO |
| [DS1307 real time clock](https://datasheets.maximintegrated.com/en/ds/DS1307.pdf) | I2C |
| [DS3231 real time clock](https://datasheets.maximintegrated.com/en/ds/DS3231.pdf) | I2C |
| ["Easystepper" stepper motor controller](https://en.wikipedia.org/wiki/Stepper_motor) | GPIO |
@@ -68,6 +71,7 @@ func main() {
| [LIS3DH accelerometer](https://www.st.com/resource/en/datasheet/lis3dh.pdf) | I2C |
| [MAG3110 magnetometer](https://www.nxp.com/docs/en/data-sheet/MAG3110.pdf) | I2C |
| [BBC micro:bit LED matrix](https://github.com/bbcmicrobit/hardware/blob/master/SCH_BBC-Microbit_V1.3B.pdf) | GPIO |
| [Microphone - PDM](https://cdn-learn.adafruit.com/assets/assets/000/049/977/original/MP34DT01-M.pdf) | I2S/PDM |
| [MMA8653 accelerometer](https://www.nxp.com/docs/en/data-sheet/MMA8653FC.pdf) | I2C |
| [MPU6050 accelerometer/gyroscope](https://store.invensense.com/datasheets/invensense/MPU-6050_DataSheet_V3%204.pdf) | I2C |
| [PCD8544 display](http://eia.udg.edu/~forest/PCD8544_1.pdf) | SPI |
+171
View File
@@ -0,0 +1,171 @@
// Package at24cx provides a driver for the AT24C32/64/128/256/512 2-wire serial EEPROM
//
// Datasheet:
// https://www.openimpulse.com/blog/wp-content/uploads/wpsc/downloadables/24C32-Datasheet.pdf
package at24cx // import "tinygo.org/x/drivers/at24cx"
import (
"errors"
"machine"
"time"
)
// Device wraps an I2C connection to a DS3231 device.
type Device struct {
bus machine.I2C
Address uint16
pageSize uint16
currentRAMAddress uint16
startRAMAddress uint16
endRAMAddress uint16
}
type Config struct {
PageSize uint16
StartRAMAddress uint16
EndRAMAddress uint16
}
// New creates a new AT24C32/64 connection. The I2C bus must already be
// configured.
//
// This function only creates the Device object, it does not touch the device.
func New(bus machine.I2C) Device {
return Device{
bus: bus,
Address: Address,
}
}
// Configure sets up the device for communication
func (d *Device) Configure(cfg Config) {
if cfg.PageSize == 0 {
d.pageSize = 32
} else {
d.pageSize = cfg.PageSize
}
if cfg.EndRAMAddress == 0 {
d.endRAMAddress = 4096
} else {
d.endRAMAddress = cfg.EndRAMAddress
}
d.startRAMAddress = cfg.StartRAMAddress
}
// WriteByte writes a byte at the specified address
func (d *Device) WriteByte(eepromAddress uint16, value uint8) error {
address := []uint8{
uint8((eepromAddress >> 8) & 0xFF),
uint8(eepromAddress & 0xFF),
value,
}
return d.bus.Tx(d.Address, address, nil)
}
// ReadByte reads the byte at the specified address
func (d *Device) ReadByte(eepromAddress uint16) (uint8, error) {
address := []uint8{
uint8(eepromAddress >> 8),
uint8(eepromAddress & 0xFF),
}
data := make([]uint8, 1)
err := d.bus.Tx(d.Address, address, data)
return data[0], err
}
// WriteAt writes a byte array at the specified address
func (d *Device) WriteAt(data []byte, offset int64) (n int, err error) {
return d.writeAt(data, uint16(offset))
}
// writeAt writes a byte array at the specified address
func (d *Device) writeAt(data []byte, offset uint16) (n int, err error) {
values := make([]uint8, 32)
dataLeft := uint16(len(data))
d.currentRAMAddress = offset
offset = 0
var offsetPage uint16
var chunkLength uint16
for dataLeft > 0 {
offsetPage = d.currentRAMAddress % d.pageSize
if dataLeft < 30 { // The 32K/64K EEPROM is capable of 32-byte page writes and we're using 2 for the address
chunkLength = dataLeft
} else {
chunkLength = 30
}
if (d.pageSize - offsetPage) < chunkLength {
chunkLength = d.pageSize - offsetPage
}
for i := uint16(0); i < chunkLength; i++ {
values[2+i] = data[offset+i]
}
values[0] = uint8(d.currentRAMAddress >> 8)
values[1] = uint8(d.currentRAMAddress & 0xFF)
err := d.bus.Tx(d.Address, values[:chunkLength+2], nil)
if err != nil {
return 0, err
}
dataLeft -= chunkLength
offset += chunkLength
if d.endRAMAddress-chunkLength < d.currentRAMAddress {
d.currentRAMAddress = d.startRAMAddress + (d.currentRAMAddress+uint16(len(data)))%d.endRAMAddress
} else {
d.currentRAMAddress += chunkLength
}
time.Sleep(2 * time.Millisecond) // writing again too soon will block the device
}
return len(data), nil
}
// ReadAt reads the bytes at the specified address
func (d *Device) ReadAt(data []byte, offset int64) (n int, err error) {
return d.readAt(data, uint16(offset))
}
// readAt reads the bytes at the specified address
func (d *Device) readAt(data []byte, offset uint16) (n int, err error) {
address := []uint8{
uint8((offset >> 8) & 0xFF),
uint8(offset & 0xFF),
}
err = d.bus.Tx(d.Address, address, data)
if d.endRAMAddress-uint16(len(data)) < offset {
d.currentRAMAddress = d.startRAMAddress + (offset+uint16(len(data)))%d.endRAMAddress
} else {
d.currentRAMAddress = offset + uint16(len(data))
}
return len(data), err
}
// Seek sets the offset for the next Read or Write on SRAM to offset, interpreted
// according to whence: 0 means relative to the origin of the SRAM, 1 means
// relative to the current offset, and 2 means relative to the end.
// returns new offset and error, if any
func (d *Device) Seek(offset int64, whence int) (int64, error) {
w := uint16(0)
switch whence {
case 0:
w = d.startRAMAddress
case 1:
w = d.currentRAMAddress
case 2:
w = d.endRAMAddress
default:
return 0, errors.New("invalid whence")
}
d.currentRAMAddress = w + uint16(offset)
return int64(d.currentRAMAddress), nil
}
// Write writes len(data) bytes to SRAM
// returns number of bytes written and error, if any
func (d *Device) Write(data []byte) (n int, err error) {
return d.writeAt(data, d.currentRAMAddress)
}
// Read reads len(data) from SRAM
// returns number of bytes written and error, if any
func (d *Device) Read(data []uint8) (n int, err error) {
return d.readAt(data, d.currentRAMAddress)
}
+4
View File
@@ -0,0 +1,4 @@
package at24cx
// The I2C address which this device listens to.
const Address = 0x57
+257
View File
@@ -0,0 +1,257 @@
// Package bme280 provides a driver for the BME280 digital combined
// humidity and pressure sensor by Bosch.
//
// Datasheet:
// https://cdn-shop.adafruit.com/datasheets/BST-BME280_DS001-10.pdf
//
package bme280
import (
"machine"
"math"
)
// calibrationCoefficients reads at startup and stores the calibration coefficients
type calibrationCoefficients struct {
t1 uint16
t2 int16
t3 int16
p1 uint16
p2 int16
p3 int16
p4 int16
p5 int16
p6 int16
p7 int16
p8 int16
p9 int16
h1 uint8
h2 int16
h3 uint8
h4 int16
h5 int16
h6 int8
}
// Device wraps an I2C connection to a BME280 device.
type Device struct {
bus machine.I2C
Address uint16
calibrationCoefficients calibrationCoefficients
}
// New creates a new BME280 connection. The I2C bus must already be
// configured.
//
// This function only creates the Device object, it does not touch the device.
func New(bus machine.I2C) Device {
return Device{
bus: bus,
Address: Address,
}
}
// Configure sets up the device for communication and
// read the calibration coefficientes.
func (d *Device) Configure() {
var data [24]byte
err := d.bus.ReadRegister(uint8(d.Address), REG_CALIBRATION, data[:])
if err != nil {
return
}
var h1 [1]byte
err = d.bus.ReadRegister(uint8(d.Address), REG_CALIBRATION_H1, h1[:])
if err != nil {
return
}
var h2lsb [7]byte
err = d.bus.ReadRegister(uint8(d.Address), REG_CALIBRATION_H2LSB, h2lsb[:])
if err != nil {
return
}
d.calibrationCoefficients.t1 = readUintLE(data[0], data[1])
d.calibrationCoefficients.t2 = readIntLE(data[2], data[3])
d.calibrationCoefficients.t3 = readIntLE(data[4], data[5])
d.calibrationCoefficients.p1 = readUintLE(data[6], data[7])
d.calibrationCoefficients.p2 = readIntLE(data[8], data[9])
d.calibrationCoefficients.p3 = readIntLE(data[10], data[11])
d.calibrationCoefficients.p4 = readIntLE(data[12], data[13])
d.calibrationCoefficients.p5 = readIntLE(data[14], data[15])
d.calibrationCoefficients.p6 = readIntLE(data[16], data[17])
d.calibrationCoefficients.p7 = readIntLE(data[18], data[19])
d.calibrationCoefficients.p8 = readIntLE(data[20], data[21])
d.calibrationCoefficients.p9 = readIntLE(data[22], data[23])
d.calibrationCoefficients.h1 = h1[0]
d.calibrationCoefficients.h2 = readIntLE(h2lsb[0], h2lsb[1])
d.calibrationCoefficients.h3 = h2lsb[2]
d.calibrationCoefficients.h6 = int8(h2lsb[6])
d.calibrationCoefficients.h4 = 0 + (int16(h2lsb[3]) << 4) | (int16(h2lsb[4] & 0x0F))
d.calibrationCoefficients.h5 = 0 + (int16(h2lsb[5]) << 4) | (int16(h2lsb[4]) >> 4)
d.bus.WriteRegister(uint8(d.Address), CTRL_HUMIDITY_ADDR, []byte{0x3f})
d.bus.WriteRegister(uint8(d.Address), CTRL_MEAS_ADDR, []byte{0xB7})
d.bus.WriteRegister(uint8(d.Address), CTRL_CONFIG, []byte{0x00})
}
// Connected returns whether a BME280 has been found.
// It does a "who am I" request and checks the response.
func (d *Device) Connected() bool {
data := []byte{0}
d.bus.ReadRegister(uint8(d.Address), WHO_AM_I, data)
return data[0] == CHIP_ID
}
// Reset the device
func (d *Device) Reset() {
d.bus.WriteRegister(uint8(d.Address), CMD_RESET, []byte{0xB6})
}
// ReadTemperature returns the temperature in celsius milli degrees (ºC/1000)
func (d *Device) ReadTemperature() (int32, error) {
data, err := d.readData()
if err != nil {
return 0, err
}
temp, _ := d.calculateTemp(data)
return temp, nil
}
// ReadPressure returns the pressure in milli pascals mPa
func (d *Device) ReadPressure() (int32, error) {
data, err := d.readData()
if err != nil {
return 0, err
}
_, tFine := d.calculateTemp(data)
pressure := d.calculatePressure(data, tFine)
return pressure, nil
}
// ReadHumidity returns the relative humidity in hundredths of a percent
func (d *Device) ReadHumidity() (int32, error) {
data, err := d.readData()
if err != nil {
return 0, err
}
_, tFine := d.calculateTemp(data)
humidity := d.calculateHumidity(data, tFine)
return humidity, nil
}
// ReadAltitude returns the current altitude in meters based on the
// current barometric pressure and estimated pressure at sea level.
// Calculation is based on code from Adafruit BME280 library
// https://github.com/adafruit/Adafruit_BME280_Library
func (d *Device) ReadAltitude() (alt int32, err error) {
mPa, _ := d.ReadPressure()
atmP := float32(mPa) / 100000
alt = int32(44330.0 * (1.0 - math.Pow(float64(atmP/SEALEVEL_PRESSURE), 0.1903)))
return
}
// convert2Bytes converts two bytes to int32
func convert2Bytes(msb byte, lsb byte) int32 {
return int32(readUint(msb, lsb))
}
// convert3Bytes converts three bytes to int32
func convert3Bytes(msb byte, b1 byte, lsb byte) int32 {
return int32(((((uint32(msb) << 8) | uint32(b1)) << 8) | uint32(lsb)) >> 4)
}
// readUint converts two bytes to uint16
func readUint(msb byte, lsb byte) uint16 {
return (uint16(msb) << 8) | uint16(lsb)
}
// readUintLE converts two little endian bytes to uint16
func readUintLE(msb byte, lsb byte) uint16 {
temp := readUint(msb, lsb)
return (temp >> 8) | (temp << 8)
}
// readIntLE converts two little endian bytes to int16
func readIntLE(msb byte, lsb byte) int16 {
return int16(readUintLE(msb, lsb))
}
// readData does a burst read from 0xF7 to 0xF0 according to the datasheet
// resulting in an slice with 8 bytes 0-2 = pressure / 3-5 = temperature / 6-7 = humidity
func (d *Device) readData() (data [8]byte, err error) {
err = d.bus.ReadRegister(uint8(d.Address), REG_PRESSURE, data[:])
if err != nil {
println(err)
return
}
return
}
// calculateTemp uses the data slice and applies calibrations values on it to convert the value to milli degrees
// it also calculates the variable tFine which is used by the pressure and humidity calculation
func (d *Device) calculateTemp(data [8]byte) (int32, int32) {
rawTemp := convert3Bytes(data[3], data[4], data[5])
var1 := (((rawTemp >> 3) - (int32(d.calibrationCoefficients.t1) << 1)) * int32(d.calibrationCoefficients.t2)) >> 11
var2 := (((((rawTemp >> 4) - int32(d.calibrationCoefficients.t1)) * ((rawTemp >> 4) - int32(d.calibrationCoefficients.t1))) >> 12) * int32(d.calibrationCoefficients.t3)) >> 14
tFine := var1 + var2
T := (tFine*5 + 128) >> 8
return (10 * T), tFine
}
// calculatePressure uses the data slice and applies calibrations values on it to convert the value to milli pascals mPa
func (d *Device) calculatePressure(data [8]byte, tFine int32) int32 {
rawPressure := convert3Bytes(data[0], data[1], data[2])
var1 := int64(tFine) - 128000
var2 := var1 * var1 * int64(d.calibrationCoefficients.p6)
var2 = var2 + ((var1 * int64(d.calibrationCoefficients.p5)) << 17)
var2 = var2 + (int64(d.calibrationCoefficients.p4) << 35)
var1 = ((var1 * var1 * int64(d.calibrationCoefficients.p3)) >> 8) + ((var1 * int64(d.calibrationCoefficients.p2)) << 12)
var1 = ((int64(1) << 47) + var1) * int64(d.calibrationCoefficients.p1) >> 33
if var1 == 0 {
return 0 // avoid exception caused by division by zero
}
p := int64(1048576 - rawPressure)
p = (((p << 31) - var2) * 3125) / var1
var1 = (int64(d.calibrationCoefficients.p9) * (p >> 13) * (p >> 13)) >> 25
var2 = (int64(d.calibrationCoefficients.p8) * p) >> 19
p = ((p + var1 + var2) >> 8) + (int64(d.calibrationCoefficients.p7) << 4)
p = (p / 256)
return int32(1000 * p)
}
// calculateHumidity uses the data slice and applies calibrations values on it to convert the value to relative humidity in hundredths of a percent
func (d *Device) calculateHumidity(data [8]byte, tFine int32) int32 {
rawHumidity := convert2Bytes(data[6], data[7])
h := float32(tFine) - 76800
if h == 0 {
println("invalid value")
}
var1 := float32(rawHumidity) - (float32(d.calibrationCoefficients.h4)*64.0 +
(float32(d.calibrationCoefficients.h5) / 16384.0 * h))
var2 := float32(d.calibrationCoefficients.h2) / 65536.0 *
(1.0 + float32(d.calibrationCoefficients.h6)/67108864.0*h*
(1.0+float32(d.calibrationCoefficients.h3)/67108864.0*h))
h = var1 * var2
h = h * (1 - float32(d.calibrationCoefficients.h1)*h/524288)
return int32(100 * h)
}
+25
View File
@@ -0,0 +1,25 @@
package bme280
// Constants/addresses used for I2C.
// The I2C address which this device listens to.
const Address = 0x76
// Registers. Names, addresses and comments copied from the datasheet.
const (
CTRL_MEAS_ADDR = 0xF4
CTRL_HUMIDITY_ADDR = 0xF2
CTRL_CONFIG = 0xF5
REG_PRESSURE = 0xF7
REG_CALIBRATION = 0x88
REG_CALIBRATION_H1 = 0xA1
REG_CALIBRATION_H2LSB = 0xE1
CMD_RESET = 0xE0
WHO_AM_I = 0xD0
CHIP_ID = 0x60
)
const (
SEALEVEL_PRESSURE float32 = 1013.25 // in hPa
)
+71
View File
@@ -0,0 +1,71 @@
// Package buzzer provides a very simplistic driver for a connected buzzer or low-fidelity speaker.
//
package buzzer // import "tinygo.org/x/drivers/buzzer"
import (
"machine"
"time"
)
// Device wraps a GPIO connection to a buzzer.
type Device struct {
pin machine.Pin
High bool
BPM float64
}
// New returns a new buzzer driver given which pin to use
func New(pin machine.Pin) Device {
return Device{
pin: pin,
High: false,
BPM: 96.0,
}
}
// On sets the buzzer to a high state.
func (l *Device) On() (err error) {
l.pin.Set(true)
l.High = true
return
}
// Off sets the buzzer to a low state.
func (l *Device) Off() (err error) {
l.pin.Set(false)
l.High = false
return
}
// Toggle sets the buzzer to the opposite of it's current state
func (l *Device) Toggle() (err error) {
if l.High {
err = l.Off()
} else {
err = l.On()
}
return
}
// Tone plays a tone of the requested frequency and duration.
func (l *Device) Tone(hz, duration float64) (err error) {
// calculation based off https://www.arduino.cc/en/Tutorial/Melody
tone := (1.0 / (2.0 * hz)) * 1000000.0
tempo := ((60 / l.BPM) * (duration * 1000))
for i := 0.0; i < tempo*1000; i += tone * 2.0 {
if err = l.On(); err != nil {
return
}
time.Sleep(time.Duration(tone) * time.Microsecond)
if err = l.Off(); err != nil {
return
}
time.Sleep(time.Duration(tone) * time.Microsecond)
}
return
}
+121
View File
@@ -0,0 +1,121 @@
package buzzer
const (
Whole = 4
Half = 2
Quarter = 1
Eighth = 0.500
)
// The values seem like they are little off, so feel free to make corrections, as needed.
const (
Rest = 0
C0 = 16.35
Db0 = 17.32
D0 = 18.35
Eb0 = 19.45
E0 = 20.60
F0 = 21.83
Gb0 = 23.12
G0 = 24.50
Ab0 = 25.96
A0 = 27.50
Bb0 = 29.14
B0 = 30.87
C1 = 32.70
Db1 = 34.65
D1 = 36.71
Eb1 = 38.89
E1 = 41.20
F1 = 43.65
Gb1 = 46.25
G1 = 49.00
Ab1 = 51.91
A1 = 55.00
Bb1 = 58.27
B1 = 61.74
C2 = 65.41
Db2 = 69.30
D2 = 73.42
Eb2 = 77.78
E2 = 82.41
F2 = 87.31
Gb2 = 92.50
G2 = 98.00
Ab2 = 103.83
A2 = 110.00
Bb2 = 116.54
B2 = 123.47
C3 = 130.81
Db3 = 138.59
D3 = 146.83
Eb3 = 155.56
E3 = 164.81
F3 = 174.61
Gb3 = 185.00
G3 = 196.00
Ab3 = 207.65
A3 = 220.00
Bb3 = 233.08
B3 = 246.94
C4 = 261.63
Db4 = 277.18
D4 = 293.66
Eb4 = 311.13
E4 = 329.63
F4 = 349.23
Gb4 = 369.99
G4 = 392.00
Ab4 = 415.30
A4 = 440.00
Bb4 = 466.16
B4 = 493.88
C5 = 523.25
Db5 = 554.37
D5 = 587.33
Eb5 = 622.25
E5 = 659.25
F5 = 698.46
Gb5 = 739.99
G5 = 783.99
Ab5 = 830.61
A5 = 880.00
Bb5 = 932.33
B5 = 987.77
C6 = 1046.50
Db6 = 1108.73
D6 = 1174.66
Eb6 = 1244.51
E6 = 1318.51
F6 = 1396.91
Gb6 = 1479.98
G6 = 1567.98
Ab6 = 1661.22
A6 = 1760.00
Bb6 = 1864.66
B6 = 1975.53
C7 = 2093.00
Db7 = 2217.46
D7 = 2349.32
Eb7 = 2489.02
E7 = 2637.02
F7 = 2793.83
Gb7 = 2959.96
G7 = 3135.96
Ab7 = 3322.44
A7 = 3520.00
Bb7 = 3729.31
B7 = 3951.07
C8 = 4186.01
Db8 = 4434.92
D8 = 4698.63
Eb8 = 4978.03
E8 = 5274.04
F8 = 5587.65
Gb8 = 5919.91
G8 = 6271.93
Ab8 = 6644.88
A8 = 7040.00
Bb8 = 7458.62
B8 = 7902.13
)
+2 -2
View File
@@ -74,7 +74,7 @@ func (d *Device) Seek(offset int64, whence int) (int64, error) {
case 2:
whence = SRAMEndAddress
default:
return 0, errors.New("Invalid starting point")
return 0, errors.New("invalid starting point")
}
d.AddressSRAM = uint8(whence) + uint8(offset)
if d.AddressSRAM > SRAMEndAddress {
@@ -87,7 +87,7 @@ func (d *Device) Seek(offset int64, whence int) (int64, error) {
// returns number of bytes written and error, if any
func (d *Device) Write(data []byte) (n int, err error) {
if int(d.AddressSRAM)+len(data)-1 > SRAMEndAddress {
return 0, errors.New("Writing outside of SRAM")
return 0, errors.New("writing outside of SRAM")
}
buffer := make([]byte, len(data)+1)
buffer[0] = d.AddressSRAM
+82
View File
@@ -0,0 +1,82 @@
# ESP-AT Driver
This package provides a driver to use a separate connected WiFi processor either the ESP8266 or the ESP32 from Espressif.
The way this driver works is by using the UART interface to communicate with the WiFi chip using the Espressif AT command set.
## ESP-AT Firmware Installation
In order to use this driver, you must have the ESP-AT firmware installed on the ESP8266/ESP32 chip.
### Installing on Arduino Nano33 IoT
In order to install the needed firmware on the Arduino Nano33 IoT board's built-in NINA W102 chip, you will need to use the `arduino-nano33-iot` branch of this fork of the firmware:
https://github.com/hybridgroup/esp32-at
To flash this firmware on the Arduino Nano33 IoT you will need to follow the following procedure:
- Using the normal Arduino software, load the `SerialNINAPassthrough` sketch on to the board.
- Flash the NINA 102 firmware using the `make flash` command in the https://github.com/hybridgroup/esp32-at repo.
You only need to do this one time, and then the correct ESP-AT firmware will be on the NINA chip, and you can just flash the Arduino Nano33 IoT board using TinyGo. We should be able to remove some of these step in a future release of this software.
### Installing on ESP32
The official repository for the ESP-AT for the ESP32 processor is located here:
https://github.com/espressif/esp32-at
Your best option is to follow the instructions in the official repo.
### Installing on ESP8266
The official repository for the AT command set firmware for the ESP8266 processor is located here:
https://github.com/espressif/ESP8266_NONOS_SDK
First clone the repo:
```shell
git clone https://github.com/espressif/ESP8266_NONOS_SDK.git
```
You will also need to install the Espressif `esptool` to flash this firmware on your ESP8266:
https://github.com/espressif/esptool
Once you have obtained the binary code, and installed `esptool`, you can flash the ESP8266.
Here is an example shell script that flashes a Wemos D1 Mini board:
```python
#!/bin/sh
SPToolDir="$HOME/.local/lib/python2.7/site-packages"
FirmwareDir="$HOME/Development/ESP8266_NONOS_SDK"
cd "$SPToolDir"
port=/dev/ttyUSB0
if [ ! -c $port ]; then
port=/dev/ttyUSB0
fi
if [ ! -c $port ]; then
echo "No device appears to be plugged in. Stopping."
fi
printf "Writing AT firmware to the Wemos D1 Mini in 3..."
sleep 1; printf "2..."
sleep 1; printf "1..."
sleep 1; echo "done."
echo "Erasing the flash first"
esptool.py --port $port erase_flash
esptool.py --port /dev/ttyUSB0 --baud 115200 \
write_flash -fm dio -ff 20m -fs detect \
0x0000 "$FirmwareDir/bin/boot_v1.7.bin" \
0x01000 "$FirmwareDir/bin/at/512+512/user1.1024.new.2.bin" \
0x3fc000 "$FirmwareDir/bin/esp_init_data_default_v05.bin" \
0x7e000 "$FirmwareDir/bin/blank.bin" \
0x3fe000 "$FirmwareDir/bin/blank.bin"
echo "Check the boot by typing: miniterm $port 74800"
echo " and then resetting. Use Ctrl-] to quit miniterm."
```
+16 -9
View File
@@ -42,12 +42,14 @@ const (
Disconnect = "+CWQAP"
// Set softAP configuration. This also activates the ESP8266/ESP32 to act as an access point.
// The settings will not be saved in flash memory, so they will be forgotten on next reset.
SoftAPConfigCurrent = "+CWSAP_CUR"
// On the ESP8266 the settings will not be saved in flash memory, so they will be forgotten on next reset.
// On the ESP32 the settings WILL be saved in flash memory, so they will be used on next reset.
SoftAPConfigCurrent = "+CWSAP"
// Set softAP configuration as saved in flash. This also activates the ESP8266/ESP32 to act as an
// access point. The settings will be saved in flash memory, so they will be used on next reset.
SoftAPConfigFlash = "+CWSAP_DEF"
// Set softAP configuration. This also activates the ESP8266/ESP32 to act as an access point.
// On the ESP8266 the settings will not be saved in flash memory, so they will be forgotten on next reset.
// On the ESP32 the settings WILL be saved in flash memory, so they will be used on next reset.
SoftAPConfigFlash = "+CWSAP"
// List station IP's connected to softAP
ListConnectedIP = "+CWLIF"
@@ -65,12 +67,14 @@ const (
SetStationIP = "+CIPSTA"
// Set IP address of ESP8266/ESP32 when acting as access point.
// The IP address will not be saved in flash memory, so it will be forgotten on next reset.
SetSoftAPIPCurrent = "+CIPAP_CUR"
// On the ESP8266 the IP address will not be saved in flash memory, so it will be forgotten on next reset.
// On the ESP32 the IP address WILL be saved in flash memory, so it will be used on next reset.
SetSoftAPIPCurrent = "+CIPAP"
// Set IP address of ESP8266/ESP32 when acting as access point.
// The IP address will be saved in flash memory, so they will be used on next reset.
SetSoftAPIPFlash = "+CIPAP_DEF"
// On the ESP8266 the IP address will not be saved in flash memory, so it will be forgotten on next reset.
// On the ESP32 the IP address WILL be saved in flash memory, so it will be used on next reset.
SetSoftAPIPFlash = "+CIPAP"
)
// TCP/IP commands
@@ -81,6 +85,9 @@ const (
// Establish TCP connection or register UDP port
TCPConnect = "+CIPSTART"
// DNS Lookup
TCPDNSLookup = "+CIPDOMAIN"
// Send Data
TCPSend = "+CIPSEND"
+17 -10
View File
@@ -36,6 +36,9 @@ type Device struct {
socketdata []byte
}
// 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 machine.UART) *Device {
return &Device{bus: b, response: make([]byte, 512), socketdata: make([]byte, 0, 1024)}
@@ -43,6 +46,7 @@ func New(b machine.UART) *Device {
// Configure sets up the device for communication.
func (d Device) Configure() {
ActiveDevice = &d
}
// Connected checks if there is communication with the ESP8266/ESP32.
@@ -50,7 +54,7 @@ func (d *Device) Connected() bool {
d.Execute(Test)
// handle response here, should include "OK"
r := d.Response()
r := d.Response(100)
if strings.Contains(string(r), "OK") {
return true
}
@@ -93,7 +97,7 @@ func (d Device) Set(cmd, params string) error {
// Version returns the ESP8266/ESP32 firmware version info.
func (d Device) Version() []byte {
d.Execute(Version)
return d.Response()
return d.Response(100)
}
// Echo sets the ESP8266/ESP32 echo setting.
@@ -104,7 +108,7 @@ func (d Device) Echo(set bool) {
d.Execute(EchoConfigOff)
}
// TODO: check for success
d.Response()
d.Response(100)
}
// Reset restarts the ESP8266/ESP32 firmware. Due to how the baud rate changes,
@@ -112,13 +116,13 @@ func (d Device) Echo(set bool) {
// what you are doing when you call this.
func (d Device) Reset() {
d.Execute(Restart)
d.Response()
d.Response(100)
}
// ReadSocket returns the data that has already been read in from the responses.
func (d *Device) ReadSocket(b []byte) (n int, err error) {
// make sure no data in buffer
d.Response()
d.Response(100)
count := len(b)
if len(b) >= len(d.socketdata) {
@@ -137,8 +141,11 @@ func (d *Device) ReadSocket(b []byte) (n int, err error) {
}
// Response gets the next response bytes from the ESP8266/ESP32.
func (d *Device) Response() []byte {
var i, retries int
// The call will retry for up to timeout milliseconds before returning nothing.
func (d *Device) Response(timeout int) []byte {
var i int
pause := 10 // pause to wait for 10 ms
retries := timeout / pause
header := make([]byte, 2)
for {
@@ -174,13 +181,13 @@ func (d *Device) Response() []byte {
i++
}
}
retries++
if retries > 2 {
retries--
if retries == 0 {
break
}
// pause to make sure is no more data to be read
time.Sleep(10 * time.Millisecond)
time.Sleep(time.Duration(pause) * time.Millisecond)
}
return d.response[:i]
}
+188
View File
@@ -0,0 +1,188 @@
package mqtt
import (
"errors"
"strings"
"time"
"github.com/eclipse/paho.mqtt.golang/packets"
"tinygo.org/x/drivers/espat"
"tinygo.org/x/drivers/espat/net"
"tinygo.org/x/drivers/espat/tls"
)
// NewClient will create an MQTT v3.1.1 client with all of the options specified
// in the provided ClientOptions. The client must have the Connect method called
// on it before it may be used. This is to make sure resources (such as a net
// connection) are created before the application is actually ready.
func NewClient(o *ClientOptions) Client {
c := &mqttclient{opts: o, adaptor: o.Adaptor}
return c
}
type mqttclient struct {
adaptor *espat.Device
conn net.Conn
connected bool
opts *ClientOptions
mid uint16
}
// AddRoute allows you to add a handler for messages on a specific topic
// without making a subscription. For example having a different handler
// for parts of a wildcard subscription
func (c *mqttclient) AddRoute(topic string, callback MessageHandler) {
return
}
// IsConnected returns a bool signifying whether
// the client is connected or not.
func (c *mqttclient) IsConnected() bool {
return c.connected
}
// IsConnectionOpen return a bool signifying whether the client has an active
// connection to mqtt broker, i.e not in disconnected or reconnect mode
func (c *mqttclient) IsConnectionOpen() bool {
return c.connected
}
// Connect will create a connection to the message broker.
func (c *mqttclient) Connect() Token {
var err error
// make connection
if strings.Contains(c.opts.Servers, "ssl://") {
url := strings.TrimPrefix(c.opts.Servers, "ssl://")
c.conn, err = tls.Dial("tcp", url, nil)
if err != nil {
return &mqtttoken{err: err}
}
} else if strings.Contains(c.opts.Servers, "tcp://") {
url := strings.TrimPrefix(c.opts.Servers, "tcp://")
c.conn, err = net.Dial("tcp", url)
if err != nil {
return &mqtttoken{err: err}
}
} else {
// invalid protocol
return &mqtttoken{err: errors.New("invalid protocol")}
}
// send the MQTT connect message
connectPkt := packets.NewControlPacket(packets.Connect).(*packets.ConnectPacket)
connectPkt.Qos = 0
if c.opts.Username != "" {
connectPkt.Username = c.opts.Username
connectPkt.UsernameFlag = true
}
if c.opts.Password != "" {
connectPkt.Password = []byte(c.opts.Password)
connectPkt.PasswordFlag = true
}
connectPkt.ClientIdentifier = c.opts.ClientID //"tinygo-client-" + randomString(10)
connectPkt.ProtocolVersion = byte(c.opts.ProtocolVersion)
connectPkt.ProtocolName = "MQTT"
connectPkt.Keepalive = 30
err = connectPkt.Write(c.conn)
if err != nil {
return &mqtttoken{err: err}
}
// TODO: handle timeout
for {
packet, _ := packets.ReadPacket(c.conn)
if packet != nil {
ack, ok := packet.(*packets.ConnackPacket)
if ok {
if ack.ReturnCode == 0 {
// success
return &mqtttoken{}
}
// otherwise something went wrong
return &mqtttoken{err: errors.New(packet.String())}
}
}
time.Sleep(100 * time.Millisecond)
}
c.connected = true
return &mqtttoken{}
}
// Disconnect will end the connection with the server, but not before waiting
// the specified number of milliseconds to wait for existing work to be
// completed.
func (c *mqttclient) Disconnect(quiesce uint) {
c.conn.Close()
return
}
// Publish will publish a message with the specified QoS and content
// to the specified topic.
// Returns a token to track delivery of the message to the broker
func (c *mqttclient) Publish(topic string, qos byte, retained bool, payload interface{}) Token {
pub := packets.NewControlPacket(packets.Publish).(*packets.PublishPacket)
pub.Qos = qos
pub.TopicName = topic
switch payload.(type) {
case string:
pub.Payload = []byte(payload.(string))
case []byte:
pub.Payload = payload.([]byte)
default:
return &mqtttoken{err: errors.New("Unknown payload type")}
}
pub.MessageID = c.mid
c.mid++
err := pub.Write(c.conn)
return &mqtttoken{err: err}
}
// Subscribe starts a new subscription. Provide a MessageHandler to be executed when
// a message is published on the topic provided.
func (c *mqttclient) Subscribe(topic string, qos byte, callback MessageHandler) Token {
return &mqtttoken{}
}
// SubscribeMultiple starts a new subscription for multiple topics. Provide a MessageHandler to
// be executed when a message is published on one of the topics provided.
func (c *mqttclient) SubscribeMultiple(filters map[string]byte, callback MessageHandler) Token {
return &mqtttoken{}
}
// Unsubscribe will end the subscription from each of the topics provided.
// Messages published to those topics from other clients will no longer be
// received.
func (c *mqttclient) Unsubscribe(topics ...string) Token {
return &mqtttoken{}
}
// OptionsReader returns a ClientOptionsReader which is a copy of the clientoptions
// in use by the client.
func (c *mqttclient) OptionsReader() ClientOptionsReader {
r := ClientOptionsReader{}
return r
}
type mqtttoken struct {
err error
}
func (t *mqtttoken) Wait() bool {
return true
}
func (t *mqtttoken) WaitTimeout(time.Duration) bool {
return true
}
func (t *mqtttoken) Error() error {
return t.err
}
+245
View File
@@ -0,0 +1,245 @@
// The following code is a slightly modified version of code taken from the Paho MQTT library.
// It is here until TinyGo can compile the "net" package from the standard library, at which time
// it can be removed.
/*
* Copyright (c) 2013 IBM Corp.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Seth Hoenig
* Allan Stockdill-Mander
* Mike Robertson
*/
// Portions copyright © 2018 TIBCO Software Inc.
package mqtt
import (
"strings"
"time"
"tinygo.org/x/drivers/espat"
)
const (
disconnected uint32 = iota
connecting
reconnecting
connected
)
// Client is the interface definition for a Client as used by this
// library, the interface is primarily to allow mocking tests.
//
// It is an MQTT v3.1.1 client for communicating
// with an MQTT server using non-blocking methods that allow work
// to be done in the background.
// An application may connect to an MQTT server using:
// A plain TCP socket
// A secure SSL/TLS socket
// A websocket
// To enable ensured message delivery at Quality of Service (QoS) levels
// described in the MQTT spec, a message persistence mechanism must be
// used. This is done by providing a type which implements the Store
// interface. For convenience, FileStore and MemoryStore are provided
// implementations that should be sufficient for most use cases. More
// information can be found in their respective documentation.
// Numerous connection options may be specified by configuring a
// and then supplying a ClientOptions type.
type Client interface {
// IsConnected returns a bool signifying whether
// the client is connected or not.
IsConnected() bool
// IsConnectionOpen return a bool signifying wether the client has an active
// connection to mqtt broker, i.e not in disconnected or reconnect mode
IsConnectionOpen() bool
// Connect will create a connection to the message broker, by default
// it will attempt to connect at v3.1.1 and auto retry at v3.1 if that
// fails
Connect() Token
// Disconnect will end the connection with the server, but not before waiting
// the specified number of milliseconds to wait for existing work to be
// completed.
Disconnect(quiesce uint)
// Publish will publish a message with the specified QoS and content
// to the specified topic.
// Returns a token to track delivery of the message to the broker
Publish(topic string, qos byte, retained bool, payload interface{}) Token
// Subscribe starts a new subscription. Provide a MessageHandler to be executed when
// a message is published on the topic provided, or nil for the default handler
Subscribe(topic string, qos byte, callback MessageHandler) Token
// SubscribeMultiple starts a new subscription for multiple topics. Provide a MessageHandler to
// be executed when a message is published on one of the topics provided, or nil for the
// default handler
SubscribeMultiple(filters map[string]byte, callback MessageHandler) Token
// Unsubscribe will end the subscription from each of the topics provided.
// Messages published to those topics from other clients will no longer be
// received.
Unsubscribe(topics ...string) Token
// AddRoute allows you to add a handler for messages on a specific topic
// without making a subscription. For example having a different handler
// for parts of a wildcard subscription
AddRoute(topic string, callback MessageHandler)
// OptionsReader returns a ClientOptionsReader which is a copy of the clientoptions
// in use by the client.
OptionsReader() ClientOptionsReader
}
// Token defines the interface for the tokens used to indicate when
// actions have completed.
type Token interface {
Wait() bool
WaitTimeout(time.Duration) bool
Error() error
}
// MessageHandler is a callback type which can be set to be
// executed upon the arrival of messages published to topics
// to which the client is subscribed.
type MessageHandler func(Client, Message)
// Message defines the externals that a message implementation must support
// these are received messages that are passed to the callbacks, not internal
// messages
type Message interface {
Duplicate() bool
Qos() byte
Retained() bool
Topic() string
MessageID() uint16
Payload() []byte
Ack()
}
type message struct {
duplicate bool
qos byte
retained bool
topic string
messageID uint16
payload []byte
ack func()
}
func (m *message) Duplicate() bool {
return m.duplicate
}
func (m *message) Qos() byte {
return m.qos
}
func (m *message) Retained() bool {
return m.retained
}
func (m *message) Topic() string {
return m.topic
}
func (m *message) MessageID() uint16 {
return m.messageID
}
func (m *message) Payload() []byte {
return m.payload
}
func (m *message) Ack() {
return
}
// ClientOptionsReader provides an interface for reading ClientOptions after the client has been initialized.
type ClientOptionsReader struct {
options *ClientOptions
}
// ClientOptions contains configurable options for an MQTT Client.
type ClientOptions struct {
Adaptor *espat.Device
//Servers []*url.URL
Servers string
ClientID string
Username string
Password string
//CredentialsProvider CredentialsProvider
CleanSession bool
Order bool
WillEnabled bool
WillTopic string
WillPayload []byte
WillQos byte
WillRetained bool
ProtocolVersion uint
protocolVersionExplicit bool
//TLSConfig *tls.Config
KeepAlive int64
PingTimeout time.Duration
ConnectTimeout time.Duration
MaxReconnectInterval time.Duration
AutoReconnect bool
//Store Store
//DefaultPublishHandler MessageHandler
//OnConnect OnConnectHandler
//OnConnectionLost ConnectionLostHandler
WriteTimeout time.Duration
MessageChannelDepth uint
ResumeSubs bool
//HTTPHeaders http.Header
}
// NewClientOptions returns a new ClientOptions struct.
func NewClientOptions(adaptor *espat.Device) *ClientOptions {
return &ClientOptions{Adaptor: adaptor, ProtocolVersion: 4}
}
// AddBroker adds a broker URI to the list of brokers to be used. The format should be
// scheme://host:port
// Where "scheme" is one of "tcp", "ssl", or "ws", "host" is the ip-address (or hostname)
// and "port" is the port on which the broker is accepting connections.
//
// Default values for hostname is "127.0.0.1", for schema is "tcp://".
//
// An example broker URI would look like: tcp://foobar.com:1883
func (o *ClientOptions) AddBroker(server string) *ClientOptions {
if len(server) > 0 && server[0] == ':' {
server = "127.0.0.1" + server
}
if !strings.Contains(server, "://") {
server = "tcp://" + server
}
o.Servers = server
return o
}
// SetClientID will set the client id to be used by this client when
// connecting to the MQTT broker. According to the MQTT v3.1 specification,
// a client id mus be no longer than 23 characters.
func (o *ClientOptions) SetClientID(id string) *ClientOptions {
o.ClientID = id
return o
}
// SetUsername will set the username to be used by this client when connecting
// to the MQTT broker. Note: without the use of SSL/TLS, this information will
// be sent in plaintext accross the wire.
func (o *ClientOptions) SetUsername(u string) *ClientOptions {
o.Username = u
return o
}
// SetPassword will set the password to be used by this client when connecting
// to the MQTT broker. Note: without the use of SSL/TLS, this information will
// be sent in plaintext accross the wire.
func (o *ClientOptions) SetPassword(p string) *ClientOptions {
o.Password = p
return o
}
-143
View File
@@ -1,143 +0,0 @@
package espat
import (
"strconv"
"time"
)
// DialUDP makes a UDP network connection. raadr is the port that the messages will
// be sent to, and laddr is the port that will be listened to in order to
// receive incoming messages.
func (d Device) DialUDP(network string, laddr, raddr *UDPAddr) (*SerialConn, error) {
addr := raddr.IP.String()
sendport := strconv.Itoa(raddr.Port)
listenport := strconv.Itoa(laddr.Port)
// disconnect any old socket
d.DisconnectSocket()
// connect new socket
d.ConnectUDPSocket(addr, sendport, listenport)
return &SerialConn{Adaptor: &d, laddr: laddr, raddr: raddr}, nil
}
// ListenUDP listens for UDP connections on the port listed in laddr.
func (d Device) ListenUDP(network string, laddr *UDPAddr) (*SerialConn, error) {
addr := "0"
sendport := "0"
listenport := strconv.Itoa(laddr.Port)
// disconnect any old socket
d.DisconnectSocket()
// connect new socket
d.ConnectUDPSocket(addr, sendport, listenport)
return &SerialConn{Adaptor: &d, laddr: laddr}, nil
}
// SerialConn is a loosely net.Conn compatible intended to support
// TCP/UDP over serial.
type SerialConn struct {
Adaptor *Device
laddr *UDPAddr
raddr *UDPAddr
}
// Read reads data from the connection.
// TODO: implement the full method functionality:
// Read can be made to time out and return an Error with Timeout() == true
// after a fixed time limit; see SetDeadline and SetReadDeadline.
func (c *SerialConn) Read(b []byte) (n int, err error) {
// read only the data that has been received via "+IPD" socket
return c.Adaptor.ReadSocket(b)
}
// Write writes data to the connection.
// TODO: implement the full method functionality for timeouts.
// Write can be made to time out and return an Error with Timeout() == true
// after a fixed time limit; see SetDeadline and SetWriteDeadline.
func (c *SerialConn) Write(b []byte) (n int, err error) {
// specify that is a data transfer to the
// currently open socket, not commands to the ESP8266/ESP32.
c.Adaptor.StartSocketSend(len(b))
return c.Adaptor.Write(b)
}
// Close closes the connection.
// Currently only supports a single Read or Write operations without blocking.
func (c *SerialConn) Close() error {
c.Adaptor.DisconnectSocket()
return nil
}
// LocalAddr returns the local network address.
func (c *SerialConn) LocalAddr() UDPAddr {
return *c.laddr
}
// RemoteAddr returns the remote network address.
func (c *SerialConn) RemoteAddr() UDPAddr {
return *c.laddr
}
// SetDeadline sets the read and write deadlines associated
// with the connection. It is equivalent to calling both
// SetReadDeadline and SetWriteDeadline.
//
// A deadline is an absolute time after which I/O operations
// fail with a timeout (see type Error) instead of
// blocking. The deadline applies to all future and pending
// I/O, not just the immediately following call to Read or
// Write. After a deadline has been exceeded, the connection
// can be refreshed by setting a deadline in the future.
//
// An idle timeout can be implemented by repeatedly extending
// the deadline after successful Read or Write calls.
//
// A zero value for t means I/O operations will not time out.
func (c *SerialConn) SetDeadline(t time.Time) error {
return nil
}
// SetReadDeadline sets the deadline for future Read calls
// and any currently-blocked Read call.
// A zero value for t means Read will not time out.
func (c *SerialConn) SetReadDeadline(t time.Time) error {
return nil
}
// SetWriteDeadline sets the deadline for future Write calls
// and any currently-blocked Write call.
// Even if write times out, it may return n > 0, indicating that
// some of the data was successfully written.
// A zero value for t means Write will not time out.
func (c *SerialConn) SetWriteDeadline(t time.Time) error {
return nil
}
// The following definitions are here to support a Golang standard package
// net-compatible interface for IP until TinyGo can compile the net package.
// IP is an IP address. Unlike the standard implementation, it is only
// a buffer of bytes that contains the string form of the IP address, not the
// full byte format used by the Go standard .
type IP []byte
// UDPAddr here to serve as compatible type. until TinyGo can compile the net package.
type UDPAddr struct {
IP IP
Port int
Zone string // IPv6 scoped addressing zone; added in Go 1.1
}
// ParseIP parses s as an IP address, returning the result.
func ParseIP(s string) IP {
return IP([]byte(s))
}
// String returns the string form of the IP address ip.
func (ip IP) String() string {
return string(ip)
}
+391
View File
@@ -0,0 +1,391 @@
// package net is intended to provide compatible interfaces with the
// Go standard library's net package.
package net
import (
"errors"
"strconv"
"strings"
"time"
"tinygo.org/x/drivers/espat"
)
// DialUDP makes a UDP network connection. raadr is the port that the messages will
// be sent to, and laddr is the port that will be listened to in order to
// receive incoming messages.
func DialUDP(network string, laddr, raddr *UDPAddr) (*UDPSerialConn, error) {
addr := raddr.IP.String()
sendport := strconv.Itoa(raddr.Port)
listenport := strconv.Itoa(laddr.Port)
// disconnect any old socket
espat.ActiveDevice.DisconnectSocket()
// connect new socket
espat.ActiveDevice.ConnectUDPSocket(addr, sendport, listenport)
return &UDPSerialConn{SerialConn: SerialConn{Adaptor: espat.ActiveDevice}, laddr: laddr, raddr: raddr}, nil
}
// ListenUDP listens for UDP connections on the port listed in laddr.
func ListenUDP(network string, laddr *UDPAddr) (*UDPSerialConn, error) {
addr := "0"
sendport := "0"
listenport := strconv.Itoa(laddr.Port)
// disconnect any old socket
espat.ActiveDevice.DisconnectSocket()
// connect new socket
espat.ActiveDevice.ConnectUDPSocket(addr, sendport, listenport)
return &UDPSerialConn{SerialConn: SerialConn{Adaptor: espat.ActiveDevice}, laddr: laddr}, nil
}
// DialTCP makes a TCP network connection. raadr is the port that the messages will
// be sent to, and laddr is the port that will be listened to in order to
// receive incoming messages.
func DialTCP(network string, laddr, raddr *TCPAddr) (*TCPSerialConn, error) {
addr := raddr.IP.String()
sendport := strconv.Itoa(raddr.Port)
// disconnect any old socket
espat.ActiveDevice.DisconnectSocket()
// connect new socket
espat.ActiveDevice.ConnectTCPSocket(addr, sendport)
return &TCPSerialConn{SerialConn: SerialConn{Adaptor: espat.ActiveDevice}, laddr: laddr, raddr: raddr}, nil
}
// Dial connects to the address on the named network.
// It tries to provide a mostly compatible interface
// to net.Dial().
func Dial(network, address string) (Conn, error) {
switch network {
case "tcp":
raddr, err := ResolveTCPAddr(network, address)
if err != nil {
return nil, err
}
c, e := DialTCP(network, &TCPAddr{}, raddr)
return c.opConn(), e
case "udp":
raddr, err := ResolveUDPAddr(network, address)
if err != nil {
return nil, err
}
c, e := DialUDP(network, &UDPAddr{}, raddr)
return c.opConn(), e
default:
return nil, errors.New("invalid network for dial")
}
}
// SerialConn is a loosely net.Conn compatible implementation
type SerialConn struct {
Adaptor *espat.Device
}
// UDPSerialConn is a loosely net.Conn compatible intended to support
// UDP over serial.
type UDPSerialConn struct {
SerialConn
laddr *UDPAddr
raddr *UDPAddr
}
// NewUDPSerialConn returns a new UDPSerialConn/
func NewUDPSerialConn(c SerialConn, laddr, raddr *UDPAddr) *UDPSerialConn {
return &UDPSerialConn{SerialConn: c, raddr: raddr}
}
// TCPSerialConn is a loosely net.Conn compatible intended to support
// TCP over serial.
type TCPSerialConn struct {
SerialConn
laddr *TCPAddr
raddr *TCPAddr
}
// NewTCPSerialConn returns a new TCPSerialConn/
func NewTCPSerialConn(c SerialConn, laddr, raddr *TCPAddr) *TCPSerialConn {
return &TCPSerialConn{SerialConn: c, raddr: raddr}
}
// Read reads data from the connection.
// TODO: implement the full method functionality:
// Read can be made to time out and return an Error with Timeout() == true
// after a fixed time limit; see SetDeadline and SetReadDeadline.
func (c *SerialConn) Read(b []byte) (n int, err error) {
// read only the data that has been received via "+IPD" socket
return c.Adaptor.ReadSocket(b)
}
// Write writes data to the connection.
// TODO: implement the full method functionality for timeouts.
// Write can be made to time out and return an Error with Timeout() == true
// after a fixed time limit; see SetDeadline and SetWriteDeadline.
func (c *SerialConn) Write(b []byte) (n int, err error) {
// specify that is a data transfer to the
// currently open socket, not commands to the ESP8266/ESP32.
c.Adaptor.StartSocketSend(len(b))
return c.Adaptor.Write(b)
}
// Close closes the connection.
// Currently only supports a single Read or Write operations without blocking.
func (c *SerialConn) Close() error {
c.Adaptor.DisconnectSocket()
return nil
}
// LocalAddr returns the local network address.
func (c *UDPSerialConn) LocalAddr() Addr {
return c.laddr.opAddr()
}
// RemoteAddr returns the remote network address.
func (c *UDPSerialConn) RemoteAddr() Addr {
return c.laddr.opAddr()
}
func (c *UDPSerialConn) opConn() Conn {
if c == nil {
return nil
}
return c
}
// LocalAddr returns the local network address.
func (c *TCPSerialConn) LocalAddr() Addr {
return c.laddr.opAddr()
}
// RemoteAddr returns the remote network address.
func (c *TCPSerialConn) RemoteAddr() Addr {
return c.laddr.opAddr()
}
func (c *TCPSerialConn) opConn() Conn {
if c == nil {
return nil
}
return c
}
// SetDeadline sets the read and write deadlines associated
// with the connection. It is equivalent to calling both
// SetReadDeadline and SetWriteDeadline.
//
// A deadline is an absolute time after which I/O operations
// fail with a timeout (see type Error) instead of
// blocking. The deadline applies to all future and pending
// I/O, not just the immediately following call to Read or
// Write. After a deadline has been exceeded, the connection
// can be refreshed by setting a deadline in the future.
//
// An idle timeout can be implemented by repeatedly extending
// the deadline after successful Read or Write calls.
//
// A zero value for t means I/O operations will not time out.
func (c *SerialConn) SetDeadline(t time.Time) error {
return nil
}
// SetReadDeadline sets the deadline for future Read calls
// and any currently-blocked Read call.
// A zero value for t means Read will not time out.
func (c *SerialConn) SetReadDeadline(t time.Time) error {
return nil
}
// SetWriteDeadline sets the deadline for future Write calls
// and any currently-blocked Write call.
// Even if write times out, it may return n > 0, indicating that
// some of the data was successfully written.
// A zero value for t means Write will not time out.
func (c *SerialConn) SetWriteDeadline(t time.Time) error {
return nil
}
// ResolveTCPAddr returns an address of TCP end point.
//
// The network must be a TCP network name.
//
func ResolveTCPAddr(network, address string) (*TCPAddr, error) {
// TODO: make sure network is 'tcp'
// separate domain from port, if any
r := strings.Split(address, ":")
addr, err := espat.ActiveDevice.GetDNS(r[0])
if err != nil {
return nil, err
}
ip := IP(addr)
if len(r) > 1 {
port, e := strconv.Atoi(r[1])
if e != nil {
return nil, e
}
return &TCPAddr{IP: ip, Port: port}, nil
}
return &TCPAddr{IP: ip}, nil
}
// ResolveUDPAddr returns an address of UDP end point.
//
// The network must be a UDP network name.
//
func ResolveUDPAddr(network, address string) (*UDPAddr, error) {
// TODO: make sure network is 'udp'
// separate domain from port, if any
r := strings.Split(address, ":")
addr, err := espat.ActiveDevice.GetDNS(r[0])
if err != nil {
return nil, err
}
ip := IP(addr)
if len(r) > 1 {
port, e := strconv.Atoi(r[1])
if e != nil {
return nil, e
}
return &UDPAddr{IP: ip, Port: port}, nil
}
return &UDPAddr{IP: ip}, nil
}
// The following definitions are here to support a Golang standard package
// net-compatible interface for IP until TinyGo can compile the net package.
// IP is an IP address. Unlike the standard implementation, it is only
// a buffer of bytes that contains the string form of the IP address, not the
// full byte format used by the Go standard .
type IP []byte
// UDPAddr here to serve as compatible type. until TinyGo can compile the net package.
type UDPAddr struct {
IP IP
Port int
Zone string // IPv6 scoped addressing zone; added in Go 1.1
}
// Network returns the address's network name, "udp".
func (a *UDPAddr) Network() string { return "udp" }
func (a *UDPAddr) String() string {
if a == nil {
return "<nil>"
}
if a.Port != 0 {
return a.IP.String() + ":" + strconv.Itoa(a.Port)
}
return a.IP.String()
}
func (a *UDPAddr) opAddr() Addr {
if a == nil {
return nil
}
return a
}
// TCPAddr here to serve as compatible type. until TinyGo can compile the net package.
type TCPAddr struct {
IP IP
Port int
Zone string // IPv6 scoped addressing zone
}
// Network returns the address's network name, "tcp".
func (a *TCPAddr) Network() string { return "tcp" }
func (a *TCPAddr) String() string {
if a == nil {
return "<nil>"
}
if a.Port != 0 {
return a.IP.String() + ":" + strconv.Itoa(a.Port)
}
return a.IP.String()
}
func (a *TCPAddr) opAddr() Addr {
if a == nil {
return nil
}
return a
}
// ParseIP parses s as an IP address, returning the result.
func ParseIP(s string) IP {
return IP([]byte(s))
}
// String returns the string form of the IP address ip.
func (ip IP) String() string {
return string(ip)
}
// Conn is a generic stream-oriented network connection.
// This interface is from the Go standard library.
type Conn interface {
// Read reads data from the connection.
// Read can be made to time out and return an Error with Timeout() == true
// after a fixed time limit; see SetDeadline and SetReadDeadline.
Read(b []byte) (n int, err error)
// Write writes data to the connection.
// Write can be made to time out and return an Error with Timeout() == true
// after a fixed time limit; see SetDeadline and SetWriteDeadline.
Write(b []byte) (n int, err error)
// Close closes the connection.
// Any blocked Read or Write operations will be unblocked and return errors.
Close() error
// LocalAddr returns the local network address.
LocalAddr() Addr
// RemoteAddr returns the remote network address.
RemoteAddr() Addr
// SetDeadline sets the read and write deadlines associated
// with the connection. It is equivalent to calling both
// SetReadDeadline and SetWriteDeadline.
//
// A deadline is an absolute time after which I/O operations
// fail with a timeout (see type Error) instead of
// blocking. The deadline applies to all future and pending
// I/O, not just the immediately following call to Read or
// Write. After a deadline has been exceeded, the connection
// can be refreshed by setting a deadline in the future.
//
// An idle timeout can be implemented by repeatedly extending
// the deadline after successful Read or Write calls.
//
// A zero value for t means I/O operations will not time out.
SetDeadline(t time.Time) error
// SetReadDeadline sets the deadline for future Read calls
// and any currently-blocked Read call.
// A zero value for t means Read will not time out.
SetReadDeadline(t time.Time) error
// SetWriteDeadline sets the deadline for future Write calls
// and any currently-blocked Write call.
// Even if write times out, it may return n > 0, indicating that
// some of the data was successfully written.
// A zero value for t means Write will not time out.
SetWriteDeadline(t time.Time) error
}
// Addr represents a network end point address.
type Addr interface {
Network() string // name of the network (for example, "tcp", "udp")
String() string // string form of address (for example, "192.0.2.1:25", "[2001:db8::1]:80")
}
+50 -22
View File
@@ -1,8 +1,9 @@
package espat
import (
"errors"
"strconv"
"time"
"strings"
)
const (
@@ -13,15 +14,28 @@ const (
TCPTransferModeUnvarnished = 1
)
// GetDNS returns the IP address for a domain name.
func (d *Device) GetDNS(domain string) (string, error) {
d.Set(TCPDNSLookup, "\""+domain+"\"")
r := strings.Split(string(d.Response(1000)), ":")
if len(r) != 2 {
return "", errors.New("Invalid domain lookup result")
}
res := strings.Split(r[1], "\r\n")
return res[0], nil
}
// ConnectTCPSocket creates a new TCP socket connection for the ESP8266/ESP32.
// Currently only supports single connection mode.
func (d *Device) ConnectTCPSocket(addr, port string) error {
protocol := "TCP"
val := "\"" + protocol + "\",\"" + addr + "\"," + port
val := "\"" + protocol + "\",\"" + addr + "\"," + port + ",120"
d.Set(TCPConnect, val)
time.Sleep(100 * time.Millisecond)
d.Response()
return nil
r := d.Response(1000)
if strings.Contains(string(r), "OK") {
return nil
}
return errors.New("ConnectTCPSocket error:" + string(r))
}
// ConnectUDPSocket creates a new UDP connection for the ESP8266/ESP32.
@@ -29,16 +43,31 @@ func (d *Device) ConnectUDPSocket(addr, sendport, listenport string) error {
protocol := "UDP"
val := "\"" + protocol + "\",\"" + addr + "\"," + sendport + "," + listenport + ",2"
d.Set(TCPConnect, val)
time.Sleep(pause * time.Millisecond)
d.Response()
return nil
r := d.Response(pause)
if strings.Contains(string(r), "OK") {
return nil
}
return errors.New("ConnectUDPSocket error:" + string(r))
}
// ConnectSSLSocket creates a new SSL socket connection for the ESP8266/ESP32.
// Currently only supports single connection mode.
func (d *Device) ConnectSSLSocket(addr, port string) error {
protocol := "SSL"
val := "\"" + protocol + "\",\"" + addr + "\"," + port + ",120"
d.Set(TCPConnect, val)
// this operation takes longer, so wait up to 6 seconds to complete.
r := d.Response(6000)
if strings.Contains(string(r), "CONNECT") {
return nil
}
return errors.New("ConnectSSLSocket error:" + string(r))
}
// DisconnectSocket disconnects the ESP8266/ESP32 from the current TCP/UDP connection.
func (d *Device) DisconnectSocket() error {
d.Execute(TCPClose)
time.Sleep(pause * time.Millisecond)
d.Response()
d.Response(pause)
return nil
}
@@ -47,15 +76,14 @@ func (d *Device) DisconnectSocket() error {
func (d *Device) SetMux(mode int) error {
val := strconv.Itoa(mode)
d.Set(TCPMultiple, val)
time.Sleep(pause * time.Millisecond)
d.Response()
d.Response(pause)
return nil
}
// GetMux returns the ESP8266/ESP32 current client TCP/UDP configuration for concurrent connections.
func (d *Device) GetMux() ([]byte, error) {
d.Query(TCPMultiple)
return d.Response(), nil
return d.Response(pause), nil
}
// SetTCPTransferMode sets the ESP8266/ESP32 current client TCP/UDP transfer mode.
@@ -63,15 +91,14 @@ func (d *Device) GetMux() ([]byte, error) {
func (d *Device) SetTCPTransferMode(mode int) error {
val := strconv.Itoa(mode)
d.Set(TransmissionMode, val)
time.Sleep(pause * time.Millisecond)
d.Response()
d.Response(pause)
return nil
}
// GetTCPTransferMode returns the ESP8266/ESP32 current client TCP/UDP transfer mode.
func (d *Device) GetTCPTransferMode() []byte {
d.Query(TransmissionMode)
return d.Response()
return d.Response(pause)
}
// StartSocketSend gets the ESP8266/ESP32 ready to receive TCP/UDP socket data.
@@ -79,10 +106,13 @@ func (d *Device) StartSocketSend(size int) error {
val := strconv.Itoa(size)
d.Set(TCPSend, val)
// TODO: wait until ">" is received, which indicates
// when ">" is received, it indicates
// ready to receive data
d.Response()
return nil
r := d.Response(pause)
if strings.Contains(string(r), ">") {
return nil
}
return errors.New("StartSocketSend error:" + string(r))
}
// EndSocketSend tell the ESP8266/ESP32 the TCP/UDP socket data sending is complete,
@@ -90,8 +120,6 @@ func (d *Device) StartSocketSend(size int) error {
func (d *Device) EndSocketSend() error {
d.Write([]byte("+++"))
// TODO: wait until ">" is received, which indicates
// ready to receive data
d.Response()
d.Response(pause)
return nil
}
+39
View File
@@ -0,0 +1,39 @@
// Package tls is intended to provide a minimal set of compatible interfaces with the
// Go standard library's tls package.
package tls
import (
"strconv"
"tinygo.org/x/drivers/espat"
"tinygo.org/x/drivers/espat/net"
)
// Dial makes a TLS network connection. It tries to provide a mostly compatible interface
// to tls.Dial().
// Dial connects to the given network address.
func Dial(network, address string, config *Config) (*net.TCPSerialConn, error) {
raddr, err := net.ResolveTCPAddr(network, address)
if err != nil {
return nil, err
}
addr := raddr.IP.String()
sendport := strconv.Itoa(raddr.Port)
// disconnect any old socket
espat.ActiveDevice.DisconnectSocket()
// connect new socket
err = espat.ActiveDevice.ConnectSSLSocket(addr, sendport)
if err != nil {
return nil, err
}
return net.NewTCPSerialConn(net.SerialConn{Adaptor: espat.ActiveDevice}, nil, raddr), nil
}
// Config is a placeholder for future compatibility with
// tls.Config.
type Config struct {
}
+16 -26
View File
@@ -2,7 +2,6 @@ package espat
import (
"strconv"
"time"
)
const (
@@ -19,15 +18,14 @@ const (
// GetWifiMode returns the ESP8266/ESP32 wifi mode.
func (d *Device) GetWifiMode() []byte {
d.Query(WifiMode)
return d.Response()
return d.Response(100)
}
// SetWifiMode sets the ESP8266/ESP32 wifi mode.
func (d *Device) SetWifiMode(mode int) error {
val := strconv.Itoa(mode)
d.Set(WifiMode, val)
time.Sleep(pause * time.Millisecond)
d.Response()
d.Response(pause)
return nil
}
@@ -36,7 +34,7 @@ func (d *Device) SetWifiMode(mode int) error {
// GetConnectedAP returns the ESP8266/ESP32 is currently connected to as a client.
func (d *Device) GetConnectedAP() []byte {
d.Query(ConnectAP)
return d.Response()
return d.Response(100)
}
// ConnectToAP connects the ESP8266/ESP32 to an access point.
@@ -44,32 +42,28 @@ func (d *Device) GetConnectedAP() []byte {
func (d *Device) ConnectToAP(ssid, pwd string, ws int) error {
val := "\"" + ssid + "\",\"" + pwd + "\""
d.Set(ConnectAP, val)
// TODO: a better way to wait for connect and check for up to ws seconds.
time.Sleep(time.Duration(ws) * time.Second)
d.Response()
d.Response(ws * 1000)
return nil
}
// DisconnectFromAP disconnects the ESP8266/ESP32 from the current access point.
func (d *Device) DisconnectFromAP() error {
d.Execute(Disconnect)
time.Sleep(1000 * time.Millisecond)
d.Response()
d.Response(1000)
return nil
}
// GetClientIP returns the ESP8266/ESP32 current client IP addess when connected to an Access Point.
func (d *Device) GetClientIP() string {
d.Query(SetStationIP)
return string(d.Response())
return string(d.Response(100))
}
// SetClientIP sets the ESP8266/ESP32 current client IP addess when connected to an Access Point.
func (d *Device) SetClientIP(ipaddr string) []byte {
val := "\"" + ipaddr + "\""
d.Set(ConnectAP, val)
time.Sleep(500 * time.Millisecond)
d.Response()
d.Response(500)
return nil
}
@@ -78,7 +72,7 @@ func (d *Device) SetClientIP(ipaddr string) []byte {
// GetAPConfig returns the ESP8266/ESP32 current configuration when acting as an Access Point.
func (d *Device) GetAPConfig() string {
d.Query(SoftAPConfigCurrent)
return string(d.Response())
return string(d.Response(100))
}
// SetAPConfig sets the ESP8266/ESP32 current configuration when acting as an Access Point.
@@ -89,29 +83,27 @@ func (d *Device) SetAPConfig(ssid, pwd string, ch, security int) error {
ecnval := strconv.Itoa(security)
val := "\"" + ssid + "\",\"" + pwd + "\"," + chval + "," + ecnval
d.Set(SoftAPConfigCurrent, val)
time.Sleep(1000 * time.Millisecond)
d.Response()
d.Response(1000)
return nil
}
// GetAPClients returns the ESP8266/ESP32 current clients when acting as an Access Point.
func (d *Device) GetAPClients() string {
d.Query(ListConnectedIP)
return string(d.Response())
return string(d.Response(100))
}
// GetAPIP returns the ESP8266/ESP32 current IP addess when configured as an Access Point.
func (d *Device) GetAPIP() string {
d.Query(SetSoftAPIPCurrent)
return string(d.Response())
return string(d.Response(100))
}
// SetAPIP sets the ESP8266/ESP32 current IP addess when configured as an Access Point.
func (d *Device) SetAPIP(ipaddr string) error {
val := "\"" + ipaddr + "\""
d.Set(SetSoftAPIPCurrent, val)
time.Sleep(500 * time.Millisecond)
d.Response()
d.Response(500)
return nil
}
@@ -119,7 +111,7 @@ func (d *Device) SetAPIP(ipaddr string) error {
// from flash storage. These settings are those used after a reset.
func (d *Device) GetAPConfigFlash() string {
d.Query(SoftAPConfigFlash)
return string(d.Response())
return string(d.Response(100))
}
// SetAPConfigFlash sets the ESP8266/ESP32 current configuration acting as an Access Point,
@@ -131,8 +123,7 @@ func (d *Device) SetAPConfigFlash(ssid, pwd string, ch, security int) error {
ecnval := strconv.Itoa(security)
val := "\"" + ssid + "\",\"" + pwd + "\"," + chval + "," + ecnval
d.Set(SoftAPConfigFlash, val)
time.Sleep(1000 * time.Millisecond)
d.Response()
d.Response(1000)
return nil
}
@@ -140,7 +131,7 @@ func (d *Device) SetAPConfigFlash(ssid, pwd string, ch, security int) error {
// This is the IP address that will be used after a reset.
func (d *Device) GetAPIPFlash() string {
d.Query(SetSoftAPIPFlash)
return string(d.Response())
return string(d.Response(100))
}
// SetAPIPFlash sets the ESP8266/ESP32 current IP addess when configured as an Access Point.
@@ -148,7 +139,6 @@ func (d *Device) GetAPIPFlash() string {
func (d *Device) SetAPIPFlash(ipaddr string) error {
val := "\"" + ipaddr + "\""
d.Set(SetSoftAPIPFlash, val)
time.Sleep(500 * time.Millisecond)
d.Response()
d.Response(500)
return nil
}
+122
View File
@@ -0,0 +1,122 @@
package main
import (
"machine"
"time"
"tinygo.org/x/drivers/at24cx"
)
func main() {
machine.I2C0.Configure(machine.I2CConfig{})
eeprom := at24cx.New(machine.I2C0)
eeprom.Configure(at24cx.Config{})
values := make([]uint8, 100)
for i := uint16(0); i < 100; i++ {
values[i] = uint8(65 + i%26)
}
_, err := eeprom.WriteAt(values, 0)
if err != nil {
println("There was an error in WriteAt:", err)
return
}
for i := uint16(0); i < 26; i++ {
err = eeprom.WriteByte(100+i, uint8(90-i))
if err != nil {
println("There was an error in WriteByte:", i, err)
return
}
time.Sleep(2 * time.Millisecond)
}
println("\n\r\n\rRead 26 bytes one by one from address 0")
println("Expected: ABCDEFGHIJKLMNOPQRSTUVWXYZ")
print("Real: ")
for i := uint16(0); i < 26; i++ {
char, err := eeprom.ReadByte(i)
print(string(char))
if err != nil {
println("There was an error in ReadByte:", i, err)
return
}
}
println("")
println("\n\r\n\rRead 100 bytes from address 26")
println("Expected: ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVZYXWVUTSRQPONMLKJIHGFEDCBA")
print("Real: ")
data := make([]byte, 100)
_, err = eeprom.ReadAt(data, 26)
if err != nil {
println("There was an error in ReadAt:", err)
return
}
for i := 0; i < 100; i++ {
print(string(data[i]))
}
println("")
// Move to the beginning of memory
eeprom.Seek(0, 0)
_, err = eeprom.Write([]uint8{88, 88, 88})
if err != nil {
println("There was an error in Write:", err)
return
}
println("\n\r\n\rRead 3 bytes")
println("Expected: DEF")
print("Real: ")
data = make([]byte, 3)
_, err = eeprom.Read(data)
if err != nil {
println("There was an error in Read:", err)
return
}
for _, char := range data {
print(string(char))
}
println("")
println("\n\r\n\rRead another 3 bytes (from the beginning this time)")
eeprom.Seek(-6, 1)
println("Expected: XXX")
print("Real: ")
data = make([]byte, 3)
_, err = eeprom.Read(data)
if err != nil {
println("There was an error in Read:", err)
return
}
for _, char := range data {
print(string(char))
}
println("")
// Move to the end of memory
eeprom.Seek(-4, 2)
_, err = eeprom.Write([]uint8{89, 90, 89, 90})
if err != nil {
println("There was an error in Write:", err)
return
}
println("\n\r\n\rRead the last 4 bytes of the memory and the 3 of the beginning")
eeprom.Seek(-4, 1)
println("Expected: YZYZXXX")
print("Real: ")
data = make([]byte, 7)
_, err = eeprom.Read(data)
if err != nil {
println("There was an error in Read:", err)
return
}
for _, char := range data {
print(string(char))
}
println("")
}
+35
View File
@@ -0,0 +1,35 @@
package main
import (
"machine"
"strconv"
"time"
"tinygo.org/x/drivers/bme280"
)
func main() {
machine.I2C0.Configure(machine.I2CConfig{})
sensor := bme280.New(machine.I2C0)
sensor.Configure()
connected := sensor.Connected()
if !connected {
println("BME280 not detected")
}
println("BME280 detected")
for {
temp, _ := sensor.ReadTemperature()
println("Temperature:", strconv.FormatFloat(float64(temp)/1000, 'f', 2, 64), "ºC")
press, _ := sensor.ReadPressure()
println("Pressure:", strconv.FormatFloat(float64(press)/100000, 'f', 2, 64), "hPa")
hum, _ := sensor.ReadHumidity()
println("Humidity:", strconv.FormatFloat(float64(hum)/100, 'f', 2, 64), "%")
alt, _ := sensor.ReadAltitude()
println("Altitude:", alt, "m")
time.Sleep(2 * time.Second)
}
}
+40
View File
@@ -0,0 +1,40 @@
package main
import (
"machine"
"time"
"tinygo.org/x/drivers/buzzer"
)
type note struct {
tone float64
duration float64
}
func main() {
speaker := machine.PA30
speaker.Configure(machine.PinConfig{Mode: machine.PinOutput})
speaker.Set(true)
bzrPin := machine.A0
bzrPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
bzr := buzzer.New(bzrPin)
song := []note{
{buzzer.C3, buzzer.Quarter},
{buzzer.D3, buzzer.Quarter},
{buzzer.E3, buzzer.Quarter},
{buzzer.F3, buzzer.Quarter},
{buzzer.G3, buzzer.Quarter},
{buzzer.A3, buzzer.Quarter},
{buzzer.B3, buzzer.Quarter},
{buzzer.C3, buzzer.Quarter},
}
for _, val := range song {
bzr.Tone(val.tone, val.duration)
time.Sleep(10 * time.Millisecond)
}
}
+2 -4
View File
@@ -80,11 +80,8 @@ func main() {
input[i+1] = byte('\n')
adaptor.Write(input[:i+2])
// give the ESP8266 a chance to respond.
time.Sleep(10 * time.Millisecond)
// display response
console.Write(adaptor.Response())
console.Write(adaptor.Response(100))
// prompt
prompt()
@@ -118,6 +115,7 @@ func connectToAP() {
// provide access point
func provideAP() {
time.Sleep(500 * time.Millisecond)
console.Write([]byte("Starting wifi network as access point '"))
console.Write([]byte(ssid))
console.Write([]byte("'...\r\n"))
+17 -23
View File
@@ -11,6 +11,7 @@ import (
"time"
"tinygo.org/x/drivers/espat"
"tinygo.org/x/drivers/espat/net"
)
// change actAsAP to true to act as an access point instead of connecting to one.
@@ -26,8 +27,6 @@ var (
tx = machine.D10
rx = machine.D11
console = machine.UART0
adaptor *espat.Device
)
@@ -44,7 +43,7 @@ func main() {
// first check if connected
if adaptor.Connected() {
console.Write([]byte("Connected to wifi adaptor.\r\n"))
println("Connected to wifi adaptor.")
adaptor.Echo(false)
if actAsAP {
@@ -53,24 +52,22 @@ func main() {
connectToAP()
}
} else {
console.Write([]byte("\r\n"))
console.Write([]byte("Unable to connect to wifi adaptor.\r\n"))
println("Unable to connect to wifi adaptor.")
return
}
// now make UDP connection
laddr := &espat.UDPAddr{Port: 2222}
console.Write([]byte("Loading UDP listener...\r\n"))
conn, _ := adaptor.ListenUDP("UDP", laddr)
laddr := &net.UDPAddr{Port: 2222}
println("Loading UDP listener...")
conn, _ := net.ListenUDP("UDP", laddr)
console.Write([]byte("Waiting for data...\r\n"))
println("Waiting for data...")
data := make([]byte, 50)
blink := true
for {
n, _ := conn.Read(data)
if n > 0 {
console.Write(data[:n])
console.Write([]byte("\r\n"))
println(string(data[:n]))
conn.Write([]byte("hello back\r\n"))
}
blink = !blink
@@ -83,29 +80,26 @@ func main() {
}
// Right now this code is never reached. Need a way to trigger it...
console.Write([]byte("Disconnecting UDP...\r\n"))
println("Disconnecting UDP...")
conn.Close()
console.Write([]byte("Done.\r\n"))
println("Done.")
}
// connect to access point
func connectToAP() {
console.Write([]byte("Connecting to wifi network...\r\n"))
println("Connecting to wifi network...")
adaptor.SetWifiMode(espat.WifiModeClient)
adaptor.ConnectToAP(ssid, pass, 10)
console.Write([]byte("Connected.\r\n"))
console.Write([]byte(adaptor.GetClientIP()))
console.Write([]byte("\r\n"))
println("Connected.")
println(adaptor.GetClientIP())
}
// provide access point
func provideAP() {
console.Write([]byte("Starting wifi network as access point '"))
console.Write([]byte(ssid))
console.Write([]byte("'...\r\n"))
println("Starting wifi network as access point:")
println(ssid)
adaptor.SetWifiMode(espat.WifiModeAP)
adaptor.SetAPConfig(ssid, pass, 7, espat.WifiAPSecurityWPA2_PSK)
console.Write([]byte("Ready.\r\n"))
console.Write([]byte(adaptor.GetAPIP()))
console.Write([]byte("\r\n"))
println("Ready.")
println(adaptor.GetAPIP())
}
+14 -17
View File
@@ -11,6 +11,7 @@ import (
"time"
"tinygo.org/x/drivers/espat"
"tinygo.org/x/drivers/espat/net"
)
// access point info
@@ -26,8 +27,6 @@ var (
tx = machine.D10
rx = machine.D11
console = machine.UART0
adaptor *espat.Device
)
@@ -40,44 +39,42 @@ func main() {
// first check if connected
if adaptor.Connected() {
console.Write([]byte("Connected to wifi adaptor.\r\n"))
println("Connected to wifi adaptor.")
adaptor.Echo(false)
connectToAP()
} else {
console.Write([]byte("\r\n"))
console.Write([]byte("Unable to connect to wifi adaptor.\r\n"))
println("Unable to connect to wifi adaptor.")
return
}
// now make UDP connection
ip := espat.ParseIP(hubIP)
raddr := &espat.UDPAddr{IP: ip, Port: 2222}
laddr := &espat.UDPAddr{Port: 2222}
ip := net.ParseIP(hubIP)
raddr := &net.UDPAddr{IP: ip, Port: 2222}
laddr := &net.UDPAddr{Port: 2222}
console.Write([]byte("Dialing UDP connection...\r\n"))
conn, _ := adaptor.DialUDP("udp", laddr, raddr)
println("Dialing UDP connection...")
conn, _ := net.DialUDP("udp", laddr, raddr)
for {
// send data
console.Write([]byte("Sending data...\r\n"))
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...
console.Write([]byte("Disconnecting UDP...\r\n"))
println("Disconnecting UDP...")
conn.Close()
console.Write([]byte("Done.\r\n"))
println("Done.")
}
// connect to access point
func connectToAP() {
console.Write([]byte("Connecting to wifi network...\r\n"))
println("Connecting to wifi network...")
adaptor.SetWifiMode(espat.WifiModeClient)
adaptor.ConnectToAP(ssid, pass, 10)
console.Write([]byte("Connected.\r\n"))
console.Write([]byte(adaptor.GetClientIP()))
console.Write([]byte("\r\n"))
println("Connected.")
println(adaptor.GetClientIP())
}
+135
View File
@@ -0,0 +1,135 @@
// 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/espat/mqtt"
)
// access point info
const ssid = "YOURSSID"
const pass = "YOURPASS"
// 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 (
uart = machine.UART1
tx = machine.D10
rx = machine.D11
console = machine.UART0
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(adaptor)
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 {
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...")
adaptor.SetWifiMode(espat.WifiModeClient)
adaptor.ConnectToAP(ssid, pass, 10)
println("Connected.")
println(adaptor.GetClientIP())
}
// 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)
}
}
+80
View File
@@ -0,0 +1,80 @@
// 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/espat/net"
)
// access point info
const ssid = "YOURSSID"
const pass = "YOURPASS"
// IP address of the server aka "hub". Replace with your own info.
const serverIP = "0.0.0.0"
// 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 adaptor.Connected() {
println("Connected to wifi adaptor.")
adaptor.Echo(false)
connectToAP()
} else {
println("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, _ := net.DialTCP("tcp", 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 TCP...")
conn.Close()
println("Done.")
}
// connect to access point
func connectToAP() {
println("Connecting to wifi network...")
adaptor.SetWifiMode(espat.WifiModeClient)
adaptor.ConnectToAP(ssid, pass, 10)
println("Connected.")
println(adaptor.GetClientIP())
}
+37
View File
@@ -0,0 +1,37 @@
// Example using the i2s hardware interface on the Adafruit Circuit Playground Express
// to read data from the onboard MEMS microphone.
//
// Uses ideas from the https://github.com/adafruit/Adafruit_CircuitPlayground repo.
//
package main
import (
"machine"
"tinygo.org/x/drivers/microphone"
)
const (
defaultSampleRate = 22000
quantizeSteps = 64
msForSPLSample = 50
defaultSampleCountForSPL = (defaultSampleRate / 1000) * msForSPLSample
)
func main() {
machine.I2S0.Configure(machine.I2SConfig{
Mode: machine.I2SModePDM,
AudioFrequency: defaultSampleRate * quantizeSteps / 16,
ClockSource: machine.I2SClockSourceExternal,
Stereo: true,
})
mic := microphone.New(machine.I2S0)
mic.SampleCountForSPL = defaultSampleCountForSPL
mic.Configure()
for {
spl, maxval := mic.GetSoundPressure()
println("C", spl, "max", maxval)
}
}
+1 -1
View File
@@ -1,4 +1,4 @@
package i2c_128x32
package main
import (
"machine"
+1 -1
View File
@@ -1,4 +1,4 @@
package i2c_128x64
package main
import (
"image/color"
+1 -1
View File
@@ -49,5 +49,5 @@ func sendCommand(gpsDevice GPSDevice, command []byte) (err error) {
}
}
}
return errors.New("No ACK to GPS command")
return errors.New("no ACK to GPS command")
}
+1 -1
View File
@@ -87,7 +87,7 @@ func (g *GPIO) write4BitMode(data byte) {
// Ram address can be changed by writing address in command mode
func (g *GPIO) Read(data []byte) (n int, err error) {
if len(data) == 0 {
return 0, errors.New("Length greater than 0 is required")
return 0, errors.New("length greater than 0 is required")
}
g.rw.High()
g.reconfigureGPIOMode(machine.PinInput)
+1 -1
View File
@@ -66,7 +66,7 @@ func (d *Device) Configure(cfg Config) error {
d.width = uint8(cfg.Width)
d.height = uint8(cfg.Height)
if d.width == 0 || d.height == 0 {
return errors.New("Width and height must be set")
return errors.New("width and height must be set")
}
memoryMap := uint8(ONE_LINE)
if d.height > 1 {
+173
View File
@@ -0,0 +1,173 @@
// Package microphone implements a driver for a PDM microphone.
// For example, the Adafruit PDM MEMS breakout board (https://www.adafruit.com/product/3492)
//
// Datasheet: https://cdn-learn.adafruit.com/assets/assets/000/049/977/original/MP34DT01-M.pdf
//
package microphone // import "tinygo.org/x/drivers/microphone"
import (
"machine"
"math"
)
const (
defaultSampleRate = 22000
quantizeSteps = 64
msForSPLSample = 50
defaultSampleCountForSPL = (defaultSampleRate / 1000) * msForSPLSample
defaultGain = 9.0
defaultRefLevel = 0.00002
)
// Device wraps an I2S connection to a PDM microphone device.
type Device struct {
bus machine.I2S
// data buffer used for SPL sound pressure level samples
data []int32
// buf buffer used for sinc filter
buf []uint32
// SampleCountForSPL is number of samples aka size of data buffer to be used
// for sound pressure level measurement.
// Once Configure() is called, changing this value has no effect.
SampleCountForSPL int
// Gain setting used to calculate sound pressure level
Gain float64
// ReferenceLevel setting used to calculate sound pressure level.
ReferenceLevel float64
}
// New creates a new microphone connection. The I2S bus must already be
// configured.
//
// This function only creates the Device object, it does not touch the device.
func New(bus machine.I2S) Device {
return Device{
bus: bus,
SampleCountForSPL: defaultSampleCountForSPL,
Gain: defaultGain,
ReferenceLevel: defaultRefLevel,
}
}
// Configure the microphone.
func (d *Device) Configure() {
d.data = make([]int32, d.SampleCountForSPL)
d.buf = make([]uint32, (quantizeSteps / 16))
}
// Read the raw microphone data.
func (d *Device) Read(r []int32) (int, error) {
count := len(r)
// get the next group of samples
machine.I2S0.Read(d.buf)
if len(r) > len(d.buf) {
count = len(d.buf)
}
for i := 0; i < count; i++ {
r[i] = int32(d.buf[i])
}
return count, nil
}
// ReadWithFilter reads the microphone and filters the buffer using the sinc filter.
func (d *Device) ReadWithFilter(r []int32) (int, error) {
// read/filter the samples
var sum uint16
for i := 0; i < len(r); i++ {
// get the next group of samples
machine.I2S0.Read(d.buf)
// filter
sum = applySincFilter(d.buf)
// adjust to 10 bit value
s := int32(sum >> 6)
// make it close to 0-offset signed
s -= 512
r[i] = s
}
return len(r), nil
}
// GetSoundPressure returns the sound pressure in milli-decibels.
func (d *Device) GetSoundPressure() (int32, int32) {
// read/filter the samples
d.ReadWithFilter(d.data)
// remove offset
var avg int32
for i := 0; i < len(d.data); i++ {
avg += d.data[i]
}
avg /= int32(len(d.data))
for i := 0; i < len(d.data); i++ {
d.data[i] -= avg
}
// get max value
var maxval int32
for i := 0; i < len(d.data); i++ {
v := d.data[i]
if v < 0 {
v = -v
}
if maxval < v {
maxval = v
}
}
// calculate SPL
spl := float64(maxval) / 1023.0 * d.Gain
spl = 20 * math.Log10(spl/d.ReferenceLevel)
return int32(spl * 1000), maxval
}
// sinc filter for 44 khz with 64 samples
// each value matches the corresponding bit in the 8-bit value
// for that sample.
//
// For more information: https://en.wikipedia.org/wiki/Sinc_filter
//
var sincfilter = [quantizeSteps]uint16{
0, 2, 9, 21, 39, 63, 94, 132,
179, 236, 302, 379, 467, 565, 674, 792,
920, 1055, 1196, 1341, 1487, 1633, 1776, 1913,
2042, 2159, 2263, 2352, 2422, 2474, 2506, 2516,
2506, 2474, 2422, 2352, 2263, 2159, 2042, 1913,
1776, 1633, 1487, 1341, 1196, 1055, 920, 792,
674, 565, 467, 379, 302, 236, 179, 132,
94, 63, 39, 21, 9, 2, 0, 0,
}
// applySincFilter uses the sinc filter to process a single set of sample values.
func applySincFilter(samples []uint32) (result uint16) {
var sample uint16
pos := 0
for j := 0; j < len(samples); j++ {
// takes only the low order 16-bits
sample = uint16(samples[j] & 0xffff)
for i := 0; i < 16; i++ {
if (sample & 0x1) > 0 {
result += sincfilter[pos]
pos++
}
sample >>= 1
}
}
return
}
+33 -10
View File
@@ -39,22 +39,45 @@ func (d Device) Configure() {
}
// ReadAcceleration reads the current acceleration from the device and returns
// it.
func (d Device) ReadAcceleration() (x int16, y int16, z int16) {
// it in µg (micro-gravity). When one of the axes is pointing straight to Earth
// and the sensor is not moving the returned value will be around 1000000 or
// -1000000.
func (d Device) ReadAcceleration() (x int32, y int32, z int32) {
data := make([]byte, 6)
d.bus.ReadRegister(uint8(d.Address), ACCEL_XOUT_H, data)
x = int16((uint16(data[0]) << 8) | uint16(data[1]))
y = int16((uint16(data[2]) << 8) | uint16(data[3]))
z = int16((uint16(data[4]) << 8) | uint16(data[5]))
// Now do two things:
// 1. merge the two values to a 16-bit number (and cast to a 32-bit integer)
// 2. scale the value to bring it in the -1000000..1000000 range.
// This is done with a trick. What we do here is essentially multiply by
// 1000000 and divide by 16384 to get the original scale, but to avoid
// overflow we do it at 1/64 of the value:
// 1000000 / 64 = 15625
// 16384 / 64 = 256
x = int32(int16((uint16(data[0])<<8)|uint16(data[1]))) * 15625 / 256
y = int32(int16((uint16(data[2])<<8)|uint16(data[3]))) * 15625 / 256
z = int32(int16((uint16(data[4])<<8)|uint16(data[5]))) * 15625 / 256
return
}
// ReadRotation reads the current rotation from the device and returns it.
func (d Device) ReadRotation() (x int16, y int16, z int16) {
// ReadRotation reads the current rotation from the device and returns it in
// µ°/s (micro-degrees/sec). This means that if you were to do a complete
// rotation along one axis and while doing so integrate all values over time,
// you would get a value close to 360000000.
func (d Device) ReadRotation() (x int32, y int32, z int32) {
data := make([]byte, 6)
d.bus.ReadRegister(uint8(d.Address), GYRO_XOUT_H, data)
x = int16((uint16(data[0]) << 8) | uint16(data[1]))
y = int16((uint16(data[2]) << 8) | uint16(data[3]))
z = int16((uint16(data[4]) << 8) | uint16(data[5]))
// First the value is converted from a pair of bytes to a signed 16-bit
// value and then to a signed 32-bit value to avoid integer overflow.
// Then the value is scaled to µ°/s (micro-degrees per second).
// This is done in the following steps:
// 1. Multiply by 250 * 1000_000
// 2. Divide by 32768
// The following calculation (x * 15625 / 2048 * 1000) is essentially the
// same but avoids overflow. First both operations are divided by 16 leading
// to multiply by 15625000 and divide by 2048, and then part of the multiply
// is done after the divide instead of before.
x = int32(int16((uint16(data[0])<<8)|uint16(data[1]))) * 15625 / 2048 * 1000
y = int32(int16((uint16(data[2])<<8)|uint16(data[3]))) * 15625 / 2048 * 1000
z = int32(int16((uint16(data[4])<<8)|uint16(data[5]))) * 15625 / 2048 * 1000
return
}
+1 -1
View File
@@ -127,7 +127,7 @@ func (d *Device) GetPixel(x int16, y int16) bool {
func (d *Device) SetBuffer(buffer []byte) error {
if int16(len(buffer)) != d.bufferSize {
//return ErrBuffer
return errors.New("Wrong size buffer")
return errors.New("wrong size buffer")
}
for i := int16(0); i < d.bufferSize; i++ {
d.buffer[i] = buffer[i]
+1 -1
View File
@@ -219,7 +219,7 @@ func (d *Device) GetPixel(x int16, y int16) bool {
func (d *Device) SetBuffer(buffer []byte) error {
if int16(len(buffer)) != d.bufferSize {
//return ErrBuffer
return errors.New("Wrong size buffer")
return errors.New("wrong size buffer")
}
for i := int16(0); i < d.bufferSize; i++ {
d.buffer[i] = buffer[i]
+64 -15
View File
@@ -12,8 +12,10 @@ import (
)
type Config struct {
Width int16
Height int16
Width int16 // Width is the display resolution
Height int16
LogicalWidth int16 // LogicalWidth must be a multiple of 8 and same size or bigger than Width
Rotation Rotation // Rotation is clock-wise
}
type Device struct {
@@ -22,12 +24,16 @@ type Device struct {
dc machine.Pin
rst machine.Pin
busy machine.Pin
logicalWidth int16
width int16
height int16
buffer []uint8
bufferLength uint32
rotation Rotation
}
type Rotation uint8
// Look up table for full updates
var lutFullUpdate = [30]uint8{
0x22, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x11,
@@ -61,17 +67,23 @@ func New(bus machine.SPI, csPin, dcPin, rstPin, busyPin machine.Pin) Device {
// Configure sets up the device.
func (d *Device) Configure(cfg Config) {
if cfg.LogicalWidth != 0 {
d.logicalWidth = cfg.LogicalWidth
} else {
d.logicalWidth = 128
}
if cfg.Width != 0 {
d.width = cfg.Width
} else {
d.width = 128
d.width = 122
}
if cfg.Height != 0 {
d.height = cfg.Height
} else {
d.height = 250
}
d.bufferLength = (uint32(d.width) * uint32(d.height)) / 8
d.rotation = cfg.Rotation
d.bufferLength = (uint32(d.logicalWidth) * uint32(d.height)) / 8
d.buffer = make([]uint8, d.bufferLength)
for i := uint32(0); i < d.bufferLength; i++ {
d.buffer[i] = 0xFF
@@ -158,10 +170,11 @@ func (d *Device) SetLUT(fullUpdate bool) {
// We use RGBA(0,0,0, 255) as white (transparent)
// Anything else as black
func (d *Device) SetPixel(x int16, y int16, c color.RGBA) {
if x < 0 || x >= d.width || y < 0 || y >= d.height {
x, y = d.xy(x, y)
if x < 0 || x >= d.logicalWidth || y < 0 || y >= d.height {
return
}
byteIndex := (x + y*d.width) / 8
byteIndex := (x + y*d.logicalWidth) / 8
if c.R == 0 && c.G == 0 && c.B == 0 { // TRANSPARENT / WHITE
d.buffer[byteIndex] |= 0x80 >> uint8(x%8)
} else { // WHITE / EMPTY
@@ -171,12 +184,12 @@ func (d *Device) SetPixel(x int16, y int16, c color.RGBA) {
// Display sends the buffer to the screen.
func (d *Device) Display() error {
d.setMemoryArea(0, 0, d.width-1, d.height-1)
d.setMemoryArea(0, 0, d.logicalWidth-1, d.height-1)
for j := int16(0); j < d.height; j++ {
d.setMemoryPointer(0, j)
d.SendCommand(WRITE_RAM)
for i := int16(0); i < d.width/8; i++ {
d.SendData(d.buffer[i+j*(d.width/8)])
for i := int16(0); i < d.logicalWidth/8; i++ {
d.SendData(d.buffer[i+j*(d.logicalWidth/8)])
}
}
@@ -188,15 +201,28 @@ func (d *Device) Display() error {
}
// DisplayRect sends only an area of the buffer to the screen.
// The rectangle points need to be a multiple of 8 in the screen.
// They might not work as expected if the screen is rotated.
func (d *Device) DisplayRect(x int16, y int16, width int16, height int16) error {
if x < 0 || y < 0 || x >= d.width || y >= d.height || width < 0 || height < 0 {
x, y = d.xy(x, y)
if x < 0 || y < 0 || x >= d.logicalWidth || y >= d.height || width < 0 || height < 0 {
return errors.New("wrong rectangle")
}
if d.rotation == ROTATION_90 {
width, height = height, width
x -= width
} else if d.rotation == ROTATION_180 {
x -= width - 1
y -= height - 1
} else if d.rotation == ROTATION_270 {
width, height = height, width
y -= height
}
x &= 0xF8
width &= 0xF8
width = x + width // reuse variables
if width >= d.width {
width = d.width
if width >= d.logicalWidth {
width = d.logicalWidth
}
height = y + height
if height > d.height {
@@ -209,7 +235,7 @@ func (d *Device) DisplayRect(x int16, y int16, width int16, height int16) error
d.setMemoryPointer(8*x, y)
d.SendCommand(WRITE_RAM)
for i := int16(x); i < width; i++ {
d.SendData(d.buffer[i+y*d.width/8])
d.SendData(d.buffer[i+y*d.logicalWidth/8])
}
}
@@ -222,7 +248,7 @@ func (d *Device) DisplayRect(x int16, y int16, width int16, height int16) error
// ClearDisplay erases the device SRAM
func (d *Device) ClearDisplay() {
d.setMemoryArea(0, 0, d.width-1, d.height-1)
d.setMemoryArea(0, 0, d.logicalWidth-1, d.height-1)
d.setMemoryPointer(0, 0)
d.SendCommand(WRITE_RAM)
for i := uint32(0); i < d.bufferLength; i++ {
@@ -274,5 +300,28 @@ func (d *Device) ClearBuffer() {
// Size returns the current size of the display.
func (d *Device) Size() (w, h int16) {
return d.width, d.height
if d.rotation == ROTATION_90 || d.rotation == ROTATION_270 {
return d.height, d.logicalWidth
}
return d.logicalWidth, d.height
}
// SetRotation changes the rotation (clock-wise) of the device
func (d *Device) SetRotation(rotation Rotation) {
d.rotation = rotation
}
// xy chages the coordinates according to the rotation
func (d *Device) xy(x, y int16) (int16, int16) {
switch d.rotation {
case NO_ROTATION:
return x, y
case ROTATION_90:
return d.width - y - 1, x
case ROTATION_180:
return d.width - x - 1, d.height - y - 1
case ROTATION_270:
return y, d.height - x - 1
}
return x, y
}
+5
View File
@@ -23,4 +23,9 @@ const (
SET_RAM_X_ADDRESS_COUNTER = 0x4E
SET_RAM_Y_ADDRESS_COUNTER = 0x4F
TERMINATE_FRAME_READ_WRITE = 0xFF
NO_ROTATION Rotation = 0
ROTATION_90 Rotation = 1 // 90 degrees clock-wise rotation
ROTATION_180 Rotation = 2
ROTATION_270 Rotation = 3
)
+7 -4
View File
@@ -17,12 +17,12 @@ func (d Device) WriteByte(c byte) error {
// See:
// https://wp.josh.com/2014/05/13/ws2812-neopixels-are-not-so-finicky-once-you-get-to-know-them/
// T0H: 14-16 cycles or 218.75ns - 250.00ns
// T0H: 17-19 cycles or 265.63ns - 296.88ns
// T0L: 54-56 cycles or 843.75ns - 875.00ns
// +: 68-72 cycles or 1062.50ns - 1125.00ns
// T1H: 36-38 cycles or 562.50ns - 593.75ns
// +: 71-75 cycles or 1109.38ns - 1171.88ns
// T1H: 39-41 cycles or 609.38ns - 640.63ns
// T1L: 30-32 cycles or 468.75ns - 500.0ns
// +: 66-70 cycles or 1031.25ns - 1093.75ns
// +: 69-73 cycles or 1078.13ns - 1140.63ns
// A branch is treated here as 1-3 cycles, because apparently it might get
// speculated. This is more of a guess than hard fact, because the only docs
// by ARM that state this are now considered superseded (by what?).
@@ -41,6 +41,9 @@ func (d Device) WriteByte(c byte) error {
nop
nop
nop
nop
nop
nop
bcs.n skip_store @ [1-3]
str {maskClear}, {portClear} @ [2] T0H -> T0L transition
skip_store: