diff --git a/commands/TestController.php b/commands/PanelController.php similarity index 83% rename from commands/TestController.php rename to commands/PanelController.php index a27ce96..6812ec6 100644 --- a/commands/TestController.php +++ b/commands/PanelController.php @@ -2,17 +2,16 @@ namespace app\commands; -use app\servers\Test; +use app\servers\Panel; use Ratchet\Server\IoServer; use Ratchet\Http\HttpServer; use Ratchet\WebSocket\WsServer; use React\EventLoop\Factory; use React\Socket\Server; -use Ratchet\Http\OriginCheck; use Yii; use yii\console\Controller; -class TestController extends Controller +class PanelController extends Controller { public function actionIndex($port = 8081) { @@ -26,7 +25,7 @@ class TestController extends Controller $server = new IoServer( new HttpServer( new WsServer( - new Test($loop) + new Panel($loop) ) ), $socket, diff --git a/components/WebSocketAuth.php b/components/WebSocketAuth.php index b9cc1af..4a1a661 100644 --- a/components/WebSocketAuth.php +++ b/components/WebSocketAuth.php @@ -18,12 +18,6 @@ class WebSocketAuth return null; } - $auth_key = $user->auth_key; - - // Regenerate auth key - $user->generateAuthKey(); - $user->save(); - - return $auth_key; + return $user->auth_key; } } diff --git a/config/params.php b/config/params.php index 31518e5..473c41b 100644 --- a/config/params.php +++ b/config/params.php @@ -4,7 +4,7 @@ use yii\helpers\ArrayHelper; $params = [ 'adminEmail' => 'admin@example.com', - 'wsURL' => 'ws://192.168.1.101:8081', + 'wsURL' => 'ws://192.168.1.102:8081', ]; return ArrayHelper::merge($params, require 'params-local.php'); diff --git a/models/Item.php b/models/Item.php index 78b216d..ef496fb 100644 --- a/models/Item.php +++ b/models/Item.php @@ -37,6 +37,12 @@ class Item extends ActiveRecord const VALUE_ON = 1; const VALUE_OFF = 0; + /** + * Used for WS handler + * @var mixed + */ + public $value; + /** * @inheritdoc */ diff --git a/servers/Panel.php b/servers/Panel.php index 326fc39..f67c9c9 100644 --- a/servers/Panel.php +++ b/servers/Panel.php @@ -9,7 +9,6 @@ use app\models\Item; use app\models\User; use Ratchet\ConnectionInterface; use Ratchet\MessageComponentInterface; -use Ratchet\WebSocket\Version\RFC6455\Connection; use React\EventLoop\LoopInterface; use Yii; use yii\helpers\Json; @@ -30,38 +29,38 @@ class Panel implements MessageComponentInterface protected $loop; /** + * All connected users * @var ConnectionInterface[] */ protected $user_clients; /** + * All connected boards * @var ConnectionInterface[] */ protected $board_clients; /** - * @var array + * @var Item[] */ protected $items; /** - * Panel constructor. + * Class constructor. * - * Init variables and preparing + * Init variables, etc. * * @param LoopInterface $loop */ public function __construct($loop) { + // Init variables $this->loop = $loop; $this->user_clients = []; $this->board_clients = []; - $this->items = []; - // Database driver hack + // Database driver hack: Prevent MySQL for disconnecting by timeout Yii::$app->db->createCommand('SET SESSION wait_timeout = 2147483;')->execute(); - - // Prevent MySQL for disconnecting by timeout $this->loop->addPeriodicTimer(8600, function () { Yii::$app->db->createCommand('SHOW TABLES;')->execute(); }); @@ -72,22 +71,21 @@ class Panel implements MessageComponentInterface foreach ($items as $item) { $this->items[$item->id] = $item; - if ($item->save_history_interval > 0) { - $this->loop->addPeriodicTimer($item->save_history_interval, function () use ($item) { - $this->saveHistory($item); - }); - } +// if ($item->save_history_interval > 0) { +// $this->loop->addPeriodicTimer($item->save_history_interval, function () use ($item) { +// $this->saveHistory($item); +// }); +// } } + + $this->log('Server started'); } - /** - * @inheritdoc - */ public function onOpen(ConnectionInterface $conn) { - // Get query $query = $conn->WebSocket->request->getQuery(); + // Handle connection by type $type = $query->get('type'); switch ($type) { @@ -96,7 +94,7 @@ class Panel implements MessageComponentInterface $userAuthKey = $query->get('auth_key'); if (!$userID or !$userAuthKey) { - return false; + return $conn->close(); } $user = User::findOne([ @@ -105,7 +103,7 @@ class Panel implements MessageComponentInterface ]); if (!$user) { - return false; + return $conn->close(); } // Close previous connection @@ -113,17 +111,22 @@ class Panel implements MessageComponentInterface $this->user_clients[$user->id]->close(); } - // Attach to clients + // Attach to users $conn->User = $user; $this->user_clients[$user->id] = $conn; + $conn->send(Json::encode([ + 'type' => 'init', + 'items' => $this->items, + ])); + return $this->log("Connected user [{$user->id}] {$user->username}"); case 'board': $boardID = $query->get('id'); $boardSecret = $query->get('secret'); if (!$boardID or !$boardSecret) { - return false; + return $conn->close(); } $board = Board::findOne([ @@ -133,7 +136,7 @@ class Panel implements MessageComponentInterface ]); if (!$board) { - return false; + return $conn->close(); } // Close previous connection @@ -141,7 +144,7 @@ class Panel implements MessageComponentInterface $this->board_clients[$board->id]->close(); } - // Attach to clients + // Attach to boards $conn->Board = $board; $this->board_clients[$board->id] = $conn; @@ -153,6 +156,7 @@ class Panel implements MessageComponentInterface public function onMessage(ConnectionInterface $from, $msg) { + $this->log('new message ' . $msg); if ($from->User) { return $this->handleUserMessage($from, $msg); } elseif ($from->Board) { @@ -166,27 +170,42 @@ class Panel implements MessageComponentInterface { if (isset($conn->User)) { unset($this->user_clients[$conn->User->id]); + + $conn->User->generateAuthKey(); + $conn->User->save(); + + $this->log("User [{$conn->User->id} disconnected]"); } elseif (isset($conn->Board)) { unset($this->board_clients[$conn->Board->id]); + + $this->log("Board [{$conn->Board->id} disconnected]"); } } public function onError(ConnectionInterface $conn, \Exception $e) { - // Logging - echo "Error: {$e->getMessage()} in file {$e->getFile()} at line {$e->getLine()}" . PHP_EOL; + $this->log("Error: {$e->getMessage()} in file {$e->getFile()} at line {$e->getLine()}"); // Close connection $conn->close(); } + /** + * @param $from + * @param $msg + * @return bool + */ public function handleUserMessage($from, $msg) { $user = $from->User; $data = Json::decode($msg); switch ($data['type']) { - case 'switch': + case 'turnON': + return $this->handleTurnOn($from, $user, $data); + case 'turnOFF': + return $this->handleTurnOff($from, $user, $data); + /*case 'switch': $item_id = $data['item_id']; $item = Item::findOne($item_id); @@ -205,20 +224,26 @@ class Panel implements MessageComponentInterface } } - break; + break;*/ } return false; } + /** + * @param $from + * @param $msg + * @return bool + */ public function handleBoardMessage($from, $msg) { + /** @var Board $board */ $board = $from->Board; $data = Json::decode($msg); switch ($data['type']) { case 'value': - $pin = $data['pin']; + $pin = (integer)$data['pin']; $item = Item::findOne([ 'board_id' => $board->id, @@ -229,6 +254,14 @@ class Panel implements MessageComponentInterface return false; } + $this->items[$item->id]->value = $data['value']; + + $this->sendUsers([ + 'type' => 'value', + 'item_id' => $item->id, + 'value' => $data['value'], + ]); + break; } @@ -243,6 +276,8 @@ class Panel implements MessageComponentInterface */ protected function handleTurnOn(ConnectionInterface $from, $user, $data) { + $this->log('handle turn on '); + $item_id = (int)$data['item_id']; $item = Item::findOne($item_id); @@ -261,10 +296,67 @@ class Panel implements MessageComponentInterface ]); } - $api = new ApiHelper($item); - $api->turnOn(); + $board = $item->board; - return $this->saveHistory($item, Item::VALUE_ON); + if ($board->type === Board::TYPE_AREST) { + $api = new ApiHelper($item); + $result = $api->turnOn(); + } elseif ($board->type === Board::TYPE_WEBSOCKET) { + $result = $this->sendToBoard($board->id, [ + 'type' => 'turnON', + 'pin' => $item->pin, + ]); + } + + if (!$result) { + return $from->send(Json::encode([ + 'type' => 'error', + 'message' => 'Не получилось включить: устройство не подключено', + ])); + } + + return true; + } + + /** + * @param ConnectionInterface $from + * @param User $user + * @param array $data + * @return bool|mixed + */ + protected function handleTurnOff(ConnectionInterface $from, $user, $data) + { + $item_id = (int)$data['item_id']; + + $item = Item::findOne($item_id); + + if (!$item) { + return $from->send([ + 'type' => 'error', + 'message' => 'Выключить не удалось', + ]); + } + + if ($item->type !== Item::TYPE_SWITCH) { + return $from->send([ + 'type' => 'error', + 'message' => 'Данный тип устройства нельзя переключать', + ]); + } + + $board = $item->board; + + if ($board->type === Board::TYPE_AREST) { + $api = new ApiHelper($item); + return $api->turnOff(); + } elseif ($board->type === Board::TYPE_WEBSOCKET) { + return $this->sendToBoard($board->id, [ + 'type' => 'turnOFF', + 'pin' => $item->pin, + ]); + } + + return false; } /** @@ -272,31 +364,37 @@ class Panel implements MessageComponentInterface * * @param array $data */ - private function sendAll($data) + private function sendUsers($data) { - $encodedData = Json::encode($data); + $msg = Json::encode($data); foreach ($this->user_clients as $client) { - $client->send($encodedData); + $client->send($msg); } } /** - * Send data to specific user + * Send data to specific board * - * @param integer $user_id + * @param integer $board_id * @param array $data - * @return bool|mixed + * @return bool|ConnectionInterface */ - private function sendTo($user_id, $data) + private function sendToBoard($board_id, $data) { - if (isset($this->user_clients[$user_id])) { + if (isset($this->board_clients[$board_id])) { /** @var ConnectionInterface $client */ - $client = $this->user_clients[$user_id]; + $client = $this->board_clients[$board_id]; - return $client->send(Json::encode($data)); + $msg = Json::encode($data); + + $this->log("Sending to board [$board_id]: $msg"); + + return $client->send($msg); } + $this->log("Cannot send to board [$board_id]: not connected"); + return false; } diff --git a/servers/Test.php b/servers/Test.php deleted file mode 100644 index b153fef..0000000 --- a/servers/Test.php +++ /dev/null @@ -1,146 +0,0 @@ - - */ -class Test implements MessageComponentInterface -{ - /** - * @var \React\EventLoop\LoopInterface - */ - protected $loop; - - /** - * @var ConnectionInterface[] - */ - protected $clients; - - /** - * @var ConnectionInterface[] - */ - protected $boards; - - /** - * Panel constructor. - * - * Init variables and preparing - * - * @param LoopInterface $loop - */ - public function __construct($loop) - { - $this->loop = $loop; - $this->clients = []; - $this->boards = []; - - $this->log('Server started!'); - } - - /** - * @inheritdoc - */ - public function onOpen(ConnectionInterface $conn) - { - $this->log('Connecting new client....'); - - /** @var QueryString $query */ - $query = $conn->WebSocket->request->getQuery(); - - $type = $query->get('type'); - - if (!$type) { - return false; - } - - if ($type == 'board') { - $id = $query->get('id'); - - $conn->type = 'board'; - $conn->id = $id; - $this->boards[$id] = $conn; - - $this->log("Board connected. BoardID: $id"); - - $this->loop->addPeriodicTimer(3, function () use ($conn) { - $conn->send(Json::encode(['type' => 'switch', 'pin' => 8])); - }); - - return true; - } elseif ($type == 'user') { - $id = $query->get('id'); - - $conn->type = 'user'; - $conn->id = $id; - - $this->clients[$id] = $conn; - - $this->log("User connected. ID: $id"); - - return true; - } - - return false; - } - - public function onMessage(ConnectionInterface $from, $msg) - { - if ($from->type == 'user') { - $data = Json::decode($msg); - - if ($data['type'] == 'switch') { - $boardID = $data['boardID']; - $this->boards[$boardID]->send(1); - } - $this->log("New message from [{$from->username}]: $msg"); - } elseif (!$from->type) { - $this->log("New message from [{$from->id}]: $msg"); - } - } - - public function onClose(ConnectionInterface $conn) - { - $this->sendAll("Disconnected []"); - $this->log("Disconnected []"); - } - - public function onError(ConnectionInterface $conn, \Exception $e) - { - // Logging - echo "Error: {$e->getMessage()} in file {$e->getFile()} at line {$e->getLine()}" . PHP_EOL; - - // Close connection - $conn->close(); - } - - public function sendAll($text) - { - foreach ($this->clients as $client) { - $client->send($text); - } - } - - public function log($msg) - { - echo $msg . PHP_EOL; - } -} diff --git a/views/item/_form.php b/views/item/_form.php index 979aaf8..3f9eb00 100644 --- a/views/item/_form.php +++ b/views/item/_form.php @@ -17,33 +17,47 @@ use yii\widgets\ActiveForm; - field($model, 'name')->textInput(['maxlength' => true]) ?> +
+
+

Основные настройки

- field($model, 'icon')->textInput(['maxlength' => true]) ?> + field($model, 'name')->textInput(['maxlength' => true]) ?> - field($model, 'bg')->textInput(['maxlength' => true]) ?> + field($model, 'type')->dropDownList(Item::getTypesArray()) ?> - field($model, 'class')->textInput(['maxlength' => true]) ?> + field($model, 'board_id')->dropDownList( + ArrayHelper::map(Board::find()->all(), 'id', 'name') + ) ?> - field($model, 'board_id')->dropDownList( - ArrayHelper::map(Board::find()->all(), 'id', 'name') - ) ?> +
+
+ field($model, 'pin')->input('number') ?> +
+
+ field($model, 'url')->textInput(['maxlength' => true]) ?> +
+
- field($model, 'pin')->input('number') ?> + field($model, 'update_interval')->textInput() ?> - field($model, 'type')->dropDownList(Item::getTypesArray()) ?> + field($model, 'save_history_interval')->input('number') ?> +
+
+

Дизайн

- field($model, 'update_interval')->textInput() ?> + field($model, 'icon')->textInput(['maxlength' => true]) ?> - field($model, 'save_history_interval')->input('number') ?> + field($model, 'bg')->textInput(['maxlength' => true]) ?> - field($model, 'room_id')->dropDownList( - ArrayHelper::map(Room::find()->all(), 'id', 'name') - ) ?> + field($model, 'class')->textInput(['maxlength' => true]) ?> - field($model, 'url')->textInput(['maxlength' => true]) ?> + field($model, 'room_id')->dropDownList( + ArrayHelper::map(Room::find()->all(), 'id', 'name') + ) ?> - field($model, 'sort_order')->textInput() ?> + field($model, 'sort_order')->textInput() ?> +
+
isNewRecord ? 'Добавить' : 'Сохранить',