<?php
namespace SHServ\Models;
use \SHServ\Tools\DeviceScanner;
use \SHServ\Tools\DeviceAPI\Base;
use \SHServ\Entities\Device;
use \SHServ\Entities\DeviceAuth;
class Devices extends \SHServ\Middleware\Model {
public function connect_new_device(String $device_ip, String $alias = "", String $name = "", String $description = "", ?Base $device_api = null): Device | Array {
// validate device
if ($device_api === null) {
$device_api = new Base($device_ip);
}
$device_info = $device_api -> get_about();
if(!$device_info["data"]) {
return [
"result" => false,
"err_alias" => "device_not_found"
];
}
if($device_info["data"]["status"] != "setup") {
return [
"result" => false,
"err_alias" => "device_mode_error"
];
}
// generate token
$device_token = app() -> utils -> generate_token(16);
try {
app() -> thin_builder -> beginTransaction();
$check_sql = "SELECT id FROM `" . Device::$table_name . "` WHERE `device_hard_id` = ? AND `status` = 'active'";
if(in_array(FCONF['db']['dblib'] ?? '', ['mysql', 'mariadb'])) {
$check_sql .= " FOR UPDATE";
}
$stmt = app() -> thin_builder -> prepare($check_sql);
$stmt -> execute([$device_info["data"]["device_id"]]);
if($stmt -> fetch(\PDO::FETCH_ASSOC)) {
app() -> thin_builder -> rollBack();
return [
"result" => false,
"err_alias" => "device_already_registered"
];
}
// create in table devices
$device_id = app() -> thin_builder -> insert(Device::$table_name, [
"alias" => $alias,
"name" => $name,
"device_type" => $device_info["data"]["device_type"],
"device_ip" => $device_info["data"]["ip_address"],
"device_mac" => $device_info["data"]["mac_address"],
"device_hard_id" => $device_info["data"]["device_id"],
"firmware_version" => $device_info["data"]["firmware_version"],
"connection_status" => "active",
"status" => "active",
"description" => $description,
"last_contact" => date("Y-m-d H:i:s"),
"create_at" => date("Y-m-d H:i:s")
]);
$device = $device_id ? new Device($device_id) : null;
if(!$device) {
throw new \Exception("Device insert failed");
}
// create in table device_auth
$device_auth = app() -> thin_builder -> insert(DeviceAuth::$table_name, [
"device_id" => $device -> id(),
"device_token" => $device_token,
"status" => "active",
"create_at" => date("Y-m-d H:i:s")
]);
if(!$device_auth) {
throw new \Exception("Device auth insert failed");
}
app() -> thin_builder -> commit();
$device -> set_device_token($device_token);
$device -> device_api() -> set_device_name($name);
return $device;
} catch (\Exception $e) {
if(app() -> thin_builder -> inTransaction()) {
app() -> thin_builder -> rollBack();
}
return [
"result" => false,
"err_alias" => "db_error"
];
}
}
public function resetup_device(Device $device) {
$device_token = app() -> utils -> generate_token(16);
return $device -> resetup($device_token);
}
public function remove_device(int $device_id): Array {
$device = $this -> by_id($device_id);
if(!$device) {
return [
"result" => false,
"err_alias" => "device_not_found"
];
}
$device_auth = $device -> auth();
if(!$device_auth) {
return [
"result" => false,
"err_alias" => "device_not_found"
];
}
$response = $device -> device_api() -> reset();
$device_msg = "";
$status = is_array($response["data"]) ? ($response["data"]["status"] ?? null) : null;
if($response["http_code"] == 200 && $status == "ok") {
$device_msg = $response["data"]["message"] ?? "";
} else {
$device_msg = FCONF['text_msgs']['device_removed_offline'] ?? 'Устройство не отвечает, удалено только из системы';
}
// status killed in device_auth table
$device_auth -> kill();
// remove from devices
$device -> remove();
return [
"result" => true,
"device_msg" => $device_msg
];
}
public function get_device_list(String $status = "active"): Array {
// get from db table devices with status = "active"
$raw_devices = app() -> thin_builder -> select(
Device::$table_name,
Device::get_fields(),
[ ["status", "=", $status] ],
[ "id" ],
"DESC"
);
if(!$raw_devices) {
return [];
}
// wrapped result to Device entity
$devices = [];
foreach($raw_devices as $i => $raw_device) {
$devices[] = new Device($raw_device["id"], $raw_device);
}
return $devices;
}
public function get_unregistered_devices(): Array {
return $this -> scanning_localnet(FCONF["device_ip_range"][0], FCONF["device_ip_range"][1], "setup");
}
public function scanning_localnet(String $from_ip, String $to_ip, String $status = ""): Array {
$device_scanner = new DeviceScanner();
$devices = $device_scanner -> scan_range($from_ip, $to_ip);
if(!$status) {
return $devices;
}
return $device_scanner -> filter_by_status($devices, $status);
}
public function by_hard_id(String $device_hard_id): Device | null {
$results = app() -> thin_builder -> select(
Device::$table_name,
Device::get_fields(),
[
[ "device_hard_id", "=", $device_hard_id ],
"AND",
[ "status", "=", "active" ]
]
);
if(!$results) {
return null;
}
return new Device($results[0]["id"], $results[0]);
}
public function by_alias(String $alias): Device | null {
$results = app() -> thin_builder -> select(
Device::$table_name,
Device::get_fields(),
[
[ "alias", "=", $alias ],
"AND",
[ "status", "=", "active" ]
]
);
if(!$results) {
return null;
}
return new Device($results[0]["id"], $results[0]);
}
public function by_id(int $device_id): Device | null {
$results = app() -> thin_builder -> select(
Device::$table_name,
Device::get_fields(),
[
[ "id", "=", $device_id ],
"AND",
[ "status", "=", "active" ]
]
);
if(!$results) {
return null;
}
return new Device($results[0]["id"], $results[0]);
}
public function reboot_device(int $device_id): Array {
$device = $this -> by_id($device_id);
if(!$device) {
return [
"result" => false,
"err_alias" => "device_not_found"
];
}
$response = $device -> device_api() -> reboot();
if($response["http_code"] != 200) {
return [
"result" => false,
"err_alias" => "device_request_fail"
];
}
if($response["data"]["status"] == "ok") {
return [
"result" => true,
"msg" => $response["data"]["message"] ?? ""
];
}
$err_type = $response["data"]["error"] ?? "undefined_error";
$err_msg = $response["data"]["message"] ?? "Unknown error";
return [
"result" => false,
"err_alias" => "device_request_fail",
"msg" => "<b>{$err_type}</b>. {$err_msg}"
];
}
public function get_device_info($device_id_or_alias): Array | null {
if(is_string($device_id_or_alias)) {
$device = $this -> by_alias($device_id_or_alias);
} else {
$device = $this -> by_id($device_id_or_alias);
}
if(!$device) {
return null;
}
$about = $device -> device_api() -> get_about();
$info = [
"id" => $device -> id(),
"alias" => $device -> alias,
"name" => $device -> name,
"description" => $device -> description,
"status" => $device -> status,
"connection_status" => $device -> connection_status,
"last_contact" => $device -> last_contact,
"create_at" => $device -> create_at,
"device" => $about["data"] ?? []
];
return $info;
}
public function alias_is_uniq(String $alias, ?int $exclude_id = null): bool {
$where = [ ["alias", "=", $alias], "AND", ["status", "!=", "removed"] ];
if ($exclude_id !== null) {
$where[] = "AND";
$where[] = ["id", "!=", $exclude_id];
}
$count = app() -> thin_builder -> count(
Device::$table_name,
$where
);
return $count ? false : true;
}
/**
* Reconcile scan results against active devices in DB.
* For each active device whose hard_id was found on the network,
* update device_ip if it changed and restore connection_status to active.
* Returns statistics about updates.
*
* @param array $found_devices Results from DeviceScanner::scan_range()
* @return array ['updated' => int, 'restored' => int]
*/
public function reconcile_scan_results(array $found_devices): array {
$active_devices = $this -> get_device_list("active");
$found_by_hard_id = [];
foreach($found_devices as $found) {
$hard_id = $found["device_id"] ?? null;
if($hard_id) {
$found_by_hard_id[(string)$hard_id] = $found;
}
}
$updated = 0;
$restored = 0;
foreach($active_devices as $device) {
if(!isset($found_by_hard_id[(string)$device -> device_hard_id])) {
continue;
}
$found = $found_by_hard_id[(string)$device -> device_hard_id];
$is_changed = false;
if($device -> device_ip != ($found["ip_address"] ?? "")) {
$device -> device_ip = $found["ip_address"] ?? "";
$is_changed = true;
$updated++;
}
if($device -> connection_status != "active") {
$device -> connection_status = "active";
$is_changed = true;
$restored++;
}
if($is_changed) {
$device -> update();
}
// HTTP-ответ от устройства = контакт
$device -> touch_last_contact();
}
return [
"updated" => $updated,
"restored" => $restored,
];
}
}