Skip to content

Commit

Permalink
Add the supports for Aqara Gateways
Browse files Browse the repository at this point in the history
  • Loading branch information
niceboy committed Apr 9, 2024
1 parent 7ab00cb commit 089ccb4
Show file tree
Hide file tree
Showing 7 changed files with 207 additions and 17 deletions.
45 changes: 44 additions & 1 deletion internal/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"github.com/rs/zerolog/log"
"io"
"os"
"os/exec"
"runtime"
"strings"
)
Expand Down Expand Up @@ -39,8 +40,12 @@ func Init() {

// get device model and firmware version
if b, err := os.ReadFile("/etc/build.prop"); err == nil {
Firmware = getKey(b, "ro.sys.mi_fw_ver=") + "_" + getKey(b, "ro.sys.mi_build_num=")
Model = getKey(b, "ro.sys.model=")
if IsAiot() {
Firmware = getKey(b, "ro.sys.fw_ver=") + "_" + getKey(b, "ro.sys.build_num=")
} else {
Firmware = getKey(b, "ro.sys.mi_fw_ver=") + "_" + getKey(b, "ro.sys.mi_build_num=")
}
} else if b, err = os.ReadFile("/etc/rootfs_fw_info"); err == nil {
Firmware = getKey(b, "version=")
Model = ModelMGW
Expand Down Expand Up @@ -79,9 +84,16 @@ const (
ModelMGW = "lumi.gateway.mgl03"
ModelE1 = "lumi.gateway.aqcn02"
ModelMGW2 = "lumi.gateway.mcn001"
ModelM1S = "lumi.gateway.acn01"
ModelM1S22 = "lumi.gateway.acn004"
ModelM2 = "lumi.gateway.iragl5"
ModelG2HPro= "lumi.camera.agl001"
ModelG3 = "lumi.camera.gwpagl01"
ModelM2PoE = "lumi.gateway.iragl8"
ModelM3 = "lumi.gateway.acn012"
)

var Cloud string
var Firmware string
var Model string
var Args = map[string]string{}
Expand All @@ -97,3 +109,34 @@ func getKey(b []byte, sub string) string {
}
return string(b)
}

func IsAiot() bool {
switch Model {
case ModelM2, ModelM1S, ModelM1S22, ModelG2HPro, ModelM2PoE, ModelG3, ModelM3:
if Model == ModelM1S || Model == ModelM1S22 {
if Cloud == "" {
if b, err := exec.Command("agetprop", "persist.sys.cloud").Output(); err == nil {
Cloud = strings.TrimRight(string(b), "\n")
log.Info().Msgf("b %s", string(b))
}
}
if Cloud == "miot" {
return false
}
}
Cloud = "aiot"
return true
case ModelE1:
if Cloud == "" {
if b, err := exec.Command("agetprop", "persist.sys.cloud").Output(); err == nil {
Cloud = strings.TrimRight(string(b), "\n")
}
}
if Cloud == "aiot" {
return true
}
}
Cloud = "miot"
return false
}

50 changes: 50 additions & 0 deletions internal/app/serial.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ type SerialStats struct {
ZigbeeRX uint32 `json:"zigbee_rx,omitempty"`
ZigbeeTX uint32 `json:"zigbee_tx,omitempty"`
ZigbeeOE uint32 `json:"zigbee_oe,omitempty"`
IrdaRX uint32 `json:"irda_rx,omitempty"`
IrdaTX uint32 `json:"irda_tx,omitempty"`
IrdaOE uint32 `json:"irda_oe,omitempty"`
}
}

