sonoff_s2x.go 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  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. //This things has web UI. Check if it is sonoff by grabbing its index
  38. value, err := tryGet("http://" + dev.IPv4[0].String() + "/")
  39. if err != nil {
  40. //This things is not sonoff smart socket
  41. log.Println(dev.HostName + " is not sonoff")
  42. continue
  43. }
  44. //Check if the return value contains the keyword:
  45. if strings.Contains(value, "Sonoff-Tasmota") {
  46. //This is sonoff device!
  47. //Extract its MAC Address from Web UI
  48. info, err := tryGet("http://" + dev.IPv4[0].String() + "/in")
  49. if err != nil {
  50. //This things is not sonoff smart socket
  51. log.Println(dev.HostName + " failed to extract its MAC address from /in page")
  52. continue
  53. }
  54. //Try to seperate the MAC address out
  55. //I have no idea what I am doing here
  56. re := regexp.MustCompile("[[:alnum:]][[:alnum:]]:[[:alnum:]][[:alnum:]]:[[:alnum:]][[:alnum:]]:[[:alnum:]][[:alnum:]]:[[:alnum:]][[:alnum:]]:[[:alnum:]][[:alnum:]]")
  57. match := re.FindStringSubmatch(info)
  58. deviceMAC := ""
  59. if len(match) > 0 {
  60. deviceMAC = match[0]
  61. } else {
  62. //Can't find MAC address for no reason?
  63. continue
  64. }
  65. //Try to get the device status
  66. status, err := tryGet("http://" + dev.IPv4[0].String() + "/ay")
  67. if err != nil {
  68. continue
  69. }
  70. devStatus := map[string]interface{}{}
  71. if strings.Contains(status, "ON") {
  72. //It is on
  73. devStatus["Power"] = "ON"
  74. } else {
  75. //It is off
  76. devStatus["Power"] = "OFF"
  77. }
  78. toggleEndpoint := iot.Endpoint{
  79. RelPath: "ay?o=1",
  80. Name: "Toggle Power",
  81. Desc: "Toggle the power of the smart switch",
  82. Type: "none",
  83. }
  84. results = append(results, &iot.Device{
  85. Name: strings.Title(strings.ReplaceAll(dev.HostName, ".local.", "")),
  86. Port: 80,
  87. Model: "Sonoff S2X Smart Switch",
  88. Version: "",
  89. Manufacturer: "Sonoff",
  90. DeviceUUID: deviceMAC,
  91. IPAddr: dev.IPv4[0].String(),
  92. RequireAuth: false,
  93. RequireConnect: false,
  94. Status: devStatus,
  95. ControlEndpoints: []*iot.Endpoint{&toggleEndpoint},
  96. Handler: h,
  97. })
  98. } else {
  99. continue
  100. }
  101. }
  102. }
  103. return results, nil
  104. }
  105. func (h *Handler) Connect(device *iot.Device, authInfo *iot.AuthInfo) error {
  106. return nil
  107. }
  108. func (h *Handler) Disconnect(device *iot.Device) error {
  109. return nil
  110. }
  111. func (h *Handler) Status(device *iot.Device) (map[string]interface{}, error) {
  112. //Try to get the device status
  113. status, err := tryGet("http://" + device.IPAddr + "/ay")
  114. if err != nil {
  115. return map[string]interface{}{}, err
  116. }
  117. devStatus := map[string]interface{}{}
  118. if strings.Contains(status, "ON") {
  119. //It is on
  120. devStatus["Power"] = "ON"
  121. } else {
  122. //It is off
  123. devStatus["Power"] = "OFF"
  124. }
  125. return devStatus, nil
  126. }
  127. func (h *Handler) Icon(device *iot.Device) string {
  128. return "switch"
  129. }
  130. func (h *Handler) Execute(device *iot.Device, endpoint *iot.Endpoint, payload interface{}) (interface{}, error) {
  131. results, err := tryGet("http://" + device.IPAddr + "/" + endpoint.RelPath)
  132. if err != nil {
  133. return nil, err
  134. }
  135. results = strings.ReplaceAll(results, "{t}", "")
  136. return results, nil
  137. }
  138. func (h *Handler) Stats() iot.Stats {
  139. return iot.Stats{
  140. Name: "Sonoff Tasmota",
  141. Desc: "Tasmota firmware for Sonoff S2X devices",
  142. Version: "1.0",
  143. ProtocolVer: "1.0",
  144. Author: "tobychui",
  145. AuthorWebsite: "http://imuslab.com",
  146. AuthorEmail: "[email protected]",
  147. ReleaseDate: 1616944405,
  148. }
  149. }