程序总体框架
首先我们需要模拟一个真正的外部设备通过天猫精灵进行交互的过程,对于一个设备来说主要是由控制,状态等组成,天猫精灵给了3中方式实现,分别是发现、控制和状态查询(Discovery,Control,Query)。对于一个服务器来说真的通过一些方法去控制远端的设备是很难的,因为远端的设备往往是无法直接访问,一般的实现方法是远端设备去定时访问服务器来同步最新的状态,如果服务器记录的状态改变则设备状态变化,如果本地的设备有主动变化则主动上报服务器去修改记录。服务器端一般是由一个对应设备的数据库来记录设备的状态,我们这里为了简便,我们使用一个txt文本模拟数据库,文本中的数据代表着远端设备的状态。
首先展示一下我们目录结构:
.
├── aligenie (针对天猫精灵的php文件)
│ ├── aligenies_authorize.php
│ ├── aligenies_gate.php
│ ├── aligenies_handle.php
│ ├── aligenies_server.php
│ ├── aligenies_token.php
│ ├── ControlResponse.php
│ ├── DiscoveryResponse.php
│ └── QueryResponse.php
├── icon (显示使用的图标)
│ ├── cz.png
│ └── td.png
├── oauth2-server-php (下载的认证源码)
└── sqldata.txt (模拟数据库的文本文件)
其中sqldata.txt就是我们用来模拟设备数据库的文件,其中的内容是我们设备需要记录的一个json结构,比如我们需要记录一个具有黄白双色的可调光的吸顶灯,一个开关。
{
"dev_array": [
{
"entity_id": "light.ceiling_lamp.34ce008dc8c3",
"device_type": "ceiling_lamp",
"properties": [
{
"name": "powerstate",
"value": "on"
},
{
"name": "brightness_w",
"value": 0
},
{
"name": "brightness_y",
"value": 100
}
]
},
{
"entity_id": "switch.gateway_switch.34ce008dc8c3",
"device_type": "gateway_switch",
"properties": [
{
"name": "powerstate",
"value": "on"
}
]
}
]
}
entity_id是设备ID,device_type是设备的类型,这个类型不是天猫使用的,而是我们自己定义的设备类型,properties是该设备具有的属性,powerstate是灯的状态属性,brightness_w是白色的亮度属性,brightness_y是黄色的亮度属性。在我们自己的app上可以比天猫精灵控制更多的属性(一般都是这样),比如我们可以自由的组合黄白双色的亮度比例,而天猫精灵一次对话只能控制其中一个属性的变化。
总的天猫精灵控制流程就是首先会进入aligenies_gate.php文件,这个是天猫精灵控制的入口,也是我们在天猫精灵服务器网页设置的网址,aligenies_gate.php中会调用aligenies_handle.php去处理数据,把天猫精灵的格式转为我们自己的格式,并做相应的动作。
ControlResponse.php,DiscoveryResponse.php,QueryResponse.php分别是天猫精灵需要回复的json格式的类,我们需要将类填充并转为json格式传给天猫精灵即可。
三种回复的格式
DiscoveryResponse.php文件是设备发现使用的回复格式
设备发现主要是如下的格式例子
正确相应
{
"header":{
"namespace":"AliGenie.Iot.Device.Discovery",
"name":"DiscoveryDevicesResponse",
"messageId":"1bd5d003-31b9-476f-ad03-71d471922820",
"payLoadVersion":1
},
"payload":{
"devices":[{
"deviceId":"34ea34cf2e63",
"deviceName":"light1",
"deviceType":"light",
"zone":"",
"brand":"",
"model":"",
"icon":"https://git.cn-hangzhou.oss-cdn.aliyun-inc.com/uploads/aicloud/aicloud-proxy-service/41baa00903a71c97e3533cf4e19a88bb/image.png",
"properties":[{
"name":"color",
"value":"Red"
}],
"actions":[
"TurnOn",
"TurnOff",
"SetBrightness",
"AdjustBrightness",
"SetTemperature",
"Query"
],
"extensions":{
"extension1":"",
"extension2":""
}
}]
}
}
错误相应
{
"header":{
"namespace":"AliGenie.Iot.Device.XXXX",
"name":"ErrorResponse",
"messageId":"1bd5d003-31b9-476f-ad03-71d471922820",
"payLoadVersion":1
},
"payload":{
"deviceId":"34234",
"errorCode":"DEVICE_NOT_SUPPORT_FUNCTION",
"message":"device not support"
}
}
所以总结上面的格式我们定义了个回复设备发现的类DiscoveryResponse.php
<?php
namespace AliGenie;
class DiscoveryResponse {
public $header;
public $payload;
public function __construct($isError = false) {
$this->header = new DiscoveryHeader();
if ($isError) {
$this->header->putResponseName("Error");
$this->payload = new DiscoveryErrPayload();
} else {
$this->payload = new DiscoveryPayload();
}
}
}
class DiscoveryHeader {
public $namespace = "AliGenie.Iot.Device.Discovery";
public $name = "DiscoveryDevicesResponse";
public $messageId = "";
public $payLoadVersion = 1;
public function putResponseName($name) {
$this->name = $name."Response";
}
public function putResponseMessageId($messageId) {
$this->messageId = $messageId;
}
}
class DiscoveryPayload {
public $devices = array();
public function putResponseDevices($device) {
$this->devices[] = $device;
}
}
class DiscoveryErrPayload extends DiscoveryPayload{
public $errorCode;
public $message;
public function putResponseError($errorCode, $message = "") {
$this->errorCode = $errorCode;
switch ($errorCode) {
case "INVALIDATE_CONTROL_ORDER":
if ($message == "") {
$this->message = "invalidate control order";
}
break;
case "SERVICE_ERROR":
break;
case "DEVICE_NOT_SUPPORT_FUNCTION":
if ($message == "") {
$this->message = "device not support";
}
break;
case "INVALIDATE_PARAMS":
if ($message == "") {
$this->message = "invalidate params";
}
break;
case "DEVICE_IS_NOT_EXIST":
if ($message == "") {
$this->message = "device is not exist";
}
break;
case "IOT_DEVICE_OFFLINE":
if ($message == "") {
$this->message = "device is offline";
}
break;
case "ACCESS_TOKEN_INVALIDATE":
if ($message == "") {
$this->message = "access_token is invalidate";
}
break;
default:
break;
}
}
}
class DiscoveryDevice {
public $deviceId;
public $deviceName;
public $deviceType;
public $zone = "";
public $brand = "";
public $model = "";
public $icon;
public $properties = array();
public $actions = array();
public $extensions;
public function putResponseDeviceInfo($deviceId, $deviceName, $deviceType, $icon) {
$this->deviceId = $deviceId;
$this->deviceName = $deviceName;
$this->deviceType = $deviceType;
$this->icon = $icon;
}
public function putResponseProperties($propertie) {
$this->properties[] = $propertie;
}
public function putResponseExtensions($extension) {
$this->extensions = $extension;
}
}
class DiscoveryPropertie {
public $name;
public $value;
public function __construct($name, $value) {
$this->name = $name;
$this->value = $value;
}
}
class DiscoveryExtension {
public $extension1 = "";
public $extension2 = "";
}
class DiscoveryExtensionEx extends DiscoveryExtension {
public $parentId;
public function __construct($parentId) {
$this->parentId = $parentId;
}
}
function discovery_response_demo()
{
$res = new DiscoveryResponse();
$res->header->putResponseMessageId("1bd5d003-31b9-476f-ad03-71d471922820");
$dev = new DiscoveryDevice();
$dev->putResponseDeviceInfo("34ea34cf2e63", "单孔插座", "outlet",
"https://git.cn-hangzhou.oss-cdn.aliyun-inc.com/uploads/aicloud/aicloud-proxy-service/41baa00903a71c97e3533cf4e19a88bb/image.png");
$pro = new DiscoveryPropertie("powerstate", "off");
$dev->putResponseProperties($pro);
$ext = new DiscoveryExtension();
$dev->putResponseExtensions($ext);
$dev->actions = array("TurnOn", "TurnOff");
$res->payload->putResponseDevices($dev);
$dev = new DiscoveryDevice();
$dev->putResponseDeviceInfo("34ea34cf2eff", "灯", "light",
"https://git.cn-hangzhou.oss-cdn.aliyun-inc.com/uploads/aicloud/aicloud-proxy-service/41baa00903a71c97e3533cf4e19a88bb/image.png");
$pro = new DiscoveryPropertie("powerstate", "off");
$dev->putResponseProperties($pro);
$ext = new DiscoveryExtensionEx("34ea34cf2e63");
$dev->putResponseExtensions($ext);
$dev->actions = array("TurnOn", "TurnOff");
$res->payload->putResponseDevices($dev);
$resJson = json_encode($res);
echo "$resJson";
}
?>
ControlResponse.php文件是设备控制使用的回复格式
设备控制主要是如下的格式例子
正确相应
{
"header":{
"namespace":"AliGenie.Iot.Device.Control",
"name":"TurnOnResponse",
"messageId":"1bd5d003-31b9-476f-ad03-71d471922820",
"payLoadVersion":1
},
"payload":{
"deviceId":"34234"
}
}
错误相应同上。
所以总结上面的格式我们定义了个回复设备控制的类ControlResponse.php
<?php
namespace AliGenie;
class ControlResponse {
public $header;
public $payload;
public function __construct($isError = false) {
$this->header = new ControlHeader();
if ($isError) {
$this->header->putResponseName("Error");
$this->payload = new ControlErrPayload();
} else {
$this->payload = new ControlPayload();
}
}
}
class ControlHeader {
public $namespace = "AliGenie.Iot.Device.Control";
public $name = "";
public $messageId = "";
public $payLoadVersion = 1;
public function putResponseName($name) {
$this->name = $name."Response";
}
public function putResponseMessageId($messageId) {
$this->messageId = $messageId;
}
}
class ControlPayload {
public $deviceId;
public function putResponseDeviceId($deviceId) {
$this->deviceId = $deviceId;
}
}
class ControlErrPayload extends ControlPayload{
public $errorCode;
public $message;
public function putResponseError($errorCode, $message = "") {
$this->errorCode = $errorCode;
switch ($errorCode) {
case "INVALIDATE_CONTROL_ORDER":
if ($message == "") {
$this->message = "invalidate control order";
}
break;
case "SERVICE_ERROR":
break;
case "DEVICE_NOT_SUPPORT_FUNCTION":
if ($message == "") {
$this->message = "device not support";
}
break;
case "INVALIDATE_PARAMS":
if ($message == "") {
$this->message = "invalidate params";
}
break;
case "DEVICE_IS_NOT_EXIST":
if ($message == "") {
$this->message = "device is not exist";
}
break;
case "IOT_DEVICE_OFFLINE":
if ($message == "") {
$this->message = "device is offline";
}
break;
case "ACCESS_TOKEN_INVALIDATE":
if ($message == "") {
$this->message = "access_token is invalidate";
}
break;
default:
break;
}
}
}
function control_response_demo()
{
$res = new ControlResponse();
$res->header->putResponseName("TurnOn");
$res->header->putResponseMessageId("1bd5d003-31b9-476f-ad03-71d471922820");
$res->payload->putResponseDeviceId("34234");
$resJson = json_encode($res);
echo "$resJson";
$res = new ControlResponse(true);
$res->header->putResponseMessageId("1bd5d003-31b9-476f-ad03-71d471922820");
$res->payload->putResponseDeviceId("34234");
$res->payload->putResponseError("DEVICE_NOT_SUPPORT_FUNCTION");
$resJson = json_encode($res);
echo "$resJson";
}
?>
QueryResponse.php文件是设备状态查询使用的回复格式
设备状态查询主要是如下的格式例子
正确相应
{
"properties":[
{
"name":"temperature",
"value":"27"
}
],
"header":{
"namespace":"AliGenie.Iot.Device.Query",
"name":"QueryTemperatureResponse",
"messageId":"1bd5d003-31b9-476f-ad03-71d471922820",
"payLoadVersion":1
},
"payload":{
"deviceId":"34234"
}
}
错误相应同上
所以总结上面的格式我们定义了个回复设备状态查询的类QueryResponse.php
<?php
namespace AliGenie;
class QueryResponse {
public $header;
public $payload;
public function __construct($isError = false) {
$this->header = new QueryHeader();
if ($isError) {
$this->header->putResponseName("Error");
$this->payload = new QueryErrPayload();
} else {
$this->payload = new QueryPayload();
$this->properties = array();
}
}
}
class QueryPropertie {
public $name;
public $value;
public function __construct($name, $value) {
$this->name = $name;
$this->value = $value;
}
}
class QueryHeader {
public $namespace = "AliGenie.Iot.Device.Query";
public $name = "";
public $messageId = "";
public $payLoadVersion = 1;
public function putResponseName($name) {
$this->name = $name."Response";
}
public function putResponseMessageId($messageId) {
$this->messageId = $messageId;
}
}
class QueryPayload {
public $deviceId;
public function putResponseDeviceId($deviceId) {
$this->deviceId = $deviceId;
}
}
class QueryErrPayload extends QueryPayload{
public $errorCode;
public $message;
public function putResponseError($errorCode, $message = "") {
$this->errorCode = $errorCode;
switch ($errorCode) {
case "INVALIDATE_CONTROL_ORDER":
if ($message == "") {
$this->message = "invalidate control order";
}
break;
case "SERVICE_ERROR":
break;
case "DEVICE_NOT_SUPPORT_FUNCTION":
if ($message == "") {
$this->message = "device not support";
}
break;
case "INVALIDATE_PARAMS":
if ($message == "") {
$this->message = "invalidate params";
}
break;
case "DEVICE_IS_NOT_EXIST":
if ($message == "") {
$this->message = "device is not exist";
}
break;
case "IOT_DEVICE_OFFLINE":
if ($message == "") {
$this->message = "device is offline";
}
break;
case "ACCESS_TOKEN_INVALIDATE":
if ($message == "") {
$this->message = "access_token is invalidate";
}
break;
default:
break;
}
}
}
function query_response_demo()
{
$res = new QueryResponse();
$res->header->putResponseName("Query");
$res->header->putResponseMessageId("1bd5d003-31b9-476f-ad03-71d471922820");
$res->payload->putResponseDeviceId("34234");
$pro = new QueryPropertie("powerstate", "on");
$res->properties[] = $pro;
$pro = new QueryPropertie("color", "Red");
$res->properties[] = $pro;
$pro = new QueryPropertie("temperature", "27");
$res->properties[] = $pro;
$pro = new QueryPropertie("humidity", "20");
$res->properties[] = $pro;
$pro = new QueryPropertie("windspeed", "2");
$res->properties[] = $pro;
$pro = new QueryPropertie("humidity", "23");
$res->properties[] = $pro;
$pro = new QueryPropertie("pm2.5", "20");
$res->properties[] = $pro;
$pro = new QueryPropertie("direction", "left");
$res->properties[] = $pro;
$pro = new QueryPropertie("angle", "60");
$res->properties[] = $pro;
$resJson = json_encode($res);
echo "$resJson";
$res = new QueryResponse(true);
$res->header->putResponseMessageId("1bd5d003-31b9-476f-ad03-71d471922820");
$res->payload->putResponseDeviceId("34234");
$res->payload->putResponseError("DEVICE_NOT_SUPPORT_FUNCTION");
$resJson = json_encode($res);
echo "$resJson";
}
?>
以上是三种类型的回复,其实这里实现的并不是很好,因为三种错误的回复格式都是一样的,可以把错误回复提炼成一个类,但是懒得做了,因为在做的过程中开始渐渐的对天猫精灵失去了信心,想把这里做完了就去实现小度的语音开发,后面我们在说天猫精灵的各种问题。