|
@@ -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,
|
|
|
|
+ }
|
|
|
|
+}
|