blog

Categories     Timeline     RSS

Ulanzi TC001 Hello World

Compatible board in Arduino: NodeMCU-32S

Pinout

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.

Hello World

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;
  }
}

Bridging to a SIM800L with a Raspberry Pico

Send GSM-AT commands from a computer via serial console > Raspberry Pi Pico > SIM800L in order to phone home.

Pins

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.

Bridge

#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)

Typical interaction

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

Betriebsamkeit

Ganz schön was los

Minimal three tier Golang app v2

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)
}

Receiver-side Cutoff the third

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.

< Older

Newer >