339 lines
9.0 KiB
Go
339 lines
9.0 KiB
Go
package em3395ty
|
||
|
||
import (
|
||
"crypto/hmac"
|
||
"crypto/sha256"
|
||
"encoding/hex"
|
||
"encoding/json"
|
||
"fmt"
|
||
"io"
|
||
"log"
|
||
"net/http"
|
||
"rain_monitor/models"
|
||
"strconv"
|
||
"strings"
|
||
"time"
|
||
|
||
"github.com/google/uuid"
|
||
)
|
||
|
||
// 涂鸦API配置
|
||
type TuyaConfig struct {
|
||
ClientID string
|
||
Secret string
|
||
BaseURL string
|
||
}
|
||
|
||
var config TuyaConfig
|
||
|
||
// InitConfig 初始化涂鸦API配置
|
||
func InitConfig(clientID, secret, baseURL string) {
|
||
config = TuyaConfig{
|
||
ClientID: clientID,
|
||
Secret: secret,
|
||
BaseURL: baseURL,
|
||
}
|
||
log.Printf("EM3395TY API配置已初始化,ClientID: %s, BaseURL: %s", clientID, baseURL)
|
||
}
|
||
|
||
// 计算HMAC-SHA256签名
|
||
func calculateSignature(secret, stringToSign string) string {
|
||
h := hmac.New(sha256.New, []byte(secret))
|
||
h.Write([]byte(stringToSign))
|
||
return hex.EncodeToString(h.Sum(nil))
|
||
}
|
||
|
||
// GetAccessToken 获取访问令牌
|
||
func GetAccessToken() (string, error) {
|
||
t := strconv.FormatInt(time.Now().UnixNano()/1e6, 10) // 13位时间戳
|
||
nonce := uuid.New().String() // 生成随机UUID
|
||
url := "/v1.0/token?grant_type=1"
|
||
|
||
// 构建stringToSign
|
||
contentSHA256 := "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" // 空body的SHA256值
|
||
stringToSign := fmt.Sprintf("GET\n%s\n\n%s", contentSHA256, url)
|
||
|
||
// 构建待签名字符串
|
||
strToHash := config.ClientID + t + nonce + stringToSign
|
||
|
||
// 使用HMAC-SHA256计算签名
|
||
signature := calculateSignature(config.Secret, strToHash)
|
||
signatureUpper := strings.ToUpper(signature)
|
||
|
||
// 构建请求头
|
||
req, err := http.NewRequest("GET", config.BaseURL+url, nil)
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
|
||
req.Header.Set("client_id", config.ClientID)
|
||
req.Header.Set("sign", signatureUpper)
|
||
req.Header.Set("sign_method", "HMAC-SHA256")
|
||
req.Header.Set("t", t)
|
||
req.Header.Set("nonce", nonce)
|
||
|
||
// 发起GET请求
|
||
client := &http.Client{}
|
||
resp, err := client.Do(req)
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
defer resp.Body.Close()
|
||
|
||
body, err := io.ReadAll(resp.Body)
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
|
||
// 解析响应
|
||
var tokenResp models.EM3395TYTokenResponse
|
||
if err := json.Unmarshal(body, &tokenResp); err != nil {
|
||
return "", err
|
||
}
|
||
|
||
if !tokenResp.Success {
|
||
return "", fmt.Errorf("获取token失败: %s", string(body))
|
||
}
|
||
|
||
return tokenResp.Result.AccessToken, nil
|
||
}
|
||
|
||
// GetDeviceInfo 获取设备信息
|
||
func GetDeviceInfo(accessToken, deviceID string) (*models.EM3395TYDeviceInfo, error) {
|
||
t := strconv.FormatInt(time.Now().UnixNano()/1e6, 10) // 13位时间戳
|
||
nonce := uuid.New().String() // 生成随机UUID
|
||
url := fmt.Sprintf("/v2.0/cloud/thing/%s", deviceID)
|
||
|
||
// 构建stringToSign
|
||
contentSHA256 := "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" // 空body的SHA256值
|
||
stringToSign := fmt.Sprintf("GET\n%s\n\n%s", contentSHA256, url)
|
||
|
||
// 构建待签名字符串
|
||
strToHash := config.ClientID + accessToken + t + nonce + stringToSign
|
||
|
||
// 使用HMAC-SHA256计算签名
|
||
signature := calculateSignature(config.Secret, strToHash)
|
||
signatureUpper := strings.ToUpper(signature)
|
||
|
||
// 构建请求头
|
||
req, err := http.NewRequest("GET", config.BaseURL+url, nil)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
req.Header.Set("client_id", config.ClientID)
|
||
req.Header.Set("access_token", accessToken)
|
||
req.Header.Set("sign", signatureUpper)
|
||
req.Header.Set("sign_method", "HMAC-SHA256")
|
||
req.Header.Set("t", t)
|
||
req.Header.Set("nonce", nonce)
|
||
|
||
// 发起GET请求
|
||
client := &http.Client{}
|
||
resp, err := client.Do(req)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
defer resp.Body.Close()
|
||
|
||
body, err := io.ReadAll(resp.Body)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
// 解析响应
|
||
var deviceInfo models.EM3395TYDeviceInfo
|
||
if err := json.Unmarshal(body, &deviceInfo); err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
return &deviceInfo, nil
|
||
}
|
||
|
||
// GetDeviceStatus 获取设备状态
|
||
func GetDeviceStatus(accessToken, deviceID string) (*models.EM3395TYDeviceStatus, error) {
|
||
t := strconv.FormatInt(time.Now().UnixNano()/1e6, 10) // 13位时间戳
|
||
nonce := uuid.New().String() // 生成随机UUID
|
||
url := fmt.Sprintf("/v1.0/iot-03/devices/%s/status", deviceID)
|
||
|
||
// 构建stringToSign
|
||
contentSHA256 := "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" // 空body的SHA256值
|
||
stringToSign := fmt.Sprintf("GET\n%s\n\n%s", contentSHA256, url)
|
||
|
||
// 构建待签名字符串
|
||
strToHash := config.ClientID + accessToken + t + nonce + stringToSign
|
||
|
||
// 使用HMAC-SHA256计算签名
|
||
signature := calculateSignature(config.Secret, strToHash)
|
||
signatureUpper := strings.ToUpper(signature)
|
||
|
||
// 构建请求头
|
||
req, err := http.NewRequest("GET", config.BaseURL+url, nil)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
req.Header.Set("client_id", config.ClientID)
|
||
req.Header.Set("access_token", accessToken)
|
||
req.Header.Set("sign", signatureUpper)
|
||
req.Header.Set("sign_method", "HMAC-SHA256")
|
||
req.Header.Set("t", t)
|
||
req.Header.Set("nonce", nonce)
|
||
|
||
// 发起GET请求
|
||
client := &http.Client{}
|
||
resp, err := client.Do(req)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
defer resp.Body.Close()
|
||
|
||
body, err := io.ReadAll(resp.Body)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
// 首先解析为临时结构
|
||
var tempResponse struct {
|
||
Result []models.EM3395TYStatusItem `json:"result"`
|
||
Success bool `json:"success"`
|
||
T int64 `json:"t"`
|
||
TID string `json:"tid"`
|
||
}
|
||
|
||
if err := json.Unmarshal(body, &tempResponse); err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
// 创建最终响应
|
||
deviceStatus := &models.EM3395TYDeviceStatus{
|
||
Success: tempResponse.Success,
|
||
T: tempResponse.T,
|
||
TID: tempResponse.TID,
|
||
}
|
||
|
||
// 将临时结构中的数据转换为我们的结构化数据
|
||
statusData := models.EM3395TYStatusData{}
|
||
|
||
// 遍历状态项并填充结构体
|
||
for _, item := range tempResponse.Result {
|
||
switch item.Code {
|
||
case "temp_current":
|
||
if val, ok := item.Value.(float64); ok {
|
||
statusData.TempCurrent = int(val)
|
||
}
|
||
case "humidity_value":
|
||
if val, ok := item.Value.(float64); ok {
|
||
statusData.HumidityValue = int(val)
|
||
}
|
||
case "battery_percentage":
|
||
if val, ok := item.Value.(float64); ok {
|
||
statusData.BatteryPercentage = int(val)
|
||
}
|
||
case "temp_unit_convert":
|
||
if val, ok := item.Value.(string); ok {
|
||
statusData.TempUnitConvert = val
|
||
}
|
||
case "windspeed_unit_convert":
|
||
if val, ok := item.Value.(string); ok {
|
||
statusData.WindspeedUnitConvert = val
|
||
}
|
||
case "pressure_unit_convert":
|
||
if val, ok := item.Value.(string); ok {
|
||
statusData.PressureUnitConvert = val
|
||
}
|
||
case "rain_unit_convert":
|
||
if val, ok := item.Value.(string); ok {
|
||
statusData.RainUnitConvert = val
|
||
}
|
||
case "bright_unit_convert":
|
||
if val, ok := item.Value.(string); ok {
|
||
statusData.BrightUnitConvert = val
|
||
}
|
||
case "temp_current_external":
|
||
if val, ok := item.Value.(float64); ok {
|
||
statusData.TempCurrentExternal = int(val)
|
||
}
|
||
case "humidity_outdoor":
|
||
if val, ok := item.Value.(float64); ok {
|
||
statusData.HumidityOutdoor = int(val)
|
||
}
|
||
case "temp_current_external_1":
|
||
if val, ok := item.Value.(float64); ok {
|
||
statusData.TempCurrentExternal1 = int(val)
|
||
}
|
||
case "humidity_outdoor_1":
|
||
if val, ok := item.Value.(float64); ok {
|
||
statusData.HumidityOutdoor1 = int(val)
|
||
}
|
||
case "temp_current_external_2":
|
||
if val, ok := item.Value.(float64); ok {
|
||
statusData.TempCurrentExternal2 = int(val)
|
||
}
|
||
case "humidity_outdoor_2":
|
||
if val, ok := item.Value.(float64); ok {
|
||
statusData.HumidityOutdoor2 = int(val)
|
||
}
|
||
case "temp_current_external_3":
|
||
if val, ok := item.Value.(float64); ok {
|
||
statusData.TempCurrentExternal3 = int(val)
|
||
}
|
||
case "humidity_outdoor_3":
|
||
if val, ok := item.Value.(float64); ok {
|
||
statusData.HumidityOutdoor3 = int(val)
|
||
}
|
||
case "atmospheric_pressture":
|
||
if val, ok := item.Value.(float64); ok {
|
||
statusData.AtmosphericPressure = int(val)
|
||
}
|
||
case "pressure_drop":
|
||
if val, ok := item.Value.(float64); ok {
|
||
statusData.PressureDrop = int(val)
|
||
}
|
||
case "windspeed_avg":
|
||
if val, ok := item.Value.(float64); ok {
|
||
statusData.WindspeedAvg = int(val)
|
||
}
|
||
case "windspeed_gust":
|
||
if val, ok := item.Value.(float64); ok {
|
||
statusData.WindspeedGust = int(val)
|
||
}
|
||
case "rain_1h":
|
||
if val, ok := item.Value.(float64); ok {
|
||
statusData.Rain1h = int(val)
|
||
}
|
||
case "rain_24h":
|
||
if val, ok := item.Value.(float64); ok {
|
||
statusData.Rain24h = int(val)
|
||
}
|
||
case "rain_rate":
|
||
if val, ok := item.Value.(float64); ok {
|
||
statusData.RainRate = int(val)
|
||
}
|
||
case "uv_index":
|
||
if val, ok := item.Value.(float64); ok {
|
||
statusData.UVIndex = int(val)
|
||
}
|
||
case "dew_point_temp":
|
||
if val, ok := item.Value.(float64); ok {
|
||
statusData.DewPointTemp = int(val)
|
||
}
|
||
case "feellike_temp":
|
||
if val, ok := item.Value.(float64); ok {
|
||
statusData.FeellikeTemp = int(val)
|
||
}
|
||
case "heat_index":
|
||
if val, ok := item.Value.(float64); ok {
|
||
statusData.HeatIndex = int(val)
|
||
}
|
||
case "windchill_index":
|
||
if val, ok := item.Value.(float64); ok {
|
||
statusData.WindchillIndex = int(val)
|
||
}
|
||
}
|
||
}
|
||
|
||
deviceStatus.Result = statusData
|
||
return deviceStatus, nil
|
||
}
|