FindBoxCoreSystem.php 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375
  1. <?php
  2. /**
  3. * @param array $array
  4. * @param array $keysToExtract
  5. * @return array
  6. * @author Open Mind
  7. * @date 28/03/2018
  8. * @time 09:13
  9. */
  10. namespace OpenMind\FindBox;
  11. use Exception;
  12. use OpenMind\System\System;
  13. use ZipArchive;
  14. function array_filter_keys(array $array, array $keysToExtract)
  15. {
  16. return array_intersect_key($array, array_flip($keysToExtract));
  17. }
  18. class FindBoxCoreSystem
  19. {
  20. private $Host;
  21. private $Port;
  22. private $Key;
  23. private $Pass;
  24. private $coreDetails;
  25. /**
  26. * CoreLabel constructor.
  27. *
  28. * @param string $host hostname of FindBox OS system
  29. * @param int $port
  30. * @param string $APIKey
  31. * @param string $Password
  32. * @throws Exception
  33. */
  34. public function __construct(string $host, int $port, string $APIKey, string $Password)
  35. {
  36. $this->Host = $host;
  37. $this->Port = $port;
  38. $this->Key = $APIKey;
  39. $this->Pass = $Password;
  40. Global $System;
  41. /** @var System $System */
  42. $Cache = $System->getCache();
  43. if (!$Cache->exists('FindBoxCoreSystem')) {
  44. $URI = '/1.0.0/index.php/getstatus/1.0.0/';
  45. $ret = $this->CallAPI($URI, "");
  46. if ($ret['err'] != 0)
  47. throw new Exception("Can't connect to FindBox server:<br/><br/>{$ret['value']}");
  48. if ($ret['err'] == 0 && $ret['value']['code'] == 0) {
  49. $details = $ret['value']['value'];
  50. $this->coreDetails = array_filter_keys($details, ['core.box_id', 'core.version']);
  51. }
  52. $Cache->set('FindBoxCoreSystem', $this->coreDetails, 3600);
  53. } else {
  54. $this->coreDetails = $Cache->get('FindBoxCoreSystem');
  55. }
  56. }
  57. /**
  58. * CallGetAPI make HTTP GET requests
  59. * @param string $URI full path of api url, for example "/1.0.0/index.php/smartesl.blink_special/1.0.0/"
  60. * @param string $Parameters
  61. * @param bool $skipResponse for faster communications, if response not need, you can skip this step
  62. * @return array converted from JSON response of request if present
  63. */
  64. public function CallAPI(string $URI, string $Parameters, bool $skipResponse = false): array
  65. {
  66. $bodyJSON = null;
  67. $auth = "Authorization: Basic " . base64_encode("{$this->Key}:{$this->Pass}") . "\r\n";
  68. $streamfbOS = @stream_socket_client("tcp://{$this->Host}:{$this->Port}", $errno, $errstr, 1);
  69. if (!$streamfbOS) {
  70. return $this->returnCode($errno, $errstr);
  71. } else {
  72. //$requestText = "{$method} {$URI}{$Parameters} HTTP/1.0\r\nHost: {$this->Host}\r\n{$auth}Accept: */*\r\nConnection: close\r\n\r\n";
  73. $requestBodyLength = strlen($Parameters);
  74. $requestText = "POST {$URI} HTTP/1.0\r\nHost: {$this->Host}\r\n{$auth}Accept: */*\r\nContent-Type: application/json\r\nContent-Length: {$requestBodyLength}\r\n\r\n{$Parameters}\r\n";
  75. fwrite($streamfbOS, $requestText);
  76. if (!$skipResponse) {
  77. $r = stream_get_contents($streamfbOS);
  78. list(, $bodyJSON) = explode("\r\n\r\n", $r, 2);
  79. }
  80. fclose($streamfbOS);
  81. }
  82. $bodyJSON = ($bodyJSON == null) ? "{}" : $bodyJSON;
  83. return $this->returnCode(0, $bodyJSON);
  84. }
  85. /**
  86. * Helper for make JSON return code
  87. *
  88. * @param int $errorCode
  89. * @param string $value
  90. * @return array
  91. */
  92. private function returnCode(int $errorCode, string $value): array
  93. {
  94. $val = json_decode($value, true) ?? $value;
  95. return array(
  96. "err" => $errorCode,
  97. "value" => $val);
  98. }
  99. public function AsyncCallAPI(string $URI, string $Parameters, bool $skipResponse = false)
  100. {
  101. $bodyJSON = null;
  102. $auth = "Authorization: Basic " . base64_encode("{$this->Key}:{$this->Pass}") . "\r\n";
  103. $streamfbOS = @stream_socket_client("tcp://{$this->Host}:{$this->Port}", $errno, $errstr, 1);
  104. if (!$streamfbOS) {
  105. return $this->returnCode($errno, $errstr);
  106. } else {
  107. stream_set_blocking($streamfbOS, false);
  108. //$requestText = "{$method} {$URI}{$Parameters} HTTP/1.0\r\nHost: {$this->Host}\r\n{$auth}Accept: */*\r\nConnection: close\r\n\r\n";
  109. $requestBodyLength = strlen($Parameters);
  110. $requestText = "POST {$URI} HTTP/1.0\r\nHost: {$this->Host}\r\n{$auth}Accept: */*\r\nContent-Type: application/json\r\nContent-Length: {$requestBodyLength}\r\n\r\n{$Parameters}\r\n";
  111. fwrite($streamfbOS, $requestText);
  112. if (!$skipResponse) {
  113. while ('' == $r = stream_get_contents($streamfbOS))
  114. // Send(true) interrupt the capp
  115. if ($reason = (yield $r)) {
  116. $r = "\r\n\r\n{\"value\": \"{$reason}\"}";
  117. break;
  118. }
  119. list(, $bodyJSON) = explode("\r\n\r\n", $r, 2);
  120. }
  121. fclose($streamfbOS);
  122. }
  123. $bodyJSON = ($bodyJSON == null) ? "{}" : $bodyJSON;
  124. yield $this->returnCode(0, $bodyJSON);
  125. }
  126. public function BlinkUnassigned()
  127. {
  128. $URI = "/1.0.0/index.php/smartesl.blink_unassigned/2.0.0/";
  129. $this->CallAPI($URI, "", true);
  130. }
  131. /**
  132. * Save products into findbus database
  133. * @param string $barcode
  134. * @param string $name
  135. * @param array $dataFields Custom fields
  136. * @return array
  137. */
  138. public function SaveProducts(string $barcode, string $name, array $dataFields): array
  139. {
  140. $product = json_encode([[[
  141. "barcode" => $barcode,
  142. "name1" => $name,
  143. "datafields" => $dataFields
  144. ]]]);
  145. $URI = "/1.0.0/index.php/saveproducts/1.2.0/";
  146. return $this->CallAPI($URI, $product);
  147. }
  148. /**
  149. * Add label into findbox system and Bind it with a barcode
  150. *
  151. * @param string $labelId
  152. * @param string $barcode
  153. * @return array
  154. */
  155. public function Bind(string $labelId, string $barcode): array
  156. {
  157. $URI = "/1.0.0/index.php/smartesl.bind/1.1.0/";
  158. return $this->CallAPI($URI, "[\"{$labelId}\",\"{$barcode}\"]");
  159. }
  160. /**
  161. * Remove label from findbox system
  162. *
  163. * @param string $labelId
  164. * @return array
  165. */
  166. public function UnBind(string $labelId): array
  167. {
  168. // Retrieve barcode associated to the label
  169. $labelDetails = $this->CallAPI("/1.0.0/index.php/smartesl.getstatus/3.1.0/", "[[\"{$labelId}\"]]");
  170. // If RSSI exist, label it is binded to system
  171. if (isset($labelDetails['value']['value'][$labelId]['rssi'])) {
  172. $barcode = $labelDetails['value']['value'][$labelId]['data']['barcode'];
  173. $ret = $this->CallAPI("/1.0.0/index.php/smartesl.unbind/1.0.0/", "[\"{$labelId}\"]");
  174. if ($ret['err'] != 0)
  175. return $this->returnCode(1, 'Error on unbind');
  176. $ret = $this->CallAPI("/1.0.0/index.php/deleteproducts/1.0.0/", "[\"{$barcode}\"]");
  177. if ($ret['err'] != 0)
  178. return $this->returnCode(1, 'Error on deleteproducts');
  179. // All ok
  180. return $this->returnCode(0, '');
  181. } else
  182. return $this->returnCode(1, 'No label exist on Findbox');
  183. }
  184. /**
  185. * Request all label status to FindBox core.
  186. * If request is forced may require large amount of time to retrieve status directly from labels
  187. *
  188. * @param bool $force true = ask status directly to ESL and not from FindBox Core Cache
  189. *
  190. * @return null|array return array with labelID => status_struct
  191. */
  192. public function GetAllESLStatus($force = false): ?array
  193. {
  194. global $frameworkUtils;
  195. // Retrieve all labels
  196. $URI = '/1.0.0/index.php/smartesl.getstatus/4.0.0/[[],false]';
  197. $ret = $this->CallAPI($URI, "");
  198. if ($ret['err'] != 0)
  199. return null;
  200. $fbOSResponse = $this->getLabelsFromResponse($ret);
  201. if (!$fbOSResponse) {
  202. $frameworkUtils->getLogger()->write('GetAllESLStatus: ' . $ret['value']['value'], 'd/m/Y H:i:s');
  203. return null;
  204. }
  205. $labels = $fbOSResponse;
  206. $this->setTemplatesName($labels);
  207. if (!$force)
  208. return $labels;
  209. // Request status directly to ESL
  210. $eslStatus = [];
  211. foreach($labels as $labelID => $status) {
  212. // Filter for ONLINE labels only
  213. $labelStatus = strtoupper($status['data']['esl_state']);
  214. if ($force && in_array($labelStatus, ['OFFLINE', 'BLOCKED,FC']))
  215. continue;
  216. // Request status to ESL
  217. $URI = "/1.0.0/index.php/smartesl.getstatus/4.0.0/[[\"{$labelID}\"],true]";
  218. $fbOSResponse = $this->getLabelsFromResponse($this->CallAPI($URI, ""));
  219. if ($fbOSResponse != null) {
  220. $eslStatus = array_merge($eslStatus, $fbOSResponse);
  221. }
  222. }
  223. return $eslStatus;
  224. }
  225. /**
  226. * Check the response of FindBoxOS, return an array if no error, else NULL
  227. *
  228. * @param array $fbOS_Response
  229. * @return NULL|array
  230. */
  231. public function getLabelsFromResponse(array $fbOS_Response): ?array
  232. {
  233. // Check if response have field 'value' that is an array
  234. $haveArray = isset($fbOS_Response['value']['value']) && is_array($fbOS_Response['value']['value']);
  235. if (!$haveArray)
  236. return NULL;
  237. return $fbOS_Response['value']['value'];
  238. }
  239. /**
  240. * Add 'data.template_name' to the structure returned by GetStatus
  241. *
  242. * @param array $status
  243. */
  244. private function setTemplatesName(&$status)
  245. {
  246. $tplNameList = [];
  247. $fb_template = $this->CallAPI('/1.0.0/index.php/smartesl.gettemplates/1.0.0/', '[]');
  248. $templates = $this->getLabelsFromResponse($fb_template);
  249. if ($templates != null) {
  250. foreach($templates as $item) {
  251. $tplNameList[$item['template_id']] = $item['name'];
  252. }
  253. }
  254. foreach($status as &$esl) {
  255. if (count($tplNameList) == 0) {
  256. $esl['data']['template_name'] = '';
  257. continue;
  258. }
  259. $tplName = $tplNameList[$esl['data']['template_id']];
  260. $esl['data']['template_name'] = $tplName ?? '{NO_NAME}';
  261. }
  262. }
  263. /**
  264. * Retrieve the SVG file of template
  265. *
  266. * @param $templateID name or id of findbox tremplate
  267. * @return string|null
  268. */
  269. public function GetTemplateImage($templateID)
  270. {
  271. $quote = !is_numeric($templateID) ? '"' : '';
  272. $urlAPI = "/1.0.0/index.php/smartesl.templates.gettemplate/1.0.0/[{$quote}{$templateID}{$quote}]";
  273. $ret = $this->CallAPI($urlAPI, "");
  274. if ($ret['err'] == 0 && $ret['value']['code'] == 0) {
  275. // Update current properties
  276. $status = $ret['value']['value'];
  277. $binZIP = @base64_decode($status['base64_zip']);
  278. if ($binZIP == null)
  279. return null;
  280. $zipName = "fbtpl/{$templateID}.zip";
  281. @mkdir('fbtpl', 0777, true);
  282. file_put_contents($zipName, $binZIP);
  283. $zip = new ZipArchive();
  284. if ($zip->open($zipName) === TRUE) {
  285. $dest = "fbtpl/{$templateID}.svg";
  286. $zip->extractTo('fbtpl/', 'source.svg');
  287. $zip->close();
  288. rename('fbtpl/source.svg', $dest);
  289. unlink("fbtpl/{$templateID}.zip");
  290. $this->fixFindBoxSVG($dest);
  291. return $dest;
  292. }
  293. }
  294. return NULL;
  295. }
  296. private function fixFindBoxSVG($svgPath)
  297. {
  298. global $f3;
  299. $svgTemplate = new \DOMDocument();
  300. $svgTemplate->load($svgPath);
  301. $xlink = $svgTemplate->getElementsByTagName('image');
  302. if ($xlink->length > 0) {
  303. $href = $xlink[0]->getAttribute('xlink:href');
  304. $url = parse_url($href);
  305. $url['host'] = $f3->get('CORE.fb.host');
  306. $imgUrl = "{$url['scheme']}://{$url['host']}{$url['path']}";
  307. $xlink[0]->setAttribute('xlink:href', $imgUrl);
  308. }
  309. $svgNode = $svgTemplate->getElementsByTagName('svg');
  310. if ($svgNode->length > 0) {
  311. $svgNode[0]->removeAttribute('transform');
  312. }
  313. $svgTemplate->save($svgPath);
  314. }
  315. }