树莓派配置阿里云物联网平台

632 阅读5分钟
  • 业务流程说明: 手机端->业务服务器->阿里云物联网平台->树莓派->本地设备(比如:打印机服务)->树莓派->业务服务器 由此流程,则可以通过手机控制对应的设备进行操作后将得到的数据上传到服务器

  • 需要准备的东西:

  1. 树莓派(3b)

  2. 阿里云账号(开通物联网平台,并开通公共实例)

  3. 业务服务器,可以用本地服务来测试(本文使用go来开启业务服务)

  4. 阿里云物联网平台的文档

    ① 设备接入: help.aliyun.com/document_de…

    ② 云端API: help.aliyun.com/document_de…

  • 平台配置
  1. 先进入公共实例 image.png

  2. 创建产品

image.png

  1. 创建设备

image.png

  1. 添加物模型属性配置

image.png

image.png

image.png

image.png

  1. 在线调试; 图片中几个框框起来的注意一下, 下面那两个属性就是在产品中添加的属性, 这里发现会有两个设置, 如果选择第一个设置, 那么就是将当前所有的属性都发过去, 如果是第二个设置, 那么指挥发送当前的属性过去, 设备收到的数据都是一个key-value的json格式数据, 例如:第一个设置:{"color":"red","size":5}, 第二个设置:{"color":"red"}

image.png

  1. 配置树莓派上的python SDK,这里用的是 aliyun-iot-linkkit ,(注意:树莓派上一定要安装python3.6,因为官方推荐使用这个版本,而我一开始选择了比较高的版本,结果出问题了,最后还是找阿里工程师才解决的.) 这里, 树莓派的环境配置和阿里云的物联网sdk安装以及碰到的一些坑就不展开述说了, 可以看其他的文章.
pip install aliyun-iot-linkkit
  1. python(树莓派)代码展示:
# 后续改成从服务器获取
options = {
    'regionId': 'xxxx',      # https://developer.aliyun.com/article/775321 这里查
    'productKey': "xxxxxx",  # 产品key
    'deviceName': "xxx",     # 设备名称
    'deviceSecret': "xxxxxx", #设备secret
}

lk = linkkit.LinkKit(
    host_name=options['regionId'],
    product_key=options['productKey'],
    device_name=options['deviceName'],
    device_secret=options['deviceSecret'],  # 一机一密
)


# 连接
def on_connect(session_flag, rc, userdata):
    print("on_connect:%d,rc:%d,userdata:" % (session_flag, rc))
    if rc == 0:
        # 连接成功
        print("Connection successful")
    elif rc == 1:
        # 协议版本错误
        print("Protocol version error")
    elif rc == 2:
        # 无效的客户端标识
        print("Invalid client identity")
    elif rc == 3:
        # 服务器无法使用
        print("server unavailable")
    elif rc == 4:
        # 错误的用户名或密码
        print("Wrong user name or password")
    elif rc == 5:
        # 未经授权
        print("unaccredited")
    print("Connect with the result code " + str(rc))


# 断开连接
def on_disconnect(rc, userdata):
    print("on_disconnect:rc:%d,userdata:" % rc)
    if rc != 0:
        print("Unexpected disconnection %s" % rc)


# 接收云端的数据
def on_topic_message(topic, payload, qos, userdata):
    print("on_topic_message,topic=%s;qos=%s;payload=%s;userdata=%s" % (topic, str(payload), str(qos), str(userdata)))
    try:
        s = payload.decode('utf-8', errors='ignore')
        print("收到消息:", s)
        pass
    except Exception as e:
        print('No this moType', e)


# 订阅topic
def on_subscribe_topic(mid, qos, userdata):
    print("on_subscribe_topic mid:%d, granted_qos:%s" %
          (mid, str(','.join('%s' % it for it in qos))))
    print(qos)
    if qos == 128:
        print("订阅失败")


# 取消订阅
def on_unsubscribe_topic(mid, userdata):
    print("on_unsubscribe_topic mid:%d" % mid)
    pass


# 监听发布
def on_publish_topic(mid, userdata):
    print("on_publish_topic mid:%d" % mid)


# mqtt发布启动函数
def mqtt_publish(sensor_data, topic='defult', qos=0):
    try:
        rc, mid = lk.publish_topic(lk.to_full_topic("user/update"), sensor_data)
        print("mqtt_publish:已启动...", "user/update", sensor_data)
        return
    except KeyboardInterrupt:
        print("EXIT")
        # 这是网络循环的阻塞形式,直到客户端调用disconnect()时才会返回。它会自动处理重新连接。
        lk.on_disconnect()
        sys.exit(0)


# 开启物模型
def on_thing_enable(userdata):
    print("on_thing_enable")


# 关闭物模型
def on_thing_disable(userdata):
    print("on_thing_disable")


# 上报属性
def on_thing_prop_post(request_id, code, data, message, userdata):
    print("on_thing_prop_post request id:%s, code:%d, data:%s message:%s" %
          (request_id, code, str(data), message))


# 物模型属性上报回调
def on_thing_prop_changed(params, userdata):
    # 多进程操作,数据并不共享。现实使用直接读取引脚建议。
    print('on_thing_prop_changed', params, userdata)


# 物模型事件回调
def on_thing_event_post(event, request_id, code, data, message, userdata):
    print("on_thing_event_post event:%s,request id:%s, code:%d, data:%s, message:%s" %
          (event, request_id, code, str(data), message))


# 调用物模型服务回调
def on_thing_call_service(identifier, request_id, params, userdata):
    print("on_thing_call_service identifier:%s, request id:%s, params:%s" %
          (identifier, request_id, params))


