Compatible board in Arduino: NodeMCU-32S
Pin (GPIO) | Function |
---|---|
14 | Button 3 |
15 | Buzzer |
21 | I2C SDA |
22 | I2C SCL |
26 | Button 1 |
27 | Button 2 |
32 | WS2812 Pin 1 |
34 | ADC In (Battery) |
35 | ADC Illuminance |
8x32 LEDs
The rows are connected with alternating base indices (i.e. row 0 starts at 0 and ends at 31, row 1 starts 63 and ends at 32) - in serpentine fashion, if you like.
Shifting one red pixel through the LED matrix.
You need the NeoPixelBus library by Makuna (installable inside the Arduino IDE).
#include <NeoPixelBus.h>
#define ROWS 8
#define COLS 32
#define NUM_PIXELS (ROWS*COLS)
#define WS2812_PIN 32
NeoPixelBus<NeoGrbFeature, NeoWs2812xMethod> ulanzi(NUM_PIXELS, WS2812_PIN);
RgbColor red(255,0,0);
RgbColor black(0,0,0);
void setup() {
ulanzi.Begin();
pinMode(15, OUTPUT);
digitalWrite(15, 0);
}
void loop() {
int i = 255;
while (true) {
ulanzi.SetPixelColor(i, black);
ulanzi.SetPixelColor((i+1)%256, red);
ulanzi.Show();
i++;
i = i % 256;
}
}
Send GSM-AT commands from a computer via serial console > Raspberry Pi Pico > SIM800L in order to phone home.
SIM800L | Raspberry Pi Pico |
---|---|
RX | UART1_TX / GP8 / Pin 11 |
TX | UART1_RX / GP9 / Pin 12 |
GND | GND / Pin 38 |
VCC | VBUS |
Caution: Voltage is a little above spec this way, but it suffices for tests.
#define S2_TX 8
#define S2_RX 9
UART Serial2(S2_TX,S2_RX,0,0);
void setup() {
Serial.begin(9600);
Serial2.begin(9600);
}
void loop() {
while (Serial2.available() > 0) {
Serial.print(Serial2.readString());
delay(10);
}
while (Serial.available() > 0) {
Serial2.print(Serial.readString());
delay(10);
}
}
Then open a serial console (e.g. inside the Arduino IDE)
Command | Meaning |
---|---|
ATI | Get info about the SIM800L board, make sure it’s connected |
AT+CSQ | Check signal quality, answer: +CSQ: FLOATVAL, everything above 10,0 is OK |
AT+COPS? | Check net login (e.g. D1) |
ATD+49123456789; | Call +49 1234 56789 (mind the semicolon) |
ATH | Hang up |
Building on Minimal three tier Golang app, I made a three tier app with a Go frontend (as Web Assembly), a Go backend and a .txt as exemplary “data store”. Still no external dependencies, all compiling to a single binary. You need Go, Make and a browser. Compile and start with make, then open http://localhost:8000 and hit the button.
$ cat Makefile
all: hellofront hello
hellofront: hellofront.go
GOOS=js GOARCH=wasm go build hellofront.go structures.go
hello:
go run hello.go structures.go
$ cat structures.go
package main
type Answer struct {
Current int
}
$ cat hellofront.go
package main
import (
"encoding/json"
"fmt"
"net/http"
"syscall/js"
)
var current js.Value
func main() {
done := make(chan struct{}, 0)
current = js.Global().Get("document").Call("getElementById", "current")
js.Global().Set("GetCurrent", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
go GetCurrent()
return nil
}))
<-done
}
func GetCurrent() {
resp, err := http.Get("/current")
if err != nil {
fmt.Println(err)
}
var res Answer
err = json.NewDecoder(resp.Body).Decode(&res)
if err != nil {
fmt.Println(err)
}
current.Set("innerText", res.Current)
}
$ cat hello.go
package main
import (
_ "embed"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
)
//go:embed hellofront
var wasm []byte
// copied from $(go env GOROOT)/misc/wasm/wasm_exec.js
//go:embed wasm_exec.js
var wasm_exec string
var html = `<html>
<head>
<script>` + wasm_exec + `</script>
<script>
const go = new Go();
var module = WebAssembly.instantiateStreaming(fetch("hellofront"), go.importObject).then((result) => {
go.run(result.instance);
});
</script>
</head>
<body>
<h1 id="current"></h1>
<button onclick="GetCurrent()">Load Current</button>
</body>
</html>
`
const storefile = "myfancydatastore.txt"
func readFromStore() Answer {
b, err := ioutil.ReadFile(storefile)
if err != nil {
// do something
}
var a Answer
err = json.Unmarshal(b, &a)
if err != nil {
// do something
}
return a
}
func writeToStore(a Answer) {
b, err := json.Marshal(a)
if err != nil {
// do something
}
ioutil.WriteFile(storefile, b, 0644)
}
func serveIndex(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodGet {
w.Header().Set("content-type", "text/html; charset=utf-8")
w.Write([]byte(html))
}
}
func handler(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodGet {
w.Header().Set("content-type", "application/json")
a := readFromStore()
b, err := json.Marshal(a)
if err != nil {
// do something
}
w.Write(b)
a.Current = a.Current + 1
writeToStore(a)
}
}
func loadWASM(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodGet {
w.Write(wasm)
}
}
func main() {
writeToStore(Answer{Current: 0})
mux := http.NewServeMux()
mux.HandleFunc("/", serveIndex)
mux.HandleFunc("/current", handler)
mux.HandleFunc("/hellofront", loadWASM)
fmt.Println("Starting server on localhost:8000")
http.ListenAndServe("localhost:8000", mux)
}
I guess what I was thinking of in Receiver-side Cutoff is just IRC without bouncers. People can’t continue sending when you’re not connected. Seems like a good idea. Also no GDPR/DSGVO problems, because messages aren’t saved.