Expand Down Expand Up @@ -47,6 +50,53 @@ func (s *SerialStats) MarshalJSON() ([]byte, error) {
s.stat.ZigbeeRX = counters[4]
s.stat.ZigbeeOE = counters[5]
}
case ModelM1S:
counters := readSerial("/proc/tty/driver/serial")
if len(counters) >= 6 {
s.stat.ZigbeeTX = counters[3]
s.stat.ZigbeeRX = counters[4]
s.stat.ZigbeeOE = counters[5]
}
case ModelM2:
counters := readSerial("/proc/tty/driver/ms_uart")
if len(counters) >= 6 {
s.stat.ZigbeeTX = counters[3]
s.stat.ZigbeeRX = counters[4]
s.stat.ZigbeeOE = counters[5]
s.stat.IrdaTX = counters[6]
s.stat.IrdaRX = counters[7]
s.stat.IrdaOE = counters[8]
}
case ModelM1S22, ModelG2HPro:
counters := readSerial("/proc/tty/driver/ms_uart")
if len(counters) >= 6 {
s.stat.ZigbeeTX = counters[3]
s.stat.ZigbeeRX = counters[4]
s.stat.ZigbeeOE = counters[5]
}
case ModelG3:
counters := readSerial("/proc/tty/driver/ms_uart")
if len(counters) >= 9 {
s.stat.IrdaTX = counters[6]
s.stat.IrdaRX = counters[7]
s.stat.IrdaOE = counters[8]
s.stat.ZigbeeTX = counters[9]
s.stat.ZigbeeRX = counters[10]
s.stat.ZigbeeOE = counters[11]
}
case ModelM2PoE, ModelM3:
counters := readSerial("/proc/tty/driver/ms_uart")
if len(counters) >= 9 {
s.stat.ZigbeeTX = counters[3]
s.stat.ZigbeeRX = counters[4]
s.stat.ZigbeeOE = counters[5]
s.stat.BluetoothTX = counters[6]
s.stat.BluetoothRX = counters[7]
s.stat.BluetoothOE = counters[8]
s.stat.IrdaTX = counters[9]
s.stat.IrdaRX = counters[10]
s.stat.IrdaOE = counters[11]
}
}

return json.Marshal(s.stat)
Expand Down
10 changes: 9 additions & 1 deletion internal/miio/cloud.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package miio