# 启动函数
def mqtt_run():
    # 物模型路径
    lk.enable_logger(level=logging.DEBUG)
    # 注册接收到云端数据的方法
    lk.on_connect = on_connect
    # 注册取消接收到云端数据的方法
    lk.on_disconnect = on_disconnect
    # 如果产品生产时错误地将一个三元组烧写到了多个设备,多个设备将会被物联网平台认为是同一个设备,
    # 从而出现一个设备上线将另外一个设备的连接断开的情况。用户可以将自己的接口信息上传到云端,那么云端可以通过接口的信息来进行问题定位。
    lk.config_device_info("Eth|03ACDEFF0032|Eth|03ACDEFF0031")
    # 企业实例域名配置的更改
    lk.config_mqtt(endpoint="iot-06z00ftnh9vq6fq.mqtt.iothub.aliyuncs.com")
    # 注册云端订阅的方法
    lk.on_subscribe_topic = on_subscribe_topic
    # 注册当接受到云端发送的数据的时候的方法
    lk.on_topic_message = on_topic_message
    # 注册向云端发布数据的时候顺便所调用的方法
    lk.on_publish_topic = on_publish_topic
    # 注册取消云端订阅的方法
    lk.on_unsubscribe_topic = on_unsubscribe_topic

    # 物模型
    lk.on_thing_enable = on_thing_enable
    lk.on_thing_disable = on_thing_disable
    lk.on_thing_prop_post = on_thing_prop_post
    lk.on_thing_prop_changed = on_thing_prop_changed
    lk.on_thing_event_post = on_thing_event_post
    lk.on_thing_call_service = on_thing_call_service

    # 连接阿里云的函数(异步调用)
    lk.connect_async()
    # 因为他是他是异步调用需要时间所以如果没有这个延时函数的话,他就会出现not in connected state的错误
    time.sleep(2)
    # 订阅这个topic
    rc, mid = lk.subscribe_topic(lk.to_full_topic("user/get"))
    if rc == 0:
        print("subscribe multiple topics success:%r, mid:%r" % (rc, mid))
    else:
        print("subscribe multiple topics fail:%d" % rc)
    pass
    input()


if __name__ == '__main__':
    thread_mqtt_run = threading.Thread(target=mqtt_run)
    thread_mqtt_run.start()
  1. golang 业务服务(这里)
package main

import (
   "encoding/base64"
   "net/http"

   openapi "github.com/alibabacloud-go/darabonba-openapi/client"
   "github.com/alibabacloud-go/iot-20180120/v2/client"
   "github.com/alibabacloud-go/tea/tea"
   "github.com/gin-gonic/gin"
   jsoniter "github.com/json-iterator/go"
)

/**
accessKey,accessSecret 
阿里云颁发给用户的访问服务所用的密钥ID。
登录阿里云控制台,将光标移至账号头像上,然后单击accesskeys,跳转至用户信息管理页,即可创建和查看AccessKey。
endPoint:代码中的endPoint在:实例详情->查看开发配置->云端API->云端 API 调用(点击复制即可)
 */
const (
   accessKey    = "xxxx"
   accessSecret = "xxxxx"
   endPoint     = "iot.cn-shanghai.aliyuncs.com"
)

var iotClient *client.Client

func init() {
   iotClient, _ = CreateClient(tea.String(accessKey), tea.String(accessSecret))
}

func CreateClient(accessKeyId *string, accessKeySecret *string) (_result *client.Client, _err error) {
   config := &openapi.Config{
      AccessKeyId:     accessKeyId,
      AccessKeySecret: accessKeySecret,
   }
   // 访问的域名
   config.Endpoint = tea.String(endPoint)
   _result = &client.Client{}
   _result, _err = client.NewClient(config)
   return _result, _err
}

func Publish(c *gin.Context) {
   json := make(map[string]interface{})
   err := c.BindJSON(&json)
   if err != nil {
      c.Status(http.StatusBadRequest)
      c.Abort()
   }
   data, _ := jsoniter.Marshal(json)
   pubRequest := &client.PubRequest{
      ProductKey:    tea.String("gg2xYGJC3qi"),
      TopicFullName: tea.String("/gg2xYGJC3qi/scanner/user/get"),
      IotInstanceId: tea.String("iot-06z00ftnh9vq6fq"),
      Qos:           tea.Int32(0),
   }
   pubRequest.MessageContent = tea.String(base64.StdEncoding.EncodeToString(data))
   resp, err := iotClient.Pub(pubRequest)
   if err != nil {
      c.Status(http.StatusBadRequest)
   } else {
      c.String(http.StatusOK, resp.GoString())
   }
}

func SetDeviceProperty(c *gin.Context) {
   json := make(map[string]interface{})
   err := c.BindJSON(&json)
   if err != nil {
      c.Status(http.StatusBadRequest)
      c.Abort()
   }
   data, _ := jsoniter.Marshal(json)
   setDevicePropertyRequest := &client.SetDevicePropertyRequest{
      IotInstanceId: tea.String("iot-06z00ftnh9vq6fq"),
      ProductKey:    tea.String("gg2xYGJC3qi"),
      DeviceName:    tea.String("scanner"),
      Items:         tea.String(string(data)),
   }

   resp, err := iotClient.SetDeviceProperty(setDevicePropertyRequest)
   if err != nil {
      c.Status(http.StatusBadRequest)
   } else {
      c.String(http.StatusOK, resp.GoString())
   }
}

func main() {
   gin.SetMode(gin.DebugMode)
   router := gin.Default()
   router.POST("/lulu/publish", Publish)
   router.POST("/lulu/setDeviceProperty", SetDeviceProperty)
   _ = http.ListenAndServe(":8080", router)
}

这里定义了两个接口,一个是消息发布的方式,一个是属性上报的方式.