Skip to content

Instantly share code, notes, and snippets.

@andyleap
Created September 28, 2019 06:21
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save andyleap/2d0ec3c643bb0978642909094182b111 to your computer and use it in GitHub Desktop.
Save andyleap/2d0ec3c643bb0978642909094182b111 to your computer and use it in GitHub Desktop.
package main
import (
"bytes"
"context"
"log"
"os"
"strconv"
"github.com/go-ble/ble"
"github.com/go-ble/ble/darwin"
)
var (
header = []byte{22, 22}
uart = ble.MustParse("ffffd1fd388d938b344a939d1f6efee0")
read = ble.MustParse("ffffd1fd388d938b344a939d1f6efee1")
write = ble.MustParse("ffffd1fd388d938b344a939d1f6efee2")
)
func checksum(msg []byte) []byte {
rolling := int32(0)
checksum := int32(0)
for _, v := range msg {
rolling += int32(v)
checksum += rolling
}
return []byte{byte(checksum >> 8), byte(checksum)}
}
func findBed(adv ble.Advertisement) bool {
if adv.Services() == nil {
return false
}
return ble.Contains(adv.Services(), uart)
}
func main() {
ctx := context.Background()
dev, err := darwin.NewDevice()
if err != nil {
log.Fatal(err)
}
ble.SetDefaultDevice(dev)
timeout, cancel := context.WithCancel(ctx)
var adv ble.Advertisement
err = ble.Scan(timeout, false, func(a ble.Advertisement) {
adv = a
cancel()
}, findBed)
b, err := Connect(dev, adv.Addr())
if err != nil {
log.Fatal(err)
}
b.Send(DeviceQuery())
ret := <-b.msgs
a, c := ProcessDeviceQuery(ret)
switch os.Args[1] {
case "sn":
side := 1
if os.Args[2] == "r" {
side = 0
}
number, err := strconv.Atoi(os.Args[3])
if err != nil {
log.Fatal(err)
}
b.Send(SetPoint(side, number, a, c))
case "p":
side := 1
if os.Args[2] == "r" {
side = 0
}
preset := 0
switch os.Args[3]{
case "favorite":
preset = PRESET_FAVORITE
case "read":
preset = PRESET_READ
case "tv":
preset = PRESET_TV
case "flat":
preset = PRESET_FLAT
case "zerog":
preset = PRESET_ZERO_G
case "snore":
preset = PRESET_SNORE
}
if preset == 0 {
log.Fatal("Invalid position")
}
b.Send(Preset(side, preset, a, c))
}
}
type bed struct {
recvbuf []byte
msgs chan *packet
client ble.Client
readChar, writeChar *ble.Characteristic
}
func Connect(dev ble.Device, addr ble.Addr) (*bed, error) {
client, err := dev.Dial(context.Background(), addr)
if err != nil {
return nil, err
}
services, err := client.DiscoverServices([]ble.UUID{uart})
if err != nil {
log.Fatal(err)
}
service := services[0]
chars, err := client.DiscoverCharacteristics(nil, service)
var readChar, writeChar *ble.Characteristic
for _, char := range chars {
if bytes.Equal(char.UUID, read) {
readChar = char
}
if bytes.Equal(char.UUID, write) {
writeChar = char
}
}
b := &bed{
msgs: make(chan *packet, 2),
client: client,
readChar: readChar,
writeChar: writeChar,
}
err = b.recv()
if err != nil {
return nil, err
}
return b, nil
}
func (b *bed) Send(p packet) error {
return b.client.WriteCharacteristic(b.writeChar, p.encode(), false)
}
func (b *bed) recv() error {
return b.client.Subscribe(b.readChar, false, func(req []byte) {
b.recvbuf = append(b.recvbuf, req...)
for {
p, n := decode(b.recvbuf)
if n == 0 {
break
}
b.recvbuf = b.recvbuf[n:]
if p != nil {
log.Println(p)
b.msgs <- p
}
}
})
}
type packet struct {
a, b, c uint16
d, e, f, g, h uint8
i []uint8
}
func (p packet) encode() []byte {
buf := make([]byte, 10+len(p.i)+4)
copy(buf, header)
buf[2] = p.d
buf[3], buf[4] = byte(p.a>>8), byte(p.a)
buf[5], buf[6] = byte(p.c>>8), byte(p.c)
buf[7] = p.e
buf[8], buf[9] = byte(p.b>>8), byte(p.b)
buf[10] = p.f
buf[11] = p.g&15<<4 | byte(len(p.i)&15)
copy(buf[12:], p.i)
copy(buf[12+len(p.i):], checksum(buf[2:12+len(p.i)]))
return buf
}
func decode(buf []byte) (*packet, int) {
if len(buf) < 14 {
return nil, 0
}
if !(buf[0] == 22 && buf[1] == 22) {
return nil, 1
}
p := &packet{}
p.d = buf[2]
p.a = uint16(buf[3])<<8 + uint16(buf[4])
p.c = uint16(buf[5])<<8 + uint16(buf[6])
p.e = buf[7]
p.b = uint16(buf[8])<<8 + uint16(buf[9])
p.f = buf[10]
leni := int(buf[11] & 15)
p.g = buf[11] >> 4
if len(buf) < 10+leni+4 {
return nil, 0
}
p.i = buf[12 : 12+leni]
sum := checksum(buf[2 : 12+leni])
if sum[0] != buf[12+leni] || sum[1] != buf[13+leni] {
return nil, 1
}
return p, 14 + leni
}
/*
c > d
d > a
e > c
f > e
g > b
h > f
i > g
j > len(i)
a > sets i
*/
func SetPoint(side, level int, a, c uint16) packet {
return packet{
a: a,
b: a,
c: c,
d: 2,
e: 2,
f: 17,
g: uint8(side),
i: []byte{0, byte(level)},
}
}
func DeviceQuery() packet {
return packet{
d: 2,
e: 2,
f: 0,
i: []byte{1, 2, 3, 4, 5, 6, 7, 8},
}
}
func ProcessDeviceQuery(p *packet) (a, c uint16) {
a = uint16(p.i[8])<<8 + uint16(p.i[9])
c = p.a
return
}
var (
PRESET_FAVORITE = 1
PRESET_READ = 2
PRESET_TV = 3
PRESET_FLAT = 4
PRESET_ZERO_G = 5
PRESET_SNORE = 6
)
func Preset(side, preset int, a, c uint16) packet {
return packet{
a: a,
b: a,
c: c,
d: 66,
e: 66,
f: 21,
g: uint8(side),
i: []byte{byte(preset), 0},
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment