Last Updated on : 2024-06-26 07:15:52download
This topic describes how to implement scene creation, including the conditions and actions.
Process
Get the supported condition types by querying the list of conditions and display them to users as needed.
ThingHomeSdk.getSceneServiceInstance().conditionService().getConditionAll(
homeId, // The home ID
true, "", object : IResultCallback<ConditionItemList?>() {
override fun onSuccess(result: ConditionItemList?) {
// On top of the result, you can add local custom condition types.
}
override fun onError(errorCode: String?, errorMessage: String?) {
}
})
After users select a condition type, present the UI accordingly.
When users complete the condition set, assemble the condition model of the scene using the builder provided by creating scene conditions.
An open source simplified example project is available to help you understand the above process.
The example creates a geofence condition. It is recommended to use the geofencing component from Google or Huawei to monitor geofences and choose a suitable map for creating geofences. Alternatively, use Tuya’s geofence monitoring component. The following section will describe how to integrate with this component.
Specifically, get the radius
, latitude
, longitude
, and address
from the geofence drawing page. With these parameters, after users select geofenceType
, create a condition of when the location changes.
val radius = 100
val latitude: Double = 30.30288959184809
val longitude: Double = 120.0640840491766
val address = "XX"
val geofenceType = GeofencingType.GEOFENCING_TYPE_ENTER.type
val geofenceConditionBuilder = GeofenceConditionBuilder(radius, latitude, longitude, address, geofenceType)
val conditionBase = geofenceConditionBuilder.build() as ConditionBase
val geoCondition = SceneCondition(conditionBase).apply {
this.entityName = address
}
The geofencing components based on Google and Huawei have been integrated into the BizBundle SDK. You cannot use both of these components simultaneously. The geofencing based on Huawei takes precedence over the one based on Google.
Follow the HMS Core documentation Configuring App Information in AppGallery Connect, Integrating the SDK, Configuring Obfuscation Scripts, and Optimizing the App Package Size to complete the preparations.
Integrate the component.
//If you have already included the following dependencies in the preparation steps, you can ignore them, but the version specified here prevails.
implementation 'com.huawei.hms:maps:6.0.0.301'
implementation 'com.huawei.hms:location:6.0.0.302'
implementation "com.thingclips.smart:thingsmart:${sdk_version}"
// BizBundle SDK components
implementation "com.thingclips.smart:thingsmart-expansion-sdk:${expansion_version}"
// If you already include the following dependencies, ignore them.
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'com.alibaba:fastjson:1.1.67.android'
Write code.
Use the component.
The section When Location Changes describes how to use the API. For the restrictions, see Geofence Restrictions and Devices Supporting Geofence.
Integrate the component.
implementation 'com.google.android.gms:play-services-maps:17.0.0'
implementation 'com.google.android.gms:play-services-location:17.0.0'
implementation "com.thingclips.smart:thingsmart:${sdk_version}"
// BizBundle SDK components
implementation("com.thingclips.smart:thingsmart-expansion-sdk:${expansion_version}"){
// Note: Exclude the Huawei-based geofencing component from the global dependencies to use the Google-based geofencing component.
exclude group: "com.thingclips.smart", module: "thingsmart-geofence-huawei"
}
// If you already include the following dependencies, ignore them.
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'com.alibaba:fastjson:1.1.67.android'
Configure the project.
Exclude the Huawei-based geofencing component from the global dependencies in the build.gradle
.
android{
configurations.all{
exclude group: "com.thingclips.smart", module: "thingsmart-geofence-huawei"
}
}
Configure the AndroidManifest.xml
file.
<!-- Replace ${GOOGLE_MAP_KEY} with your own Google Map key -->
<meta-data
android:name="com.google.android.geo.API_KEY"
android:value="${GOOGLE_MAP_KEY}" />
Write code.
It is recommended to declare the location permissions in the manifest file. After users select the condition of when location changes, request the necessary permissions accordingly. For more information, see Request Location Permissions. If the app does not have location permissions, it may result in an API error.
Use the component.
The section When Location Changes describes how to use the API. For more information, see Create and Monitor Geofences and Use Best Practices for Geofencing.
The example creates a schedule condition. It is recommended to provide a date and time picker
component for users to select a time.
Get the time
and date
from the picker
component, and the timeZoneId
from either Calendar.getInstance().timeZone.id
or TimeZone.getDefault().id
. After users select the recurring type loops
, create a schedule condition with the obtained parameters.
val conditionBase: ConditionBase = TimingConditionBuilder(
timeZoneId,
loops,
time,
date,
).build() as ConditionBase
val timerCondition = SceneCondition(conditionBase).apply {
entityName = "Schedule"
exprDisplay = time
}
The example creates a condition of when weather changes at a specific location. It is recommended to display the full envConds
returned by querying the list of conditions. After users select a weather variable, provide them with a UI to input the desired value.
With the longitude and latitude of the location, you can get the value of cityId
and city
by calling the method of querying city information by longitude and latitude. Alternatively, query the list of cities and instruct users to select a city to get the city ID.
Temperature
val weatherConditionBuilder = WeatherConditionBuilder(
cityId = cityId,
cityName = city,
entityType = 3,
weatherType = WeatherType.WEATHER_TYPE_TEMP,
operator = "<", // User setting
chooseValue = chooseValue, // User setting
)
val conditionBase = weatherConditionBuilder.build() as ConditionBase
val weatherCondition = SceneCondition(conditionBase).apply {
entityName = city
iconUrl = weatherData.icon // The icon of the selected weather type. This field takes the value of newIcon for weather type data in the condition list.
exprDisplay = exprDis // Visual representation of the conditional expressions.
}
Wind speed
val weatherConditionBuilder = WeatherConditionBuilder(
cityId = cityId,
cityName = city,
entityType = 3,
weatherType = WeatherType.WEATHER_TYPE_WIND,
operator = "<", // User setting
chooseValue = chooseValue, // User setting
).setWindSpeedUnit(unit) // The unit of wind speed. This field takes the data of wind speed in the condition list, specifically property.property.unit.
val conditionBase = weatherConditionBuilder.build() as ConditionBase
val weatherCondition = SceneCondition(conditionBase).apply {
entityName = city
iconUrl = weatherData.icon // The icon of the selected weather type. This field takes the value of newIcon for wind speed in the condition list.
exprDisplay = exprDis // Visual representation of the conditional expressions.
}
Humidity
val weatherConditionBuilder = WeatherConditionBuilder(
cityId = cityId,
cityName = city,
entityType = 3,
weatherType = WeatherType.WEATHER_TYPE_HUMIDITY,
operator = null,
chooseValue = chooseValue, // User selection
)
val conditionBase = weatherConditionBuilder.build() as ConditionBase
val weatherCondition = SceneCondition(conditionBase).apply {
entityName = city
iconUrl = weatherData.icon // The icon of the selected weather type. This field takes the value of newIcon for humidity in the condition list.
exprDisplay = exprDis // Visual representation of the conditional expressions.
}
Weather
val weatherConditionBuilder = WeatherConditionBuilder(
cityId = cityId,
cityName = city,
entityType = 3,
weatherType = WeatherType.WEATHER_TYPE_CONDITION,
operator = null,
chooseValue = chooseValue, // User selection
)
val conditionBase = weatherConditionBuilder.build() as ConditionBase
val weatherCondition = SceneCondition(conditionBase).apply {
entityName = city
iconUrl = weatherData.icon // The icon of the selected weather type. This field takes the value of newIcon for weather conditions in the condition list.
exprDisplay = exprDis // Visual representation of the conditional expressions.
}
PM2.5
val weatherConditionBuilder = WeatherConditionBuilder(
cityId = cityId,
cityName = city,
entityType = 3,
weatherType = WeatherType.WEATHER_TYPE_PM,
operator = null,
chooseValue = chooseValue, // User selection
)
val conditionBase = weatherConditionBuilder.build() as ConditionBase
val weatherCondition = SceneCondition(conditionBase).apply {
entityName = city
iconUrl = weatherData.icon // The icon of the selected weather type. This field takes the value of newIcon for PM2.5 in the condition list.
exprDisplay = exprDis // Visual representation of the conditional expressions.
}
Air quality
val weatherConditionBuilder = WeatherConditionBuilder(
cityId = cityId,
cityName = city,
entityType = 3,
weatherType = WeatherType.WEATHER_TYPE_AQI,
operator = null,
chooseValue = chooseValue, // User selection
)
val conditionBase = weatherConditionBuilder.build() as ConditionBase
val weatherCondition = SceneCondition(conditionBase).apply {
entityName = city
iconUrl = weatherData.icon // The icon of the selected weather type. This field takes the value of newIcon for air quality in the condition list.
exprDisplay = exprDis // Visual representation of the conditional expressions.
}
Sunrise/Sunset
val weatherConditionBuilder = WeatherConditionBuilder(
cityId = cityId,
cityName = city,
entityType = 3,
weatherType = WeatherType.WEATHER_TYPE_SUN,
operator = null,
chooseValue = chooseValue, // User selection
)
val conditionBase = weatherConditionBuilder.build() as ConditionBase
val weatherCondition = SceneCondition(conditionBase).apply {
entityName = city
iconUrl = weatherData.icon // The icon of the selected weather type. This field takes the value of newIcon for sunrise/sunset in the condition list.
exprDisplay = exprDis // Visual representation of the conditional expressions.
}
Time before or after sunrise/sunset
val conditionBase: ConditionBase = SunRiseSetConditionBuilder(
cityId,
SunSetRiseRule.SunType.SUNSET, // User selection, SUNSET or SUNRISE
20 // The time before or after sunrise/sunset
).build() as ConditionBase
val sunRiseSetCondition = SceneCondition(conditionBase).apply {
entityName = city
iconUrl = weatherData.icon // The icon of the selected weather type. This field takes the value of newIcon for sunrise/sunset in the condition list.
exprDisplay = exprDis // Visual representation of the conditional expressions.
}
The example creates a condition of when device status changes. It is recommended to display the full list returned by querying the list of devices. After users select a device, get the list of DPs returned by querying the list of DPs for device conditions. Convert condition device DP model to DTO model to present it to users for setup. Then create a condition of when device status changes accordingly.
deviceConditionData
is obtained by converting condition device DP model to DTO model. Its fields should be updated promptly after editing. For example, update ValueTypeData.value
, ValueTypeData.operators
, OtherTypeData.checked
, and ConditionExtraInfo.timeWindow
.
Common devices
val builder = DeviceConditionBuilder(
deviceId = deviceConditionData.deviceId,
dpId = deviceConditionData.datapointId.toString(),
entityType = deviceConditionData.entityType ?: CONDITION_TYPE_DEVICE,
deviceConditionData = deviceConditionData, // After converting the condition device DP model to the DTO model, you can get the list of conditionData.
chooseValue = chooseValue // User setting
)
val conditionBase = builder.build() as ConditionBase
val deviceCondition = SceneCondition(conditionBase).apply {
entityName = deviceBean.name // Device name
entitySubIds = deviceConditionData.datapointId.toString() // Device DP ID
iconUrl = deviceBean.getIconUrl()// Device icon
exprDisplay = displayString // Visual representation of the conditional expressions.
}
DP of temperature for common devices
convertTemp
map stores DP values in different temperature units.originTempUnit
specifies the temperature unit of the DP.tempUnit
specifies the app’s preferred temperature unit.dpScale
specifies the decimal places of the DP.val builder = DeviceConditionBuilder(
deviceId = deviceConditionData.deviceId,
dpId = deviceConditionData.datapointId.toString(),
entityType = deviceConditionData.entityType ?: CONDITION_TYPE_DEVICE,
deviceConditionData = deviceConditionData, // After converting the condition device DP model to the DTO model, you can get the list of conditionData.
chooseValue = chooseValue // User setting
)
val originTempUnit = "celsius" // The temperature unit of the DP.
val tempUnit = "fahrenheit" // The app's preferred temperature unit.
val dpScale = 1
val tempMap = mutableMapOf<String, Int>()
tempMap[originTempUnit] = 120
tempMap[tempUnit] = (120/10 * 1.8 + 32) * 10 // Convert Celsius to Fahrenheit
builder.setConvertTemp(tempMap)
.setTempUnit(tempUnit)
.setOriginTempUnit(originTempUnit)
.setDpScale(dpScale)
val conditionBase = builder.build() as ConditionBase
val deviceCondition = SceneCondition(conditionBase).apply {
entityName = deviceBean.name // Device name
entitySubIds = deviceConditionData.datapointId.toString() // Device DP ID
iconUrl = deviceBean.getIconUrl()// Device icon
exprDisplay = displayString // Visual representation of the conditional expressions.
}
PIR devices
val builder = DeviceConditionBuilder(
deviceId = deviceConditionData.deviceId,
dpId = deviceConditionData.datapointId.toString(),
entityType = deviceConditionData.entityType ?: CONDITION_TYPE_DEVICE,
deviceConditionData = deviceConditionData, // After converting the condition device DP model to the DTO model, you can get the list of conditionData.
chooseValue = chooseValue // User setting
).setDelayTime(otherTypeData?.datapointKey ?: "") // Obtain from conditionData.otherTypeData.datapointKey
val conditionBase = builder.build() as ConditionBase
val deviceCondition = SceneCondition(conditionBase).apply {
entityName = deviceBean.name // Device name
entitySubIds = deviceConditionData.datapointId.toString() // Device DP ID
iconUrl = deviceBean.getIconUrl()// Device icon
exprDisplay = displayString // Visual representation of the conditional expressions.
}
DP duration
deviceConditionData.entityType = CONDITION_TYPE_WITH_TIME
val builder = DeviceConditionBuilder(
deviceId = deviceConditionData.deviceId,
dpId = deviceConditionData.datapointId.toString(),
entityType = deviceConditionData.entityType,
deviceConditionData = deviceConditionData, // After converting the condition device DP model to the DTO model, you can get the list of conditionData.
chooseValue = chooseValue // User setting
).setCalType(COND_TYPE_DURATION)
.setTimeWindow(conditionData.extraInfo?.timeWindow ?: 0)
val conditionBase = builder.build() as ConditionBase
val deviceCondition = SceneCondition(conditionBase).apply {
entityName = deviceBean.name // Device name
entitySubIds = deviceConditionData.datapointId.toString() // Device DP ID
iconUrl = deviceBean.getIconUrl()// Device icon
exprDisplay = displayString // Visual representation of the conditional expressions.
}
The example creates a condition of when home members come home. It is recommended to display the full list of door locks in the home. After users select a lock, display the full list of home members for selection.
devConds
is obtained by querying the list of conditions.
val membersString = "zhangsan,lisi" // Assemble the names of the selected home member, separated by commas.
val memberIds = "123,345" // Assemble the IDs of the selected home member, separated by commas.
val conditionBase = DeviceConditionBuilder(
deviceId = deviceId ?: "", // ID of the door lock
dpId = devConds?.find { it.entityType == ConditionEntityType.LOCK_MEMBER_GO_HOME.type }?.entitySubId ?: "",
entityType = ConditionEntityType.LOCK_MEMBER_GO_HOME.type,
deviceConditionData = null,
chooseValue = memberIds
).setMembers(membersString)
.build() as ConditionBase
val lockCondition = SceneCondition(conditionBase).apply {
entityName = membersString
exprDisplay = display // Visual representation of the conditional expressions.
}
Process
Display the list of action types to choose from.
After users select an action type, present the UI accordingly.
When users complete the action set, assemble the action model of the scene using the builder provided by creating scene actions.
The example creates a delay action. It is recommended to provide a date and time picker
component for users to select a time.
Convert the hour, minute, and second obtained from the picker
component into minutes
and seconds
. Then create a delay action accordingly.
val minutes = 62
val seconds = 20
val actionBase: ActionBase = DelayActionBuilder(minutes, seconds).build() as ActionBase
val delayAction = SceneAction(actionBase).apply {
entityName = "Delay Action"
}
The example creates an action of sending a notification to the specified channel. You can provide the UI for setup based on the notification types.
notifyType
is based on the user’s selection. The constants for message center, SMS, and phone call are ACTION_TYPE_MESSAGE
, ACTION_TYPE_SMS
, and ACTION_TYPE_PHONE
respectively, which are defined in package
: com.thingclips.smart.scene.model.constant.*
. With these parameters, create an action of sending a notification to the specified channel.
SMS and phone calls are value-added services that can be purchased on the Tuya Developer Platform before they can be used. This guide only describes how to create the data model for the notification actions.
Message center
val actionBase: ActionBase = NotifyActionBuilder(ACTION_TYPE_MESSAGE).build() as ActionBase
val notifyAction = SceneAction(actionBase).apply {
entityName = "Message Center"
}
SMS message
You can prompt users to subscribe to the value-added services by specifying the text with actionDisplayNew
. To display the subtitle of the notification action, extract the content from actionDisplayNew
.
Example:
val unableTip = "The prompt indicating SMS service is not available"
val actionBase: ActionBase = NotifyActionBuilder(ACTION_TYPE_SMS).build() as ActionBase
val notifyAction = SceneAction(actionBase).apply {
entityName = "SMS Notification"
actionDisplayNew = mutableMapOf(
"package_has_expired" to listOf(unableTip)
)
}
Phone call
val unableTip = "The prompt indicating phone notification service is not available"
val actionBase: ActionBase = NotifyActionBuilder(ACTION_TYPE_PHONE).build() as ActionBase
val notifyAction = SceneAction(actionBase).apply {
entityName = "Phone Call"
actionDisplayNew = mutableMapOf(
"voice_package_has_expired" to listOf(unableTip)
)
}
The example displays the Tap-to-Run scenes and automations returned by querying the lightweight list of scenes, and then creates a scene action after the user’s selection. You can retrieve the scene data from the local data source, provided that you have cached and updated the list. Provide the UI for setup based on the scene types.
You can get the scene ID linkageRuleId
from the selected scene. linkageOperator
is determined by the scene type selected. The constants for Tap-to-Run, automation enabled, and automation disabled are ACTION_TYPE_TRIGGER
, ACTION_TYPE_ENABLE_AUTOMATION
, and ACTION_TYPE_DISABLE_AUTOMATION
respectively. With these parameters, create a scene action.
Tap-to-Run action
val actionExecutor = ACTION_TYPE_TRIGGER
val actionBase: ActionBase = LinkageRuleActionBuilder(checkedItem.id, actionExecutor).build() as ActionBase // checkedItem.id is the ID of Tap-to-Run
SceneAction(actionBase).apply {
entityName = checkedItem.name // Name of Tap-to-Run
}
Automation action
val actionExecutor = ACTION_TYPE_ENABLE_AUTOMATION
val actionBase: ActionBase = LinkageRuleActionBuilder(checkedItem.id, actionExecutor).build() as ActionBase
// checkedItem.id is the ID of automation
SceneAction(actionBase).apply {
entityName = checkedItem.name // Name of automation
}
The example creates an action of controlling a specific DP of a device. It is recommended to display the full list of devices and groups returned by querying the list of action devices. After users select a device or group, get the DPs returned by querying the list of DPs for the action device or group. Convert action device DP model to DTO model to present it to users for setup. Then create an action of controlling a device.
deviceActionDetailBean
is obtained by converting action device DP model to DTO model. selDpValue
represents the selected DP value, while selDpDisplayValue
is its visual representation.
Common devices
val builder = DeviceActionBuilder(deviceId, deviceActionDetailBean, selDpValue, selDpDisplayValue ?: "")
val actionBase: ActionBase = builder.build() as ActionBase
SceneAction(actionBase).apply {
actionDisplayNew = actionDisplayMap // Visualization after DP selection
extService?.getDevice(deviceId)?.let {
this.devIcon = it.iconUrl // Device icon
this.isDevOnline = it.isOnline // Device online status
this.entityName = it.name // Device name
}
}
DP of step for common devices
val type = DeviceStepType.HIGH
val builder = DeviceActionBuilder(deviceId, deviceActionDetailBean, selDpValue, selDpDisplayValue ?: "", isStep = true)
.apply{
this.setType(type)
}
val actionBase: ActionBase = builder.build() as ActionBase
SceneAction(actionBase).apply {
actionDisplayNew = actionDisplayMap // Visualization after DP selection
extService?.getDevice(deviceId)?.let {
this.devIcon = it.iconUrl // Device icon
this.isDevOnline = it.isOnline // Device online status
this.entityName = it.name // Device name
}
}
DP of temperature for common devices
convertTemp
map stores DP values in different temperature units.originTempUnit
specifies the temperature unit of the DP.tempUnit
specifies the app’s preferred temperature unit.dpScale
specifies the decimal places of the DP.val originTempUnit = "celsius" // The temperature unit of the DP.
val tempUnit = "fahrenheit" // The app's preferred temperature unit.
val dpScale = 1
val convertTemp = mutableMapOf<String, Int>()
convertTemp[originTempUnit] = 120
convertTemp[tempUnit] = (120/10 * 1.8 + 32) * 10 // Convert Celsius to Fahrenheit
val builder = DeviceActionBuilder(deviceId, deviceActionDetailBean, selDpValue, selDpDisplayValue ?: "")
.apply {
originTempUnit?.let {
this.setOriginTempUnit(it)
}
dpScale?.let {
this.setDpScale(it)
}
convertTemp?.let {
this.setConvertTemp(it)
}
}
val actionBase: ActionBase = builder.build() as ActionBase
SceneAction(actionBase).apply {
actionDisplayNew = actionDisplayMap // Visualization after DP selection
extService?.getDevice(deviceId)?.let {
this.devIcon = it.iconUrl // Device icon
this.isDevOnline = it.isOnline // Device online status
this.entityName = it.name // Device name
}
}
Group action
val builder = DeviceActionBuilder(groupId.toString(), deviceActionDetailBean, selDpValue, selDpDisplayValue ?: "")
val actionBase: ActionBase = builder.build() as ActionBase
SceneAction(actionBase).apply {
actionDisplayNew = actionDisplayMap // Visualization after DP selection
this.actionExecutor = ACTION_TYPE_DEVICE_GROUP // The group actionExecutor
extService?.getGroupDevice(groupId)?.let {
this.devIcon = it.iconUrl // Group icon
this.isDevOnline = it.isOnline // Group online status
this.entityName = it.name // Group name
}
}
Is this page helpful?
YesFeedbackIs this page helpful?
YesFeedback