Files
pico-weather/main.go
T

352 lines
9.4 KiB
Go

package main
import (
"bytes"
"context"
"encoding/binary"
"fmt"
"io"
"machine"
"strconv"
"time"
)
var StateTopic = fmt.Sprintf("homeassistant/sensor/%s/state", NodeID)
// -------------------------------------------------------------
// Minimal MQTT 3.1.1 Client
// -------------------------------------------------------------
type SimpleMQTT struct {
rw io.ReadWriter
}
func NewSimpleMQTT(rw io.ReadWriter) *SimpleMQTT {
return &SimpleMQTT{rw: rw}
}
func (m *SimpleMQTT) Connect(clientID, username, password string) error {
flags := byte(0x02) // Clean session
if username != "" {
flags |= 0x80
}
if password != "" {
flags |= 0x40
}
// Variable header: MQTT (proto name), Level 4 (MQTT 3.1.1), flags, KeepAlive 60s
varHeader := []byte{0x00, 0x04, 'M', 'Q', 'T', 'T', 0x04, flags, 0x00, 0x3C}
payload := encodeString(clientID)
if username != "" {
payload = append(payload, encodeString(username)...)
}
if password != "" {
payload = append(payload, encodeString(password)...)
}
body := append(varHeader, payload...)
packet := append([]byte{0x10}, encodeLength(len(body))...)
packet = append(packet, body...)
if _, err := m.rw.Write(packet); err != nil {
return err
}
resp := make([]byte, 4)
if _, err := io.ReadFull(m.rw, resp); err != nil {
return err
}
if resp[0] != 0x20 || resp[3] != 0x00 {
return fmt.Errorf("MQTT connection refused: code %d", resp[3])
}
return nil
}
func (m *SimpleMQTT) Publish(topic string, payload []byte, retain bool) error {
cmd := byte(0x30)
if retain {
cmd |= 0x01
}
body := append(encodeString(topic), payload...)
packet := append([]byte{cmd}, encodeLength(len(body))...)
packet = append(packet, body...)
_, err := m.rw.Write(packet)
return err
}
func encodeString(s string) []byte {
b := []byte(s)
length := uint16(len(b))
return append([]byte{byte(length >> 8), byte(length & 0xFF)}, b...)
}
func encodeLength(length int) []byte {
var encoded []byte
for {
digit := byte(length % 128)
length /= 128
if length > 0 {
digit |= 0x80
}
encoded = append(encoded, digit)
if length == 0 {
break
}
}
return encoded
}
// -------------------------------------------------------------
// BME280 Driver
// -------------------------------------------------------------
const (
BME280Addr = 0x77
regCalib00 = 0x88
regCalib26 = 0xE1
regReset = 0xE0
regCtrlHum = 0xF2
regCtrlMeas = 0xF4
regConfig = 0xF5
regData = 0xF7
)
type BME280Calib struct {
digT1 uint16
digT2 int16
digT3 int16
digP1 uint16
digP2 int16
digP3 int16
digP4 int16
digP5 int16
digP6 int16
digP7 int16
digP8 int16
digP9 int16
digH1 uint8
digH2 int16
digH3 uint8
digH4 int16
digH5 int16
digH6 int8
}
type BME280 struct {
bus *machine.I2C
addr uint8
calib BME280Calib
}
func NewBME280(bus *machine.I2C, addr uint8) (*BME280, error) {
b := &BME280{bus: bus, addr: addr}
if err := bus.WriteRegister(addr, regReset, []byte{0xB6}); err != nil {
return nil, err
}
time.Sleep(100 * time.Millisecond)
if err := b.readCalibration(); err != nil {
return nil, err
}
if err := bus.WriteRegister(addr, regCtrlHum, []byte{0x01}); err != nil {
return nil, err
}
if err := bus.WriteRegister(addr, regCtrlMeas, []byte{0x27}); err != nil {
return nil, err
}
if err := bus.WriteRegister(addr, regConfig, []byte{0xA0}); err != nil {
return nil, err
}
return b, nil
}
func (b *BME280) readCalibration() error {
var buf24 [24]byte
if err := b.bus.ReadRegister(b.addr, regCalib00, buf24[:]); err != nil {
return err
}
r := bytes.NewReader(buf24[:])
binary.Read(r, binary.LittleEndian, &b.calib.digT1)
binary.Read(r, binary.LittleEndian, &b.calib.digT2)
binary.Read(r, binary.LittleEndian, &b.calib.digT3)
binary.Read(r, binary.LittleEndian, &b.calib.digP1)
binary.Read(r, binary.LittleEndian, &b.calib.digP2)
binary.Read(r, binary.LittleEndian, &b.calib.digP3)
binary.Read(r, binary.LittleEndian, &b.calib.digP4)
binary.Read(r, binary.LittleEndian, &b.calib.digP5)
binary.Read(r, binary.LittleEndian, &b.calib.digP6)
binary.Read(r, binary.LittleEndian, &b.calib.digP7)
binary.Read(r, binary.LittleEndian, &b.calib.digP8)
binary.Read(r, binary.LittleEndian, &b.calib.digP9)
var h1 [1]byte
if err := b.bus.ReadRegister(b.addr, 0xA1, h1[:]); err != nil {
return err
}
b.calib.digH1 = h1[0]
var hBuf [7]byte
if err := b.bus.ReadRegister(b.addr, regCalib26, hBuf[:]); err != nil {
return err
}
b.calib.digH2 = int16(binary.LittleEndian.Uint16(hBuf[0:2]))
b.calib.digH3 = hBuf[2]
b.calib.digH4 = (int16(hBuf[3]) << 4) | (int16(hBuf[4]) & 0x0F)
b.calib.digH5 = (int16(hBuf[5]) << 4) | (int16(hBuf[4]) >> 4)
b.calib.digH6 = int8(hBuf[6])
return nil
}
func (b *BME280) ReadValues() (float64, float64, float64, error) {
var raw [8]byte
if err := b.bus.ReadRegister(b.addr, regData, raw[:]); err != nil {
return 0, 0, 0, err
}
rawP := (int32(raw[0]) << 12) | (int32(raw[1]) << 4) | (int32(raw[2]) >> 4)
rawT := (int32(raw[3]) << 12) | (int32(raw[4]) << 4) | (int32(raw[5]) >> 4)
rawH := (int32(raw[6]) << 8) | int32(raw[7])
var1 := (((rawT >> 3) - (int32(b.calib.digT1) << 1)) * int32(b.calib.digT2)) >> 11
var2 := (((((rawT >> 4) - int32(b.calib.digT1)) * ((rawT >> 4) - int32(b.calib.digT1))) >> 12) * int32(b.calib.digT3)) >> 14
tFine := var1 + var2
temp := float64((tFine*5+128)>>8) / 100.0
pVar1 := int64(tFine) - 128000
pVar2 := pVar1*pVar1*int64(b.calib.digP6) + ((pVar1 * int64(b.calib.digP5)) << 17) + (int64(b.calib.digP4) << 35)
pVar1 = ((pVar1 * pVar1 * int64(b.calib.digP3)) >> 8) + ((pVar1 * int64(b.calib.digP2)) << 12)
pVar1 = (((int64(1) << 47) + pVar1) * int64(b.calib.digP1)) >> 33
var pres float64
if pVar1 != 0 {
p := int64(1048576 - rawP)
p = (((p << 31) - pVar2) * 3125) / pVar1
pVar1 = (int64(b.calib.digP9) * (p >> 13) * (p >> 13)) >> 25
pVar2 = (int64(b.calib.digP8) * p) >> 19
p = ((p + pVar1 + pVar2) >> 8) + (int64(b.calib.digP7) << 4)
pres = (float64(p) / 256.0) / 100.0
}
hVar := tFine - 76800
hVar = (((((rawH << 14) - (int32(b.calib.digH4) << 20) - (int32(b.calib.digH5) * hVar)) + 16384) >> 15) *
(((((((hVar * int32(b.calib.digH6)) >> 10) * (((hVar * int32(b.calib.digH3)) >> 11) + 32768)) >> 10) + 2097152)*
int32(b.calib.digH2) + 8192) >> 14))
hVar = hVar - (((((hVar >> 15) * (hVar >> 15)) >> 7) * int32(b.calib.digH1)) >> 4)
if hVar < 0 {
hVar = 0
} else if hVar > 419430400 {
hVar = 419430400
}
hum := float64(hVar>>12) / 1024.0
return temp, pres, hum, nil
}
// -------------------------------------------------------------
// Home Assistant Auto-Discovery
// -------------------------------------------------------------
type haSensorDef struct {
id string
name string
unit string
class string
valTpl string
}
func registerHADiscovery(mqtt *SimpleMQTT) error {
deviceJSON := fmt.Sprintf(`"device":{"identifiers":["%s"],"name":"%s","model":"Pico 2 W","manufacturer":"Raspberry Pi"}`,
NodeID, NodeName)
sensors := []haSensorDef{
{"temperature", "Temperature", "°C", "temperature", "{{ value_json.temperature }}"},
{"humidity", "Humidity", "%", "humidity", "{{ value_json.humidity }}"},
{"pressure", "Pressure", "hPa", "atmospheric_pressure", "{{ value_json.pressure }}"},
}
for _, s := range sensors {
topic := fmt.Sprintf("homeassistant/sensor/%s/%s/config", NodeID, s.id)
payload := fmt.Sprintf(`{"name":"%s","has_entity_name":true,"unique_id":"%s_%s","device_class":"%s","state_class":"measurement","unit_of_measurement":"%s","state_topic":"%s","value_template":"%s",%s}`,
s.name, NodeID, s.id, s.class, s.unit, StateTopic, s.valTpl, deviceJSON)
if err := mqtt.Publish(topic, []byte(payload), true); err != nil {
return err
}
println("Registered HA discovery:", s.id)
}
return nil
}
// -------------------------------------------------------------
// Panic Handler & Entry Point
// -------------------------------------------------------------
func panicErr(msg string, err error) {
if err != nil {
println("FATAL:", msg, "-", err.Error())
}
for {
time.Sleep(500 * time.Millisecond)
}
}
func main() {
time.Sleep(2 * time.Second)
// Configure I2C (Pin 16 SDA, Pin 17 SCL -> I2C0)
i2c := machine.I2C0
err := i2c.Configure(machine.I2CConfig{
Frequency: 100 * machine.KHz,
SDA: machine.GPIO16,
SCL: machine.GPIO17,
})
if err != nil {
panicErr("I2C configuration", err)
}
bme, err := NewBME280(i2c, BME280Addr)
if err != nil {
panicErr("BME280 init", err)
}
netStack, err := InitNetwork(WifiSSID, WifiPass)
if err != nil {
panicErr("network init", err)
}
portNum, _ := strconv.Atoi(MQTTPort)
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
conn, err := netStack.DialTCP(ctx, MQTTHost, uint16(portNum))
if err != nil {
panicErr("TCP dial", err)
}
mqtt := NewSimpleMQTT(conn)
println("Connecting MQTT session...")
if err := mqtt.Connect(ClientID, MQTTUser, MQTTPass); err != nil {
panicErr("MQTT connect", err)
}
println("MQTT connected.")
if err := registerHADiscovery(mqtt); err != nil {
println("Warning: HA discovery failed:", err.Error())
}
for {
t, p, h, err := bme.ReadValues()
if err != nil {
println("BME280 read error:", err.Error())
} else {
stateJSON := fmt.Sprintf(`{"temperature":%.2f,"humidity":%.2f,"pressure":%.2f}`, t, h, p)
if err := mqtt.Publish(StateTopic, []byte(stateJSON), false); err != nil {
println("MQTT publish error:", err.Error())
} else {
println(fmt.Sprintf("[%s] Published: %s", NodeName, stateJSON))
}
}
time.Sleep(30 * time.Second)
}
}