Xiaopei's DokuWiki

These are the good times in your life,
so put on a smile and it'll be alright

User Tools

Site Tools


it:iot:smartthings

SmartThings (三星)

开发

smartthings 的架构如下 SmartThings Architecture

  1. hub connectivity: smartthings 封装了 zigbee/z-wave 通信协议
  2. smartthings 有一套自定义的 capabilities1) 来分类设备/服务,device type handler 层会将不同设备的消息统一转为符合 capabilities 的信息,使之后的层能用通用的方法理解、控制设备2)

对 smartthings 做开发可分以下几类:

  1. Creating Device-Type Handlers - 加入新的设备类型
  2. Creating Event-Handler SmartApps - 通过监听事件、发送命令制作不需 UI 的 app
  3. Creating Dashboard Solution Module SmartApps - 开发 Dashboard 型的 app (e.g. Elder Care or Pet Care)
  4. Creating Integration SmartApps - 开发使用 SmartThings 全功能的 app

通过 zigbee/z-wave 连到 hub 的 device-type

Device Type Developer’s Guide — SmartThings Documentation 1.0 documentation

其中,我们最关心的是就是 device-type handler 层的开发。device-type 开发使用 groovy 语言。从底向上,开发分别包括以下工作:

  1. 通过 metadata 定义 fingerprint:hub 在连接到设备后,会根据 fingerprint 分发给 device-type 3)
  2. 通过 metadata 声明自己支持那些 capabilities。每个 capability 隐含此 device 支持若干 Attributes, Events, Commands
  3. 定义 parse method,device 收到消息会调用此方法,方法内可修改自身 attributes、生成 event
  4. You can also use sendEvent() to send events outside of the parse method.
  5. 实例化 (capabilities 中隐含的) commands
  6. metadata 中还包括 simulator metadata 供 IDE 在无物理设备时开发、调试
  7. metadata 中还包括 UX metadata 定义统一 UI 的显示元素 (包括显示布局、每块的显示方式、默认值)

完整例子如下 Device Type Example — SmartThings Documentation 1.0 documentation

/**
 *  CentraLite Switch
 *
 *  Author: SmartThings
 *  Date: 2013-12-02
 */
metadata {
 
  // Define the device type name, namespace, and author.
  definition (name: "CentraLite Switch", namespace: "", author: "SmartThings") {
 
    // These are the capabilities that the device can do.
    capability "Actuator"
    capability "Switch"
    capability "Power Meter"
    capability "Configuration"
    capability "Refresh"
    capability "Sensor"
 
    // This section defines the device fingerprint
    fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0B04,0B05", outClusters: "0019"
 
  }
 
  // simulator metadata
  // This section informs the simulator, for testing of your
  // SmartApp without the actual device. On and off populate a
  // status dropdown. If you choose them, they send the specified
  // message (“on/off: 1”, for example) to the parse method of the
  // Device Type.
  simulator {
    // status messages
    status "on": "on/off: 1"
    status "off": "on/off: 0"
 
    // reply messages
    reply "zcl on-off on": "on/off: 1"
    reply "zcl on-off off": "on/off: 0"
  }
 
  tiles {
    valueTile("temperature", "device.temperature", width: 2, height: 2) {
      state("temperature", label:'${currentValue}°',
          backgroundColors:[
            [value: 31, color: "#153591"],
            [value: 44, color: "#1e9cbb"],
            [value: 59, color: "#90d2a7"],
            [value: 74, color: "#44b621"],
            [value: 84, color: "#f1d801"],
            [value: 95, color: "#d04e00"],
            [value: 96, color: "#bc2323"]
          ]
         )
    }
    standardTile("mode", "device.thermostatMode", inactiveLabel: false, decoration: "flat") {
      state "off", label:'${name}', action:"switchMode"
      state "heat", label:'${name}', action:"switchMode"
      state "emergencryHeat", label:'${name}', action:"switchMode"
      state "cool", label:'${name}', action:"switchMode"
      state "auto", label:'${name}', action:"switchMode"
    }
    standardTile("fanMode", "device.thermostatFanMode", inactiveLabel: false, decoration: "flat") {
      state "fanAuto", label:'${name}', action:"switchFanMode"
      state "fanOn", label:'${name}', action:"switchFanMode"
      state "fanCirculate", label:'${name}', action:"switchFanMode"
    }
    controlTile("heatSliderControl", "device.heatingSetpoint", "slider", height: 1, width: 2, inactiveLabel: false) {
      state "setHeatingSetpoint", action:"thermostat.setHeatingSetpoint", backgroundColor:"#d04e00"
    }
    valueTile("heatingSetpoint", "device.heatingSetpoint", inactiveLabel: false, decoration: "flat") {
      state "heat", label:'${currentValue}° heat', backgroundColor:"#ffffff"
    }
    controlTile("coolSliderControl", "device.coolingSetpoint", "slider", height: 1, width: 2, inactiveLabel: false) {
      state "setCoolingSetpoint", action:"thermostat.setCoolingSetpoint", backgroundColor: "#1e9cbb"
    }
    valueTile("coolingSetpoint", "device.coolingSetpoint", inactiveLabel: false, decoration: "flat") {
      state "cool", label:'${currentValue}° cool', backgroundColor:"#ffffff"
    }
    main "temperature"
    details(["temperature", "mode", "fanMode", "heatSliderControl", "heatingSetpoint", "coolSliderControl", "coolingSetpoint"])
  }
 
}
 
