Bladeren bron

Added sonoff S2x support in IoT Hub

TC pushbot 5 4 jaren geleden
bovenliggende
commit
4f96367b62
4 gewijzigde bestanden met toevoegingen van 218 en 1 verwijderingen
  1. 6 0
      iot.go
  2. 1 1
      mod/iot/hdsv2/hdsv2.go
  3. 174 0
      mod/iot/sonoff_s2x/sonoff_s2x.go
  4. 37 0
      mod/iot/sonoff_s2x/utils.go

+ 6 - 0
iot.go

@@ -6,6 +6,7 @@ import (
 	"imuslab.com/arozos/mod/iot"
 	"imuslab.com/arozos/mod/iot/hds"
 	"imuslab.com/arozos/mod/iot/hdsv2"
+	"imuslab.com/arozos/mod/iot/sonoff_s2x"
 	module "imuslab.com/arozos/mod/modules"
 	prout "imuslab.com/arozos/mod/prouter"
 )
@@ -79,6 +80,7 @@ func IoTHubInit() {
 		adminRouter.HandleFunc("/system/iot/listScanner", iotManager.HandleScannerList)
 
 		//Start of the IoT Management Handlers
+
 		//Home Dynamic v1 (Legacy)
 		hdsHandler := hds.NewProtocolHandler()
 		iotManager.RegisterHandler(hdsHandler)
@@ -87,6 +89,10 @@ func IoTHubInit() {
 		hdsv2Handler := hdsv2.NewProtocolHandler(MDNS)
 		iotManager.RegisterHandler(hdsv2Handler)
 
+		//Tasmota Sonoff S2X
+		tasmotaSonoffS2x := sonoff_s2x.NewProtocolHandler(MDNS)
+		iotManager.RegisterHandler(tasmotaSonoffS2x)
+
 		//Add more here if needed
 
 	}

+ 1 - 1
mod/iot/hdsv2/hdsv2.go

@@ -46,7 +46,7 @@ func (h *Handler) Scan() ([]*iot.Device, error) {
 	hosts := h.scanner.Scan(3, "hds.arozos.com")
 	for _, host := range hosts {
 		thisDevice := iot.Device{
-			Name:         host.HostName,
+			Name:         strings.Title(strings.ReplaceAll(host.HostName, ".local.", "")),
 			Port:         host.Port,
 			Model:        host.Model,
 			Version:      host.BuildVersion + "-" + host.MinorVersion,

+ 174 - 0
mod/iot/sonoff_s2x/sonoff_s2x.go

@@ -0,0 +1,174 @@
+package sonoff_s2x
+
+import (
+	"log"
+	"regexp"
+	"strings"
+
+	"imuslab.com/arozos/mod/iot"
+	"imuslab.com/arozos/mod/network/mdns"
+)
+
+/*
+	Sonoff S2X Module
+
+	This is a module that handles Sonoff Tasmota 6.4.1(sonoff)
+	Core version: 2_4_2/2.2.1(cfd48f3)
+
+	See https://github.com/arendst/Tasmota for source code
+
+	mDNS must be set to enable in order to use this scanner
+*/
+
+type Handler struct {
+	scanner      *mdns.MDNSHost
+	lastScanTime int64
+}
+
+//Create a new Sonoff S2X Protocol Handler
+func NewProtocolHandler(scanner *mdns.MDNSHost) *Handler {
+	//Create a new MDNS Host
+	return &Handler{
+		scanner,
+		0,
+	}
+}
+
+func (h *Handler) Start() error {
+	log.Println("*IoT* Sonoff Tasmoto S2X 6.4 scanner loaded")
+	return nil
+}
+
+func (h *Handler) Scan() ([]*iot.Device, error) {
+	results := []*iot.Device{}
+	scannedDevices := h.scanner.Scan(10, "")
+	for _, dev := range scannedDevices {
+		if dev.Port == 80 {
+			//This things has web UI. Check if it is sonoff by grabbing its index
+			value, err := tryGet("http://" + dev.IPv4[0].String() + "/")
+			if err != nil {
+				//This things is not sonoff smart socket
+				log.Println(dev.HostName + " is not sonoff")
+				continue
+			}
+
+			//Check if the return value contains the keyword:
+			if strings.Contains(value, "Sonoff-Tasmota") {
+				//This is sonoff device!
+				//Extract its MAC Address from Web UI
+				info, err := tryGet("http://" + dev.IPv4[0].String() + "/in")
+				if err != nil {
+					//This things is not sonoff smart socket
+					log.Println(dev.HostName + " failed to extract its MAC address from /in page")
+					continue
+				}
+
+				//Try to seperate the MAC address out
+				//I have no idea what I am doing here
+				re := regexp.MustCompile("[[:alnum:]][[:alnum:]]:[[:alnum:]][[:alnum:]]:[[:alnum:]][[:alnum:]]:[[:alnum:]][[:alnum:]]:[[:alnum:]][[:alnum:]]:[[:alnum:]][[:alnum:]]")
+				match := re.FindStringSubmatch(info)
+				deviceMAC := ""
+				if len(match) > 0 {
+					deviceMAC = match[0]
+				} else {
+					//Can't find MAC address for no reason?
+					continue
+				}
+
+				//Try to get the device status
+				status, err := tryGet("http://" + dev.IPv4[0].String() + "/ay")
+				if err != nil {
+					continue
+				}
+
+				devStatus := map[string]interface{}{}
+				if strings.Contains(status, "ON") {
+					//It is on
+					devStatus["Power"] = "ON"
+				} else {
+					//It is off
+					devStatus["Power"] = "OFF"
+				}
+
+				toggleEndpoint := iot.Endpoint{
+					RelPath: "ay?o=1",
+					Name:    "Toggle Power",
+					Desc:    "Toggle the power of the smart switch",
+					Type:    "none",
+				}
+
+				results = append(results, &iot.Device{
+					Name:         strings.Title(strings.ReplaceAll(dev.HostName, ".local.", "")),
+					Port:         80,
+					Model:        "Sonoff S2X Smart Switch",
+					Version:      "",
+					Manufacturer: "Sonoff",
+					DeviceUUID:   deviceMAC,
+
+					IPAddr:           dev.IPv4[0].String(),
+					RequireAuth:      false,
+					RequireConnect:   false,
+					Status:           devStatus,
+					ControlEndpoints: []*iot.Endpoint{&toggleEndpoint},
+					Handler:          h,
+				})
+			} else {
+				continue
+			}
+		}
+	}
+	return results, nil
+}
+
+func (h *Handler) Connect(device *iot.Device, authInfo *iot.AuthInfo) error {
+	return nil
+}
+
+func (h *Handler) Disconnect(device *iot.Device) error {
+	return nil
+}
+
+func (h *Handler) Status(device *iot.Device) (map[string]interface{}, error) {
+	//Try to get the device status
+	status, err := tryGet("http://" + device.IPAddr + "/ay")
+	if err != nil {
+		return map[string]interface{}{}, err
+	}
+
+	devStatus := map[string]interface{}{}
+	if strings.Contains(status, "ON") {
+		//It is on
+		devStatus["Power"] = "ON"
+	} else {
+		//It is off
+		devStatus["Power"] = "OFF"
+	}
+	return devStatus, nil
+}
+
+func (h *Handler) Icon(device *iot.Device) string {
+	return "switch"
+}
+
+func (h *Handler) Execute(device *iot.Device, endpoint *iot.Endpoint, payload interface{}) (interface{}, error) {
+	results, err := tryGet("http://" + device.IPAddr + "/" + endpoint.RelPath)
+	if err != nil {
+		return nil, err
+	}
+
+	results = strings.ReplaceAll(results, "{t}", "")
+	return results, nil
+}
+
+func (h *Handler) Stats() iot.Stats {
+	return iot.Stats{
+		Name:          "Sonoff Tasmota",
+		Desc:          "Tasmota firmware for Sonoff S2X devices",
+		Version:       "1.0",
+		ProtocolVer:   "1.0",
+		Author:        "tobychui",
+		AuthorWebsite: "http://imuslab.com",
+		AuthorEmail:   "[email protected]",
+		ReleaseDate:   1616944405,
+	}
+}

+ 37 - 0
mod/iot/sonoff_s2x/utils.go

@@ -0,0 +1,37 @@
+package sonoff_s2x
+
+import (
+	"encoding/json"
+	"errors"
+	"io/ioutil"
+	"net/http"
+	"strconv"
+	"time"
+)
+
+func isJSON(s string) bool {
+	var js map[string]interface{}
+	return json.Unmarshal([]byte(s), &js) == nil
+}
+
+func tryGet(url string) (string, error) {
+	client := http.Client{
+		Timeout: 10 * time.Second,
+	}
+
+	resp, err := client.Get(url)
+	if err != nil {
+		return "", err
+	}
+
+	if resp.StatusCode != 200 {
+		return "", errors.New("Server side return status code " + strconv.Itoa(resp.StatusCode))
+	}
+
+	content, err := ioutil.ReadAll(resp.Body)
+	if err != nil {
+		return "", err
+	}
+
+	return string(content), nil
+}