sonoff_s2x.go 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. package sonoff_s2x
  2. import (
  3. "log"
  4. "regexp"
  5. "strings"
  6. "imuslab.com/arozos/mod/iot"
  7. "imuslab.com/arozos/mod/network/mdns"
  8. )
  9. /*
  10. Sonoff S2X Module
  11. This is a module that handles Sonoff Tasmota 6.4.1(sonoff)
  12. Core version: 2_4_2/2.2.1(cfd48f3)
  13. See https://github.com/arendst/Tasmota for source code
  14. mDNS must be set to enable in order to use this scanner
  15. */
  16. type Handler struct {
  17. scanner *mdns.MDNSHost
  18. lastScanTime int64
  19. }
  20. //Create a new Sonoff S2X Protocol Handler
  21. func NewProtocolHandler(scanner *mdns.MDNSHost) *Handler {
  22. //Create a new MDNS Host
  23. return &Handler{
  24. scanner,
  25. 0,
  26. }
  27. }
  28. func (h *Handler) Start() error {
  29. log.Println("[IoT] Sonoff Tasmoto S2X 6.4 scanner loaded")
  30. return nil
  31. }
  32. func (h *Handler) Scan() ([]*iot.Device, error) {
  33. results := []*iot.Device{}
  34. scannedDevices := h.scanner.Scan(10, "")
  35. for _, dev := range scannedDevices {
  36. if dev.Port == 80 {
  37. if len(dev.IPv4) == 0 {
  38. //This device has no return IP???
  39. continue
  40. }
  41. //This things has web UI. Check if it is sonoff by grabbing its index
  42. value, err := tryGet("http://" + dev.IPv4[0].String() + "/")
  43. if err != nil {
  44. //This things is not sonoff smart socket
  45. log.Println(dev.HostName + " is not sonoff")
  46. continue
  47. }
  48. //Check if the return value contains the keyword:
  49. if strings.Contains(value, "Sonoff-Tasmota") {
  50. //This is sonoff device!
  51. //Extract its MAC Address from Web UI
  52. info, err := tryGet("http://" + dev.IPv4[0].String() + "/in")
  53. if err != nil {
  54. //This things is not sonoff smart socket
  55. log.Println(dev.HostName + " failed to extract its MAC address from /in page")
  56. continue
  57. }
  58. //Try to seperate the MAC address out
  59. //I have no idea what I am doing here
  60. re := regexp.MustCompile("[[:alnum:]][[:alnum:]]:[[:alnum:]][[:alnum:]]:[[:alnum:]][[:alnum:]]:[[:alnum:]][[:alnum:]]:[[:alnum:]][[:alnum:]]:[[:alnum:]][[:alnum:]]")
  61. match := re.FindStringSubmatch(info)
  62. deviceMAC := ""
  63. if len(match) > 0 {
  64. deviceMAC = match[0]
  65. } else {
  66. //Can't find MAC address for no reason?
  67. continue
  68. }
  69. //Try to get the device status
  70. status, err := tryGet("http://" + dev.IPv4[0].String() + "/ay")
  71. if err != nil {
  72. continue
  73. }
  74. devStatus := map[string]interface{}{}
  75. if strings.Contains(status, "ON") {
  76. //It is on
  77. devStatus["Power"] = "ON"
  78. } else {
  79. //It is off
  80. devStatus["Power"] = "OFF"
  81. }
  82. toggleEndpoint := iot.Endpoint{
  83. RelPath: "ay?o=1",
  84. Name: "Toggle Power",
  85. Desc: "Toggle the power of the smart switch",
  86. Type: "none",
  87. }
  88. results = append(results, &iot.Device{
  89. Name: strings.Title(strings.ReplaceAll(dev.HostName, ".local.", "")),
  90. Port: 80,
  91. Model: "Sonoff S2X Smart Switch",
  92. Version: "",
  93. Manufacturer: "Sonoff",
  94. DeviceUUID: deviceMAC,
  95. IPAddr: dev.IPv4[0].String(),
  96. RequireAuth: false,
  97. RequireConnect: false,
  98. Status: devStatus,
  99. ControlEndpoints: []*iot.Endpoint{&toggleEndpoint},
  100. Handler: h,
  101. })
  102. } else {
  103. continue
  104. }
  105. }
  106. }
  107. return results, nil
  108. }
  109. func (h *Handler) Connect(device *iot.Device, authInfo *iot.AuthInfo) error {
  110. return nil
  111. }
  112. func (h *Handler) Disconnect(device *iot.Device) error {
  113. return nil
  114. }
  115. func (h *Handler) Status(device *iot.Device) (map[string]interface{}, error) {
  116. //Try to get the device status
  117. status, err := tryGet("http://" + device.IPAddr + "/ay")
  118. if err != nil {
  119. return map[string]interface{}{}, err
  120. }
  121. devStatus := map[string]interface{}{}
  122. if strings.Contains(status, "ON") {
  123. //It is on
  124. devStatus["Power"] = "ON"
  125. } else {
  126. //It is off
  127. devStatus["Power"] = "OFF"
  128. }
  129. return devStatus, nil
  130. }
  131. func (h *Handler) Icon(device *iot.Device) string {
  132. return "switch"
  133. }
  134. func (h *Handler) Execute(device *iot.Device, endpoint *iot.Endpoint, payload interface{}) (interface{}, error) {
  135. results, err := tryGet("http://" + device.IPAddr + "/" + endpoint.RelPath)
  136. if err != nil {
  137. return nil, err
  138. }
  139. results = strings.ReplaceAll(results, "{t}", "")
  140. return results, nil
  141. }
  142. func (h *Handler) Stats() iot.Stats {
  143. return iot.Stats{
  144. Name: "Sonoff Tasmota",
  145. Desc: "Tasmota firmware for Sonoff S2X devices",
  146. Version: "1.0",
  147. ProtocolVer: "1.0",
  148. Author: "tobychui",
  149. AuthorWebsite: "http://imuslab.com",
  150. AuthorEmail: "[email protected]",
  151. ReleaseDate: 1616944405,
  152. }
  153. }