hdsv2.go 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  1. package hdsv2
  2. import (
  3. "encoding/json"
  4. "errors"
  5. "io/ioutil"
  6. "log"
  7. "net/http"
  8. "net/url"
  9. "strconv"
  10. "strings"
  11. "imuslab.com/arozos/mod/iot"
  12. "imuslab.com/arozos/mod/network/mdns"
  13. )
  14. /*
  15. Home Dynamic 2 Controller
  16. This is a module that handles HDSv2 protocol devices scannings
  17. */
  18. type Handler struct {
  19. scanner *mdns.MDNSHost
  20. lastScanTime int64
  21. }
  22. //Create a new HDSv2 Protocol Handler
  23. func NewProtocolHandler(scanner *mdns.MDNSHost) *Handler {
  24. //Create a new MDNS Host
  25. return &Handler{
  26. scanner,
  27. 0,
  28. }
  29. }
  30. func (h *Handler) Start() error {
  31. log.Println("[IoT] Home Dynamic v2 Loaded")
  32. return nil
  33. }
  34. //Scan the devices within the LAN
  35. func (h *Handler) Scan() ([]*iot.Device, error) {
  36. foundDevices := []*iot.Device{}
  37. hosts := h.scanner.Scan(3, "hds.arozos.com")
  38. for _, host := range hosts {
  39. //Decode the URL and escape characters
  40. decodedURL, err := url.QueryUnescape(host.HostName)
  41. if err != nil {
  42. decodedURL = host.HostName
  43. }
  44. //Filter out the unknown cost of "\ " in the name
  45. decodedURL = strings.ReplaceAll(decodedURL, "\\ ", " ")
  46. //Add device
  47. thisDevice := iot.Device{
  48. Name: strings.Title(strings.ReplaceAll(decodedURL, ".local.", "")),
  49. Port: host.Port,
  50. Model: host.Model,
  51. Version: host.BuildVersion + "-" + host.MinorVersion,
  52. Manufacturer: host.Vendor,
  53. DeviceUUID: host.UUID,
  54. IPAddr: host.IPv4[0].String(),
  55. RequireAuth: false,
  56. RequireConnect: false,
  57. Status: map[string]interface{}{},
  58. Handler: h,
  59. }
  60. //Try to get the device status
  61. status, err := getStatusForDevice(&thisDevice)
  62. if err != nil {
  63. //This might be not a valid HDSv2 device. Skip this
  64. log.Println("*HDSv2* Get status failed for device: ", host.HostName, err.Error())
  65. continue
  66. }
  67. thisDevice.Status = status
  68. //Get the device content endpoints
  69. eps, err := getEndpoints(&thisDevice)
  70. if err != nil {
  71. //This might be not a valid HDSv2 device. Skip this
  72. log.Println("*HDSv2* Get endpoints failed for device: ", host.HostName, err.Error())
  73. continue
  74. }
  75. thisDevice.ControlEndpoints = eps
  76. //Push this host into found device list
  77. foundDevices = append(foundDevices, &thisDevice)
  78. }
  79. return foundDevices, nil
  80. }
  81. //Home Dynamic system's devices no need to established conenction before executing anything
  82. func (h *Handler) Connect(device *iot.Device, authInfo *iot.AuthInfo) error {
  83. return nil
  84. }
  85. //Same rules also apply to disconnect
  86. func (h *Handler) Disconnect(device *iot.Device) error {
  87. return nil
  88. }
  89. //Get the status of the device
  90. func (h *Handler) Status(device *iot.Device) (map[string]interface{}, error) {
  91. return getStatusForDevice(device)
  92. }
  93. //Get the icon filename of the device
  94. func (h *Handler) Icon(device *iot.Device) string {
  95. devModel := device.Model
  96. if devModel == "Switch" {
  97. return "switch"
  98. } else if devModel == "Test Unit" {
  99. return "test"
  100. } else if devModel == "Display" {
  101. return "display"
  102. } else {
  103. return "unknown"
  104. }
  105. }
  106. //Get the status of the device
  107. func (h *Handler) Execute(device *iot.Device, endpoint *iot.Endpoint, payload interface{}) (interface{}, error) {
  108. var result interface{}
  109. targetURL := "http://" + device.IPAddr + ":" + strconv.Itoa(device.Port) + "/" + endpoint.RelPath
  110. //Check if there are payload for this request
  111. if payload == nil {
  112. //No payload. Just call it
  113. } else {
  114. //Payload exists. Append it to the end with value=?
  115. targetURL += "?value=" + url.QueryEscape(payload.(string))
  116. }
  117. result, err := tryGet(targetURL)
  118. if err != nil {
  119. return nil, err
  120. }
  121. return result, nil
  122. }
  123. func (h *Handler) Stats() iot.Stats {
  124. return iot.Stats{
  125. Name: "Home Dynamic v2",
  126. Desc: "A basic IoT communication protocol for ESP8266 made by Makers",
  127. Version: "2.0",
  128. ProtocolVer: "2.0",
  129. Author: "tobychui",
  130. AuthorWebsite: "http://arozos.com",
  131. AuthorEmail: "[email protected]",
  132. ReleaseDate: 1614524498,
  133. }
  134. }
  135. //Get endpoint of the given device object
  136. func getEndpoints(device *iot.Device) ([]*iot.Endpoint, error) {
  137. //Parse the URL of the endpoint apis location (eps)
  138. requestURL := "http://" + device.IPAddr + ":" + strconv.Itoa(device.Port) + "/eps"
  139. resp, err := http.Get(requestURL)
  140. if err != nil {
  141. return nil, err
  142. }
  143. //Get the body content
  144. content, err := ioutil.ReadAll(resp.Body)
  145. if err != nil {
  146. return nil, err
  147. }
  148. //Convert the results to Endpoints
  149. endpoints := []iot.Endpoint{}
  150. err = json.Unmarshal(content, &endpoints)
  151. if err != nil {
  152. return nil, err
  153. }
  154. //Convert the structure to array pointers
  155. results := []*iot.Endpoint{}
  156. for _, ep := range endpoints {
  157. thisEp := ep
  158. results = append(results, &thisEp)
  159. }
  160. return results, nil
  161. }
  162. //Get status given the device object.
  163. func getStatusForDevice(device *iot.Device) (map[string]interface{}, error) {
  164. //Parse the URL for its status api endpoint
  165. requestURL := "http://" + device.IPAddr + ":" + strconv.Itoa(device.Port) + "/status"
  166. resp, err := http.Get(requestURL)
  167. if err != nil {
  168. return map[string]interface{}{}, err
  169. }
  170. //Get the body content
  171. content, err := ioutil.ReadAll(resp.Body)
  172. if err != nil {
  173. return map[string]interface{}{}, err
  174. }
  175. //Check if the resp is json
  176. if !isJSON(strings.TrimSpace(string(content))) {
  177. return map[string]interface{}{}, errors.New("Invalid HDSv2 protocol")
  178. }
  179. //Ok. Parse it
  180. status := map[string]interface{}{}
  181. err = json.Unmarshal(content, &status)
  182. if err != nil {
  183. return map[string]interface{}{}, err
  184. }
  185. return status, nil
  186. }