def parse(String description) {
  def value = zigbee.parse(description)?.text
  def name = value && value != "ping" ? "response" : null
  def result = createEvent(name: name, value: value)
  log.debug "Parse returned ${result?.descriptionText}"
  return result
}
 
def on() {
  delayBetween([
    zwave.basicV1.basicSet(value: 0xFF).format(),
    zwave.switchBinaryV1.switchBinaryGet().format()
  ])
}
 
def off() {
  delayBetween([
    zwave.basicV1.basicSet(value: 0x00).format(),
    zwave.switchBinaryV1.switchBinaryGet().format()
  ])
}
 
def poll() {
  zwave.switchBinaryV1.switchBinaryGet().format()
}

Cloud-Connected & LAN-Connected device type

SmartApps

SmartApps 是用 Groovy 写的、运行/安装在 SmartThings 手机端的模块,不是独立的 App。有以下几类:

  1. Event-Handler SmartApps: allow you to subscribe to events from devices and call a handler method upon their firing. e.g. involve you walking through a door and having the lights turn on automatically.
  2. Solution Module SmartApps: exist within the dashboard of the SmartThings app interface, are containers for other SmartApps. e.g. the “Home & Family” section of the dashboard which allows you to see the comings and goings of your family.
  3. Service Manager SmartApps: They are the connecting glue between the unique protocols of your external devices and a device type handler you’d create for those devices. They discover devices and then continue to maintain the connection for those devices. e.g. Sonos Service Manager SmartApp

SmartApps 一般会用到以下方法:

// Installed Method: A method that is called when the app is first installed
def installed() {}
 
// Subscriptions: Allow you to listen for particular events
subscribe(motion1, "motion", motionHandler)
subscribe(motion1, "motion.active", motionActiveHandler)
 
// Updated Method: Called when an already installed app is updated by the user
def updated()
{
  // unsubscribe from the previous device’s events
  unsubscribe()
  // subscribe to the new device’s instead
  subscribe(motion1, "motion", motionHandler)
}
 
// Uninstalled Method: You don’t need to do housekeeping. This method is rarely utilized.
def uninstalled()
{
  doSomething()
}

SmartApps 在 sandboxed environment 中运行,为了安全和效率,SmartApps 只能使用 Groovy 语言的子集 4),CAN'T DOs within your SmartApps:

  1. Define closures outside of methods
  2. Define or modify MetaClasses
  3. Import your own classes; you can only use select SmartThings supported libraries
  4. Use the sleep() method (you can however use a SmartThings method called pause) TODO Link Method
  5. Create and use new threads
  6. Use System level methods, such as System.out