import (
"github.com/AlexxIT/openmiio_agent/internal/app"
"bytes"
"net"
"time"
Expand All @@ -11,11 +12,18 @@ var conn0 net.Conn
func cloudWorker() {
var err error
var n int
var connection string

sep := []byte(`}{`)

for {
conn0, err = net.Dial("tcp", "localhost:54322")
if app.IsAiot() {
connection = "127.0.0.1:21397"
} else {
connection = "localhost:54322"
}

conn0, err = net.Dial("tcp", connection)
if err != nil {
time.Sleep(time.Second * 10)
continue
Expand Down
21 changes: 17 additions & 4 deletions internal/miio/local.go
Original file line number Diff line number Diff line change
@@ -1,20 +1,26 @@
package miio

import (
"github.com/AlexxIT/openmiio_agent/internal/app"
"github.com/AlexxIT/openmiio_agent/pkg/rpc"
"net"
"os"
"os/exec"
"sync"
"time"
"bytes"
)

func localWorker() {
_ = os.Remove("/tmp/miio_agent.socket")
_ = exec.Command("killall", "miio_agent").Run()
if app.IsAiot() {
_ = exec.Command("killall", "ha_agent").Run()
} else {
_ = exec.Command("killall", "miio_agent").Run()

// fix basic_gw (Multimode Gateway) bug with instant reconnection
time.Sleep(time.Millisecond * 500)
// fix basic_gw (Multimode Gateway) bug with instant reconnection
time.Sleep(time.Millisecond * 500)
}

sock, err := net.Listen("unixpacket", "/tmp/miio_agent.socket")
if err != nil {
Expand All @@ -35,6 +41,7 @@ func localWorker() {

func localClientWorker(conn net.Conn) {
var from int
var len int

b := make([]byte, 4096)
for {
Expand All @@ -43,7 +50,13 @@ func localClientWorker(conn net.Conn) {
break
}

msg, err := rpc.NewMessage(b[:n])
index := bytes.IndexByte(b, 0)
if (index > n) {
len = n
} else {
len = index
}
msg, err := rpc.NewMessage(b[:len])
if err != nil {
log.Warn().Err(err).Caller().Send()
continue
Expand Down
66 changes: 57 additions & 9 deletions internal/miio/miio.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,14 @@ const (
AddrGateway = int(32) // basic_app
AddrCentral = int(128) // central_service_lite
AddrCloud = int(0) // miio_client
AddrMQQT = int(-1) // mosquitto
AddrMQTT = int(-1) // mosquitto
AddrAiotAuto = int(256)
AddrRecorder = int(1024)
AddrPPCS = int(2048)
AddrAI = int(4096)
AddrMatter = int(65536)
AddrAiotEvent = int(262144)
AddrAiotLan = int(524288)
)

func appname(addr int) string {
Expand All @@ -38,8 +45,22 @@ func appname(addr int) string {
return "central"
case AddrCloud:
return "cloud"
case AddrMQQT:
case AddrMQTT:
return "mqtt"
case AddrAiotAuto:
return "aiot_automation"
case AddrRecorder:
return "recorder"
case AddrPPCS:
return "ppcs"
case AddrAI:
return "ai"
case AddrMatter:
return "matter"
case AddrAiotEvent:
return "aiot_event"
case AddrAiotLan:
return "aiot_lanbox"
}
return strconv.Itoa(addr)
}
Expand All @@ -53,7 +74,7 @@ func Init() {

mqtt.Subscribe(func(topic string, payload []byte) {
if topic == "miio/command" {
miioRequestRaw(AddrMQQT, payload)
miioRequestRaw(AddrMQTT, payload)
}
}, "miio/command")

Expand All @@ -62,15 +83,17 @@ func Init() {

go rpc.MarksWorker()

go cloudWorker()
if app.Model != app.ModelM3 {
go cloudWorker()
}
go localWorker()
}

func Send(to int, b []byte) {
switch to {
case AddrCloud:
sendToCloud(b)
case AddrMQQT:
case AddrMQTT:
mqtt.Publish("miio/command_ack", b, false)
default:
sendToUnicast(to, b)
Expand Down Expand Up @@ -101,7 +124,26 @@ func miioRequest(from int, msg rpc.Message) {
return // skip basic_gw bug
}

msg.SetInt("_from", from)
if app.IsAiot() {
//msg0, to0 := rpc.FindMessage(msg)
//log.Info().Msgf("1111 %s %d", msg0, to0)
if msg0, to0 := rpc.FindMessage(msg); msg0 != nil {
log.Trace().Msgf("[miio] %s res from=%d to=%d", msg, from, to0)

mqtt.Publish("miio/report_ack", msg, false)
miioResponse(to0, msg0, msg)
return
} else {
log.Trace().Msgf("[miio] %s req from=%d to=%d", msg, from, to)
if from > 0 {
mqtt.Publish("miio/report", msg, false)
}
msg.SetInt("_from", from)
}
} else {
msg.SetInt("_from", from)

}
} else if msg0, to0 := rpc.FindMessage(msg); msg0 != nil {
// 2. Response from any to any (msg with original ID)
log.Trace().Msgf("[miio] %s res from=%d to=%d", msg, from, to0)
Expand Down Expand Up @@ -138,9 +180,15 @@ func miioRequest(from int, msg rpc.Message) {
return
}

if to == AddrCloud {
// swap message ID, so we can catch response on this request
rpc.MarkMessage(msg, from)
if app.IsAiot() {
if to == AddrZigbee || to == AddrAiotAuto || to == AddrAiotLan {
rpc.MarkMessage(msg, from)
}
} else {
if to == AddrCloud {
// swap message ID, so we can catch response on this request
rpc.MarkMessage(msg, from)
}
}

b, err := msg.Marshal()
Expand Down
20 changes: 19 additions & 1 deletion internal/miio/report.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,10 @@ var report struct {
}

var cloudState string
type Params map[string]json.RawMessage

func miioReport(to int, req rpc.Message, res *rpc.Message) bool {
if string(req["method"]) == `"local.query_status"` {
if string(req["method"]) == `"local.query_status"` || string(req["method"]) == `"basis.network"` {
if state := string((*res)["params"]); state != cloudState {
cloudState = state

Expand All @@ -29,6 +30,23 @@ func miioReport(to int, req rpc.Message, res *rpc.Message) bool {
report.CloudState = nil
}
}
if app.IsAiot() {
var params Params
if err := json.Unmarshal(req["params"], &params); err == nil {
if state := string(params["name"]); state != cloudState {
cloudState = state
// params is bytes slice with quotes
report.CloudState = params["name"]

if state == `"network_signal"` {
report.CloudStarts++
report.CloudUptime = app.NewUptime()
} else {
report.CloudState = nil
}
}
}
}
}

return false // because we don't change response
Expand Down
Loading

0 comments on commit 089ccb4

Please sign in to comment.