├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── device ├── controller.go └── controller_test.go ├── examples └── first │ ├── controller.go │ └── controller_test.go ├── go.mod ├── go.sum └── main └── main.go /.gitignore: -------------------------------------------------------------------------------- 1 | build/* 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright 2022 Andreas Geiß 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | APPNAME=$(shell basename `pwd`) 2 | ARCH=$(shell uname -m) 3 | LDFLAGS="-s" 4 | 5 | INO_BAUD="921600" 6 | INO_BOARD="esp32" 7 | INO_HARDWARE_PATH="/home/$(USER)/Arduino/hardware" 8 | INO_IDE_PATH="/opt/arduino" 9 | INO_MANUFACTURER=espressif 10 | INO_PORT="/dev/ttyUSB0" 11 | INO_SDK_PATH="$(INO_HARDWARE_PATH)/$(INO_MANUFACTURER)/$(INO_BOARD)" 12 | INO_SKETCH_FILE=build/device.ino 13 | INO_SKETCH_IMAGE=build/device.img 14 | INO_SOURCE_FILE=device/controller.go 15 | INO_TOOLS_PATH="$(INO_HARDWARE_PATH)/espressif/$(INO_BOARD)/tools" 16 | INO_TOOLS_BUILD="$(INO_TOOLS_PATH)/build.py" 17 | INO_TOOLS_FLASH="$(INO_TOOLS_PATH)/esptool.py" 18 | 19 | all: clean test build 20 | 21 | build/$(APPNAME): 22 | @go build -ldflags $(LDFLAGS) -o build/$(APPNAME)-$(ARCH) main/main.go 23 | @esp32-transpiler -source $(INO_SOURCE_FILE) -target $(INO_SKETCH_FILE) 24 | @$(INO_TOOLS_BUILD) --ide_path=$(INO_IDE_PATH) -d $(INO_HARDWARE_PATH) -b $(INO_BOARD) -w all -o $(INO_SKETCH_IMAGE) $(INO_SKETCH_FILE) 25 | 26 | build: build/$(APPNAME) 27 | 28 | clean: 29 | @rm -f build/* 30 | 31 | flash: 32 | @$(INO_TOOLS_FLASH) --chip $(INO_BOARD) --port $(INO_PORT) --baud $(INO_BAUD) write_flash -fm dio -fs 4MB -ff 40m 0x00010000 $(INO_SKETCH_IMAGE) 33 | 34 | packages: 35 | @go get -u github.com/andygeiss/esp32-controller 36 | @go get -u github.com/andygeiss/esp32-transpiler 37 | @go install -u github.com/andygeiss/esp32-transpiler 38 | @rm -rf $(INO_SDK_PATH) 39 | @mkdir -p $(INO_SDK_PATH) 40 | @git clone --recursive https://github.com/espressif/arduino-esp32.git $(INO_SDK_PATH) 41 | @curl -SSL https://dl.espressif.com/dl/xtensa-esp32-elf-linux64-1.22.0-73-ge28a011-5.2.0.tar.gz > $(INO_SDK_PATH)/tools/xtensa.tar.gz 42 | @tar xzf $(INO_SDK_PATH)/tools/xtensa.tar.gz -C $(INO_SDK_PATH)/tools/ 43 | @rm -f $(INO_SDK_PATH)/tools/xtensa.tar.gz 44 | @git clone --recursive https://github.com/espressif/esp-idf.git $(INO_SDK_PATH)/framework 45 | @mkdir -p $(INO_SDK_PATH)/tools/esptool 46 | @ln -sf $(INO_SDK_PATH)/tools/esptool.py $(INO_SDK_PATH)/tools/esptool/ 47 | 48 | run: 49 | @./build/$(APPNAME)-$(ARCH) 50 | 51 | test: 52 | @go test -v ./... 53 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ESP32 2 | 3 | [![License](https://img.shields.io/github/license/andygeiss/esp32)](https://github.com/andygeiss/esp32/blob/master/LICENSE) 4 | [![Releases](https://img.shields.io/github/v/release/andygeiss/esp32)](https://github.com/andygeiss/esp32/releases) 5 | [![Go Report Card](https://goreportcard.com/badge/github.com/andygeiss/esp32)](https://goreportcard.com/report/github.com/andygeiss/esp32) 6 | [![Maintainability](https://api.codeclimate.com/v1/badges/25eafcae9797d5e8a3de/maintainability)](https://codeclimate.com/github/andygeiss/esp32/maintainability) 7 | 8 | Build your own toolchain to develop, test, build and finally deploy a Golang controller to your ESP32 device. 9 | 10 | ## Purpose 11 | 12 | The [Arduino IDE](https://www.arduino.cc/en/Main/Software) is easy to use. 13 | But I faced problems like maintainability and testability at more complicated IoT projects. 14 | I needed to compile and flash the ESP32 before testing my code functionality by doing it 100% manually. 15 | 16 | This solution transpiles Golang into Arduino code, which can be compiled to an image by using the ESP32 toolchain. 17 | Now I am able to use a fully automated testing approach instead of doing it 100% manually. 18 | 19 | ## Process 20 | 21 | +--------+ +---------+ +----------+ 22 | | Test +----> Build +----> Deploy | 23 | +--------+ +---------+ +----------+ 24 | 25 | make make flash 26 | 27 | **Important**: The Transpiler only supports a small subset of the [Golang Language Specification](https://golang.org/ref/spec). 28 | 29 | ## Installation 30 | 31 | First download and install the latest [Arduino IDE](https://www.arduino.cc/en/Main/Software) into /opt/arduino or change INO_IDE_PATH in the Makefile 32 | and necessary packages. 33 | 34 | - **Ubuntu**: 35 | 36 | sudo apt-get install -y bison flex git gperf libncurses-dev make python 37 | 38 | - **Manjaro**: 39 | 40 | sudo pip install pyserial 41 | 42 | Next run the ESP32 SDK-Installation: 43 | 44 | make packages 45 | 46 | Look at the [examples](https://github.com/andygeiss/esp32/tree/master/examples) for more information. 47 | 48 | ## Develop, Test and Build 49 | 50 | Change the Arduino port to your current settings by changing the Makefile: 51 | 52 | INO_PORT="/dev/ttyUSB0" 53 | 54 | Also set the SSID and PASS strings at application/device/controller.go to your WiFi Access Point and start the build process by using: 55 | 56 | make 57 | 58 | Run the binary at build/device-${ARCH} to simulate your ESP32 device locally. 59 | 60 | Connecting to WiFi ...... Connected! 61 | 62 | ## Deploy 63 | 64 | Finally use the following command to deploy the image device.img to your real ESP32 device. 65 | 66 | make flash 67 | 68 | **Important**: Please ensure that the current user is in the dialout group. Or you will receive a permission denied. 69 | 70 | This will create the following output: 71 | 72 | 2018/08/05 16:04:22 Flashing ... 73 | esptool.py v2.3.1 74 | Connecting.... 75 | Chip is ESP32D0WDQ6 (revision 1) 76 | Features: WiFi, BT, Dual Core 77 | Uploading stub... 78 | Running stub... 79 | Stub running... 80 | Changing baud rate to 921600 81 | Changed. 82 | Configuring flash size... 83 | Compressed 607456 bytes to 366770... 84 | Wrote 607456 bytes (366770 compressed) at 0x00010000 in 5.9 seconds (effective 816.9 kbit/s)... 85 | Hash of data verified. 86 | 87 | Leaving... 88 | Hard resetting via RTS pin... 89 | 2018/08/05 16:04:22 Done. 90 | -------------------------------------------------------------------------------- /device/controller.go: -------------------------------------------------------------------------------- 1 | package device 2 | 3 | import ( 4 | controller "github.com/andygeiss/esp32-controller" 5 | "github.com/andygeiss/esp32-controller/serial" 6 | "github.com/andygeiss/esp32-controller/timer" 7 | wifi "github.com/andygeiss/esp32-controller/wifi" 8 | ) 9 | 10 | // Controller handles the api logic and state of an ESP32. 11 | type Controller struct { 12 | } 13 | 14 | const ssid string = "SSID" 15 | const pass string = "PASSPHRASE" 16 | const host string = "HOSTNAME" 17 | const port int = 3000 18 | const request string = "GET /index.html HTTP/1.0\r\n\r\n" 19 | 20 | var client wifi.Client 21 | 22 | // NewController creates a new controller and returns its address. 23 | func NewController() controller.Controller { 24 | return &Controller{} 25 | } 26 | 27 | // Loop code will be called repeatedly. 28 | func (c *Controller) Loop() error { 29 | serial.Print("Connecting to [") 30 | serial.Print(host) 31 | serial.Print(":") 32 | serial.Print(port) 33 | serial.Print(")] ...") 34 | if client.Connect(host, port) { 35 | serial.Println(" Connected!") 36 | client.Println(request) 37 | } else { 38 | serial.Println(" Failed!") 39 | } 40 | timer.Delay(5000) 41 | return nil 42 | } 43 | 44 | // Setup code will be called once. 45 | func (c *Controller) Setup() error { 46 | serial.Begin(serial.BaudRate115200) 47 | serial.Print("Connecting to WiFi ...") 48 | for wifi.Status() != wifi.StatusConnected { 49 | wifi.BeginEncrypted(ssid, pass) 50 | serial.Print(".") 51 | timer.Delay(1000) 52 | } 53 | serial.Println(" Connected!") 54 | return nil 55 | } 56 | -------------------------------------------------------------------------------- /device/controller_test.go: -------------------------------------------------------------------------------- 1 | package device_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/andygeiss/esp32/device" 7 | "github.com/andygeiss/utils/assert" 8 | ) 9 | 10 | func TestControllerSetupErrorShouldBeNil(t *testing.T) { 11 | ctrl := device.NewController() 12 | err := ctrl.Setup() 13 | assert.That("setup should return without an error", t, err, nil) 14 | } 15 | 16 | func TestControllerLoopErrorShouldBeNil(t *testing.T) { 17 | ctrl := device.NewController() 18 | ctrl.Setup() 19 | err := ctrl.Loop() 20 | assert.That("loop once should return without an error", t, err, nil) 21 | } 22 | -------------------------------------------------------------------------------- /examples/first/controller.go: -------------------------------------------------------------------------------- 1 | package device 2 | 3 | import ( 4 | controller "github.com/andygeiss/esp32-controller" 5 | "github.com/andygeiss/esp32-controller/digital" 6 | "github.com/andygeiss/esp32-controller/serial" 7 | "github.com/andygeiss/esp32-controller/timer" 8 | ) 9 | 10 | // Controller handles the api logic and state of an ESP32. 11 | type Controller struct { 12 | } 13 | 14 | // NewController creates a new controller and returns its address. 15 | func NewController() controller.Controller { 16 | return &Controller{} 17 | } 18 | 19 | // Loop code will be called repeatedly. 20 | func (c *Controller) Loop() error { 21 | timer.Delay(500) 22 | serial.Println(" Write PIN 2 -> HIGH") 23 | digital.Write(2, digital.High) 24 | timer.Delay(500) 25 | digital.Write(2, digital.Low) 26 | serial.Println(" Write PIN 2 -> LOW") 27 | return nil 28 | } 29 | 30 | // Setup code will be called once. 31 | func (c *Controller) Setup() error { 32 | serial.Begin(serial.BaudRate115200) 33 | serial.Println("Setting up PIN 2 -> OUTPUT") 34 | digital.PinMode(2, digital.ModeOutput) 35 | serial.Println("Done.") 36 | return nil 37 | } 38 | -------------------------------------------------------------------------------- /examples/first/controller_test.go: -------------------------------------------------------------------------------- 1 | package device_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/andygeiss/esp32/device" 7 | "github.com/andygeiss/utils/assert" 8 | ) 9 | 10 | func TestControllerSetupErrorShouldBeNil(t *testing.T) { 11 | ctrl := device.NewController() 12 | err := ctrl.Setup() 13 | assert.That("setup should return without an error", t, err, nil) 14 | } 15 | 16 | func TestControllerLoopErrorShouldBeNil(t *testing.T) { 17 | ctrl := device.NewController() 18 | err := ctrl.Setup() 19 | assert.That("setup should return without an error", t, err, nil) 20 | err = ctrl.Loop() 21 | assert.That("loop once should return withoout an error", t, err, nil) 22 | } 23 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/andygeiss/esp32 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/andygeiss/esp32-controller v0.1.0 7 | github.com/andygeiss/utils v0.9.1 8 | ) 9 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/andygeiss/esp32-controller v0.1.0 h1:o+SaEmOA3Sh3Qhxf7xqUpouE9Y39AneYEYrjdP6twAI= 2 | github.com/andygeiss/esp32-controller v0.1.0/go.mod h1:5bEu/i+U9pklziFmp1rJ8ijpz43Gzyz3jmL8+UrI+ys= 3 | github.com/andygeiss/utils v0.9.1 h1:PFFgmdwNDO2/bHA/4F7/fozifH+lPyuMBAhsBuTLGf8= 4 | github.com/andygeiss/utils v0.9.1/go.mod h1:Jatj6UsFNUMgf+9vPeMZ9il72nARlhMvc54r7AiPPiU= 5 | -------------------------------------------------------------------------------- /main/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/andygeiss/esp32/device" 6 | "os" 7 | ) 8 | 9 | func main() { 10 | ctrl := device.NewController() 11 | if err := ctrl.Setup(); err != nil { 12 | fmt.Fprintf(os.Stderr, "Error on Setup: %s", err.Error()) 13 | } 14 | for { 15 | if err := ctrl.Loop(); err != nil { 16 | fmt.Fprintf(os.Stderr, "Error on Loop: %s", err.Error()) 17 | } 18 | } 19 | } 20 | --------------------------------------------------------------------------------