Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add the supports for Aqara Gateways #5

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Add the supports for Aqara Gateways
  • Loading branch information
niceboy authored and tsunglung committed Apr 12, 2024
commit 6ded4c6abdd8503f14dbd8da95f3e38c13ac86cf
45 changes: 44 additions & 1 deletion internal/app/app.go
Original file line number Diff line number Diff line change
@@ -6,6 +6,7 @@ import (
"github.com/rs/zerolog/log"
"io"
"os"
"os/exec"
"runtime"
"strings"
)
@@ -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
@@ -79,9 +84,17 @@ 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"
ModelG2H = "lumi.camera.gwagl02"
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{}
@@ -97,3 +110,33 @@ func getKey(b []byte, sub string) string {
}
return string(b)
}

func IsAiot() bool {
switch Model {
case ModelM2, ModelM1S, ModelG2H, 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")
}
}
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
}

57 changes: 57 additions & 0 deletions internal/app/serial.go
Original file line number Diff line number Diff line change
@@ -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"`
}
}

@@ -47,6 +50,60 @@ 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) >= 9 {
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 ModelG2H:
counters := readSerial("/proc/tty/driver/ms_uart")
if len(counters) >= 9 {
s.stat.ZigbeeTX = counters[6]
s.stat.ZigbeeRX = counters[7]
s.stat.ZigbeeOE = counters[8]
}
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) >= 12 {
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)
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"
@@ -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
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 {
@@ -35,6 +41,7 @@ func localWorker() {

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

b := make([]byte, 4096)
for {
@@ -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
66 changes: 57 additions & 9 deletions internal/miio/miio.go
Original file line number Diff line number Diff line change
@@ -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 {
@@ -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)
}
@@ -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")

@@ -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)
@@ -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)
@@ -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()
20 changes: 19 additions & 1 deletion internal/miio/report.go
Original file line number Diff line number Diff line change
@@ -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

@@ -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
Loading
Oops, something went wrong.