SmartApp Execution

SmartApps exist transiently. They aren’t always running, and therefore SmartApps must be told to run. They can be triggered and executed by:

  1. Event Subscription: An attribute changes on a device, which creates an event, which triggers a subscription, which calls a handler method within your SmartApp.
  2. Scheduled Events: Using a method like runIn(), you call a method within your SmartApp at a particular time .
  3. Endpoint Triggers: Using our web services API, you create an endpoint accessible over the web that calls a method within your SmartApp.

示例 App:Example: Bon Voyage — SmartThings Documentation 1.0 documentation

/**
 *  Bon Voyage
 *
 *  Author: SmartThings
 *  Date: 2013-03-07
 *
 *  Monitors a set of presence detectors and triggers a mode change when everyone has left.
 */
 
// 界面
preferences {
    section("When all of these people leave home") {
        input "people", "capability.presenceSensor", multiple: true
    }
    section("Change to this mode") {
        input "newMode", "mode", title: "Mode?"
    }
    section("False alarm threshold (defaults to 10 min)") {
        input "falseAlarmThreshold", "decimal", title: "Number of minutes", required: false
    }
    section( "Notifications" ) {
        input "sendPushMessage", "enum", title: "Send a push notification?", metadata:[values:["Yes","No"]], required:false
        input "phone", "phone", title: "Send a Text Message?", required: false
    }
}
 
def installed() {
    log.debug "Installed with settings: ${settings}"
    log.debug "Current mode = ${location.mode}, people = ${people.collect{it.label + ': ' + it.currentPresence}}"
    subscribe(people, "presence", presence)
}
 
def updated() {
    log.debug "Updated with settings: ${settings}"
    log.debug "Current mode = ${location.mode}, people = ${people.collect{it.label + ': ' + it.currentPresence}}"
    unsubscribe()
    subscribe(people, "presence", presence)
}
 
def presence(evt)
{
    log.debug "evt.name: $evt.value"
    if (evt.value == "not present") {
        if (location.mode != newMode) {
            log.debug "checking if everyone is away"
            if (everyoneIsAway()) {
                log.debug "starting sequence"
 
                // runIn: runs the method takeAction in a specified amount of time
                runIn(findFalseAlarmThreshold() * 60, "takeAction", [overwrite: false])
            }
        }
        else {
            log.debug "mode is the same, not evaluating"
        }
    }
    else {
        log.debug "present; doing nothing"
    }
}
 
def takeAction()
{
    if (everyoneIsAway()) {
        def threshold = 1000 * 60 * findFalseAlarmThreshold() - 1000
        def awayLongEnough = people.findAll { person ->
            def presenceState = person.currentState("presence")
            def elapsed = now() - presenceState.rawDateCreated.time
            elapsed >= threshold
        }
        log.debug "Found ${awayLongEnough.size()} out of ${people.size()} person(s) who were away long enough"
        if (awayLongEnough.size() == people.size()) {
            //def message = "${app.label} changed your mode to '${newMode}' because everyone left home"
            def message = "SmartThings changed your mode to '${newMode}' because everyone left home"
            log.info message
            send(message)
            setLocationMode(newMode)
        } else {
            log.debug "not everyone has been away long enough; doing nothing"
        }
    } else {
        log.debug "not everyone is away; doing nothing"
    }
}
 
private everyoneIsAway()
{
    def result = true
    for (person in people) {
        if (person.currentPresence == "present") {
            result = false
            break
        }
    }
    log.debug "everyoneIsAway: $result"
    return result
}
 
private send(msg) {
    if ( sendPushMessage != "No" ) {
        log.debug( "sending push message" )
        sendPush( msg )
    }
 
    if ( phone ) {
        log.debug( "sending text message" )
        sendSms( phone, msg )
    }
 
    log.debug msg
}
 
private findFalseAlarmThreshold() {
    (falseAlarmThreshold != null && falseAlarmThreshold != "") ? falseAlarmThreshold : 10
}
it/iot/smartthings.txt · Last modified: 2014/11/07 16:17 by admin