summaryrefslogtreecommitdiff
path: root/localwebsite/classes
diff options
context:
space:
mode:
authorEvgeny Zinoviev <me@ch1p.io>2022-05-21 01:45:56 +0300
committerEvgeny Zinoviev <me@ch1p.io>2022-05-21 01:45:56 +0300
commit6f53e6e997c9f4647a667bd58b11bc622fa3b47f (patch)
tree226bd65a04576de4f41bf8396af97cd43ca27829 /localwebsite/classes
parent902a3bfbe2c4645e6725fb7583500d1a28026fad (diff)
move local website to homekit's tree
Diffstat (limited to 'localwebsite/classes')
-rw-r--r--localwebsite/classes/E3372.php292
-rw-r--r--localwebsite/classes/GPIORelaydClient.php18
-rw-r--r--localwebsite/classes/InverterdClient.php69
-rw-r--r--localwebsite/classes/MyOpenWrtUtils.php133
-rw-r--r--localwebsite/classes/MySimpleSocketClient.php90
-rw-r--r--localwebsite/classes/Si7021dClient.php31
6 files changed, 633 insertions, 0 deletions
diff --git a/localwebsite/classes/E3372.php b/localwebsite/classes/E3372.php
new file mode 100644
index 0000000..538d387
--- /dev/null
+++ b/localwebsite/classes/E3372.php
@@ -0,0 +1,292 @@
+<?php
+
+class E3372
+{
+
+ const WIFI_CONNECTING = '900';
+ const WIFI_CONNECTED = '901';
+ const WIFI_DISCONNECTED = '902';
+ const WIFI_DISCONNECTING = '903';
+
+ const CRADLE_CONNECTING = '900';
+ const CRADLE_CONNECTED = '901';
+ const CRADLE_DISCONNECTED = '902';
+ const CRADLE_DISCONNECTING = '903';
+ const CRADLE_CONNECTFAILED = '904';
+ const CRADLE_CONNECTSTATUSNULL = '905';
+ const CRANDLE_CONNECTSTATUSERRO = '906';
+
+ const MACRO_EVDO_LEVEL_ZERO = '0';
+ const MACRO_EVDO_LEVEL_ONE = '1';
+ const MACRO_EVDO_LEVEL_TWO = '2';
+ const MACRO_EVDO_LEVEL_THREE = '3';
+ const MACRO_EVDO_LEVEL_FOUR = '4';
+ const MACRO_EVDO_LEVEL_FIVE = '5';
+
+ // CurrentNetworkType
+ const MACRO_NET_WORK_TYPE_NOSERVICE = 0;
+ const MACRO_NET_WORK_TYPE_GSM = 1;
+ const MACRO_NET_WORK_TYPE_GPRS = 2;
+ const MACRO_NET_WORK_TYPE_EDGE = 3;
+ const MACRO_NET_WORK_TYPE_WCDMA = 4;
+ const MACRO_NET_WORK_TYPE_HSDPA = 5;
+ const MACRO_NET_WORK_TYPE_HSUPA = 6;
+ const MACRO_NET_WORK_TYPE_HSPA = 7;
+ const MACRO_NET_WORK_TYPE_TDSCDMA = 8;
+ const MACRO_NET_WORK_TYPE_HSPA_PLUS = 9;
+ const MACRO_NET_WORK_TYPE_EVDO_REV_0 = 10;
+ const MACRO_NET_WORK_TYPE_EVDO_REV_A = 11;
+ const MACRO_NET_WORK_TYPE_EVDO_REV_B = 12;
+ const MACRO_NET_WORK_TYPE_1xRTT = 13;
+ const MACRO_NET_WORK_TYPE_UMB = 14;
+ const MACRO_NET_WORK_TYPE_1xEVDV = 15;
+ const MACRO_NET_WORK_TYPE_3xRTT = 16;
+ const MACRO_NET_WORK_TYPE_HSPA_PLUS_64QAM = 17;
+ const MACRO_NET_WORK_TYPE_HSPA_PLUS_MIMO = 18;
+ const MACRO_NET_WORK_TYPE_LTE = 19;
+ const MACRO_NET_WORK_TYPE_EX_NOSERVICE = 0;
+ const MACRO_NET_WORK_TYPE_EX_GSM = 1;
+ const MACRO_NET_WORK_TYPE_EX_GPRS = 2;
+ const MACRO_NET_WORK_TYPE_EX_EDGE = 3;
+ const MACRO_NET_WORK_TYPE_EX_IS95A = 21;
+ const MACRO_NET_WORK_TYPE_EX_IS95B = 22;
+ const MACRO_NET_WORK_TYPE_EX_CDMA_1x = 23;
+ const MACRO_NET_WORK_TYPE_EX_EVDO_REV_0 = 24;
+ const MACRO_NET_WORK_TYPE_EX_EVDO_REV_A = 25;
+ const MACRO_NET_WORK_TYPE_EX_EVDO_REV_B = 26;
+ const MACRO_NET_WORK_TYPE_EX_HYBRID_CDMA_1x = 27;
+ const MACRO_NET_WORK_TYPE_EX_HYBRID_EVDO_REV_0 = 28;
+ const MACRO_NET_WORK_TYPE_EX_HYBRID_EVDO_REV_A = 29;
+ const MACRO_NET_WORK_TYPE_EX_HYBRID_EVDO_REV_B = 30;
+ const MACRO_NET_WORK_TYPE_EX_EHRPD_REL_0 = 31;
+ const MACRO_NET_WORK_TYPE_EX_EHRPD_REL_A = 32;
+ const MACRO_NET_WORK_TYPE_EX_EHRPD_REL_B = 33;
+ const MACRO_NET_WORK_TYPE_EX_HYBRID_EHRPD_REL_0 = 34;
+ const MACRO_NET_WORK_TYPE_EX_HYBRID_EHRPD_REL_A = 35;
+ const MACRO_NET_WORK_TYPE_EX_HYBRID_EHRPD_REL_B = 36;
+ const MACRO_NET_WORK_TYPE_EX_WCDMA = 41;
+ const MACRO_NET_WORK_TYPE_EX_HSDPA = 42;
+ const MACRO_NET_WORK_TYPE_EX_HSUPA = 43;
+ const MACRO_NET_WORK_TYPE_EX_HSPA = 44;
+ const MACRO_NET_WORK_TYPE_EX_HSPA_PLUS = 45;
+ const MACRO_NET_WORK_TYPE_EX_DC_HSPA_PLUS = 46;
+ const MACRO_NET_WORK_TYPE_EX_TD_SCDMA = 61;
+ const MACRO_NET_WORK_TYPE_EX_TD_HSDPA = 62;
+ const MACRO_NET_WORK_TYPE_EX_TD_HSUPA = 63;
+ const MACRO_NET_WORK_TYPE_EX_TD_HSPA = 64;
+ const MACRO_NET_WORK_TYPE_EX_TD_HSPA_PLUS = 65;
+ const MACRO_NET_WORK_TYPE_EX_802_16E = 81;
+ const MACRO_NET_WORK_TYPE_EX_LTE = 101;
+
+
+ const ERROR_SYSTEM_NO_SUPPORT = 100002;
+ const ERROR_SYSTEM_NO_RIGHTS = 100003;
+ const ERROR_SYSTEM_BUSY = 100004;
+ const ERROR_LOGIN_USERNAME_WRONG = 108001;
+ const ERROR_LOGIN_PASSWORD_WRONG = 108002;
+ const ERROR_LOGIN_ALREADY_LOGIN = 108003;
+ const ERROR_LOGIN_USERNAME_PWD_WRONG = 108006;
+ const ERROR_LOGIN_USERNAME_PWD_ORERRUN = 108007;
+ const ERROR_LOGIN_TOUCH_ALREADY_LOGIN = 108009;
+ const ERROR_VOICE_BUSY = 120001;
+ const ERROR_WRONG_TOKEN = 125001;
+ const ERROR_WRONG_SESSION = 125002;
+ const ERROR_WRONG_SESSION_TOKEN = 125003;
+
+
+ private $host;
+ private $headers = [];
+ private $authorized = false;
+ private $useLegacyTokenAuth = false;
+
+ public function __construct(string $host, bool $legacy_token_auth = false) {
+ $this->host = $host;
+ $this->useLegacyTokenAuth = $legacy_token_auth;
+ }
+
+ public function __call(string $name, array $arguments) {
+ if (startsWith($name, 'get'))
+ $this->auth();
+
+ return call_user_func_array([$this, $name], $arguments);
+ }
+
+ public function auth() {
+ if ($this->authorized)
+ return;
+
+ if (!$this->useLegacyTokenAuth) {
+ $data = $this->request('webserver/SesTokInfo');
+ $this->headers = [
+ 'Cookie: '.$data['SesInfo'],
+ '__RequestVerificationToken: '.$data['TokInfo'],
+ 'Content-Type: text/xml'
+ ];
+ } else {
+ $data = $this->request('webserver/token');
+ $this->headers = [
+ '__RequestVerificationToken: '.$data['token'],
+ 'Content-Type: text/xml'
+ ];
+ }
+ $this->authorized = true;
+ }
+
+
+ // get* methods have to be protected for __call magic to work
+
+ protected function getDeviceInformation() {
+ return $this->request('device/information');
+ }
+
+ protected function getDeviceSignal() {
+ return $this->request('device/signal');
+ }
+
+ protected function getMonitoringStatus() {
+ return $this->request('monitoring/status');
+ }
+
+ protected function getNotifications() {
+ return $this->request('monitoring/check-notifications');
+ }
+
+ protected function getDialupConnection() {
+ return $this->request('dialup/connection');
+ }
+
+ protected function getTrafficStats() {
+ return $this->request('monitoring/traffic-statistics');
+ }
+
+ protected function getSMSCount() {
+ return $this->request('sms/sms-count');
+ }
+
+ protected function getSMSList($page = 1, $count = 20) {
+ $xml = $this->request('sms/sms-list', 'POST', [
+ 'PageIndex' => $page,
+ 'ReadCount' => $count,
+ 'BoxType' => 1,
+ 'SortType' => 0,
+ 'Ascending' => 0,
+ 'UnreadPreferred' => 1
+ ], true);
+ $xml = simplexml_load_string($xml);
+
+ $messages = [];
+ foreach ($xml->Messages->Message as $message) {
+ $messages[] = [
+ 'date' => (string)$message->Date,
+ 'phone' => (string)$message->Phone,
+ 'content' => (string)$message->Content
+ ];
+ }
+ return $messages;
+ }
+
+ private function xmlToAssoc(string $xml): array {
+ $xml = new SimpleXMLElement($xml);
+ $data = [];
+ foreach ($xml as $name => $value) {
+ $data[$name] = (string)$value;
+ }
+ return $data;
+ }
+
+ private function request(string $method, string $http_method = 'GET', array $data = [], bool $return_body = false) {
+ $ch = curl_init();
+ $url = 'http://'.$this->host.'/api/'.$method;
+ curl_setopt($ch, CURLOPT_URL, $url);
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+ if (!empty($this->headers))
+ curl_setopt($ch, CURLOPT_HTTPHEADER, $this->headers);
+ if ($http_method == 'POST') {
+ curl_setopt($ch, CURLOPT_POST, true);
+ if (!empty($data))
+ curl_setopt($ch, CURLOPT_POSTFIELDS, $this->postDataToXML($data));
+ }
+ $body = curl_exec($ch);
+
+ $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
+ if ($code != 200)
+ throw new Exception('e3372 host returned code '.$code);
+
+ curl_close($ch);
+ return $return_body ? $body : $this->xmlToAssoc($body);
+ }
+
+ private function postDataToXML(array $data, int $depth = 1) {
+ if ($depth == 1)
+ return '<?xml version: "1.0" encoding="UTF-8"?>'.$this->postDataToXML(['request' => $data], $depth+1);
+
+ $items = [];
+ foreach ($data as $key => $value) {
+ if (is_array($value))
+ $value = $this->postDataToXML($value, $depth+1);
+ $items[] = "<{$key}>{$value}</{$key}>";
+ }
+
+ return implode('', $items);
+ }
+
+ public static function getNetworkTypeLabel($type): string {
+ switch ((int)$type) {
+ case self::MACRO_NET_WORK_TYPE_NOSERVICE: return 'NOSERVICE';
+ case self::MACRO_NET_WORK_TYPE_GSM: return 'GSM';
+ case self::MACRO_NET_WORK_TYPE_GPRS: return 'GPRS';
+ case self::MACRO_NET_WORK_TYPE_EDGE: return 'EDGE';
+ case self::MACRO_NET_WORK_TYPE_WCDMA: return 'WCDMA';
+ case self::MACRO_NET_WORK_TYPE_HSDPA: return 'HSDPA';
+ case self::MACRO_NET_WORK_TYPE_HSUPA: return 'HSUPA';
+ case self::MACRO_NET_WORK_TYPE_HSPA: return 'HSPA';
+ case self::MACRO_NET_WORK_TYPE_TDSCDMA: return 'TDSCDMA';
+ case self::MACRO_NET_WORK_TYPE_HSPA_PLUS: return 'HSPA_PLUS';
+ case self::MACRO_NET_WORK_TYPE_EVDO_REV_0: return 'EVDO_REV_0';
+ case self::MACRO_NET_WORK_TYPE_EVDO_REV_A: return 'EVDO_REV_A';
+ case self::MACRO_NET_WORK_TYPE_EVDO_REV_B: return 'EVDO_REV_B';
+ case self::MACRO_NET_WORK_TYPE_1xRTT: return '1xRTT';
+ case self::MACRO_NET_WORK_TYPE_UMB: return 'UMB';
+ case self::MACRO_NET_WORK_TYPE_1xEVDV: return '1xEVDV';
+ case self::MACRO_NET_WORK_TYPE_3xRTT: return '3xRTT';
+ case self::MACRO_NET_WORK_TYPE_HSPA_PLUS_64QAM: return 'HSPA_PLUS_64QAM';
+ case self::MACRO_NET_WORK_TYPE_HSPA_PLUS_MIMO: return 'HSPA_PLUS_MIMO';
+ case self::MACRO_NET_WORK_TYPE_LTE: return 'LTE';
+ case self::MACRO_NET_WORK_TYPE_EX_NOSERVICE: return 'NOSERVICE';
+ case self::MACRO_NET_WORK_TYPE_EX_GSM: return 'GSM';
+ case self::MACRO_NET_WORK_TYPE_EX_GPRS: return 'GPRS';
+ case self::MACRO_NET_WORK_TYPE_EX_EDGE: return 'EDGE';
+ case self::MACRO_NET_WORK_TYPE_EX_IS95A: return 'IS95A';
+ case self::MACRO_NET_WORK_TYPE_EX_IS95B: return 'IS95B';
+ case self::MACRO_NET_WORK_TYPE_EX_CDMA_1x: return 'CDMA_1x';
+ case self::MACRO_NET_WORK_TYPE_EX_EVDO_REV_0: return 'EVDO_REV_0';
+ case self::MACRO_NET_WORK_TYPE_EX_EVDO_REV_A: return 'EVDO_REV_A';
+ case self::MACRO_NET_WORK_TYPE_EX_EVDO_REV_B: return 'EVDO_REV_B';
+ case self::MACRO_NET_WORK_TYPE_EX_HYBRID_CDMA_1x: return 'HYBRID_CDMA_1x';
+ case self::MACRO_NET_WORK_TYPE_EX_HYBRID_EVDO_REV_0: return 'HYBRID_EVDO_REV_0';
+ case self::MACRO_NET_WORK_TYPE_EX_HYBRID_EVDO_REV_A: return 'HYBRID_EVDO_REV_A';
+ case self::MACRO_NET_WORK_TYPE_EX_HYBRID_EVDO_REV_B: return 'HYBRID_EVDO_REV_B';
+ case self::MACRO_NET_WORK_TYPE_EX_EHRPD_REL_0: return 'EHRPD_REL_0';
+ case self::MACRO_NET_WORK_TYPE_EX_EHRPD_REL_A: return 'EHRPD_REL_A';
+ case self::MACRO_NET_WORK_TYPE_EX_EHRPD_REL_B: return 'EHRPD_REL_B';
+ case self::MACRO_NET_WORK_TYPE_EX_HYBRID_EHRPD_REL_0: return 'HYBRID_EHRPD_REL_0';
+ case self::MACRO_NET_WORK_TYPE_EX_HYBRID_EHRPD_REL_A: return 'HYBRID_EHRPD_REL_A';
+ case self::MACRO_NET_WORK_TYPE_EX_HYBRID_EHRPD_REL_B: return 'HYBRID_EHRPD_REL_B';
+ case self::MACRO_NET_WORK_TYPE_EX_WCDMA: return 'WCDMA';
+ case self::MACRO_NET_WORK_TYPE_EX_HSDPA: return 'HSDPA';
+ case self::MACRO_NET_WORK_TYPE_EX_HSUPA: return 'HSUPA';
+ case self::MACRO_NET_WORK_TYPE_EX_HSPA: return 'HSPA';
+ case self::MACRO_NET_WORK_TYPE_EX_HSPA_PLUS: return 'HSPA_PLUS';
+ case self::MACRO_NET_WORK_TYPE_EX_DC_HSPA_PLUS: return 'DC_HSPA_PLUS';
+ case self::MACRO_NET_WORK_TYPE_EX_TD_SCDMA: return 'TD_SCDMA';
+ case self::MACRO_NET_WORK_TYPE_EX_TD_HSDPA: return 'TD_HSDPA';
+ case self::MACRO_NET_WORK_TYPE_EX_TD_HSUPA: return 'TD_HSUPA';
+ case self::MACRO_NET_WORK_TYPE_EX_TD_HSPA: return 'TD_HSPA';
+ case self::MACRO_NET_WORK_TYPE_EX_TD_HSPA_PLUS: return 'TD_HSPA_PLUS';
+ case self::MACRO_NET_WORK_TYPE_EX_802_16E: return '802_16E';
+ case self::MACRO_NET_WORK_TYPE_EX_LTE: return 'LTE';
+ default: return '?';
+ }
+ }
+
+}
diff --git a/localwebsite/classes/GPIORelaydClient.php b/localwebsite/classes/GPIORelaydClient.php
new file mode 100644
index 0000000..89c8dc9
--- /dev/null
+++ b/localwebsite/classes/GPIORelaydClient.php
@@ -0,0 +1,18 @@
+<?php
+
+class GPIORelaydClient extends MySimpleSocketClient {
+
+ const STATUS_ON = 'on';
+ const STATUS_OFF = 'off';
+
+ public function setStatus(string $status) {
+ $this->send($status);
+ return $this->recv();
+ }
+
+ public function getStatus() {
+ $this->send('get');
+ return $this->recv();
+ }
+
+} \ No newline at end of file
diff --git a/localwebsite/classes/InverterdClient.php b/localwebsite/classes/InverterdClient.php
new file mode 100644
index 0000000..b68b784
--- /dev/null
+++ b/localwebsite/classes/InverterdClient.php
@@ -0,0 +1,69 @@
+<?php
+
+class InverterdClient extends MySimpleSocketClient {
+
+ /**
+ * @throws Exception
+ */
+ public function setProtocol(int $v): string
+ {
+ $this->send("v $v");
+ return $this->recv();
+ }
+
+ /**
+ * @throws Exception
+ */
+ public function setFormat(string $fmt): string
+ {
+ $this->send("format $fmt");
+ return $this->recv();
+ }
+
+ /**
+ * @throws Exception
+ */
+ public function exec(string $command, array $arguments = []): string
+ {
+ $buf = "exec $command";
+ if (!empty($arguments)) {
+ foreach ($arguments as $arg)
+ $buf .= " $arg";
+ }
+ $this->send($buf);
+ return $this->recv();
+ }
+
+ /**
+ * @throws Exception
+ */
+ public function recv()
+ {
+ $recv_buf = '';
+ $buf = '';
+
+ while (true) {
+ $result = socket_recv($this->sock, $recv_buf, 1024, 0);
+ if ($result === false)
+ throw new Exception(__METHOD__ . ": socket_recv() failed: " . $this->getSocketError());
+
+ // peer disconnected
+ if ($result === 0)
+ break;
+
+ $buf .= $recv_buf;
+ if (endsWith($buf, "\r\n\r\n"))
+ break;
+ }
+
+ $response = explode("\r\n", $buf);
+ $status = array_shift($response);
+ if (!in_array($status, ['ok', 'err']))
+ throw new Exception(__METHOD__.': unexpected status ('.$status.')');
+ if ($status == 'err')
+ throw new Exception(empty($response) ? 'unknown inverterd error' : $response[0]);
+
+ return trim(implode("\r\n", $response));
+ }
+
+} \ No newline at end of file
diff --git a/localwebsite/classes/MyOpenWrtUtils.php b/localwebsite/classes/MyOpenWrtUtils.php
new file mode 100644
index 0000000..6b70dbc
--- /dev/null
+++ b/localwebsite/classes/MyOpenWrtUtils.php
@@ -0,0 +1,133 @@
+<?php
+
+class MyOpenWrtUtils {
+
+ public static function getRoutingTable($table = null) {
+ $arguments = ['route-show'];
+ if ($table)
+ $arguments[] = $table;
+
+ return self::toList(self::run($arguments));
+ }
+
+ public static function getRoutingRules() {
+ return self::toList(self::run(['rule-show']));
+ }
+
+ public static function ipsetList($set_name) {
+ return self::toList(self::run(['ipset-list', $set_name]));
+ }
+
+ public static function ipsetListAll() {
+ global $config;
+
+ $args = ['ipset-list-all'];
+ $args = array_merge($args, array_keys($config['modems']));
+
+ $lines = self::toList(self::run($args));
+
+ $sets = [];
+ $cur_set = null;
+ foreach ($lines as $line) {
+ if (startsWith($line, '>')) {
+ $cur_set = substr($line, 1);
+ if (!isset($sets[$cur_set]))
+ $sets[$cur_set] = [];
+ continue;
+ }
+
+ if (is_null($cur_set)) {
+ debugError(__METHOD__.': cur_set is not set');
+ continue;
+ }
+
+ $sets[$cur_set][] = $line;
+ }
+
+ return $sets;
+ }
+
+ public static function ipsetAdd($set_name, $ip) {
+ return self::run(['ipset-add', $set_name, $ip]);
+ }
+
+ public static function ipsetDel($set_name, $ip) {
+ return self::run(['ipset-del', $set_name, $ip]);
+ }
+
+ public static function getDHCPLeases() {
+ $list = self::toList(self::run(['dhcp-leases']));
+ $list = array_map('self::toDHCPLease', $list);
+ return $list;
+ }
+
+
+ //
+ // http functions
+ //
+
+ private static function run(array $arguments) {
+ $url = self::getLink($arguments);
+
+ $ch = curl_init();
+ curl_setopt($ch, CURLOPT_URL, $url);
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+ $body = curl_exec($ch);
+ $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
+ if ($code != 200)
+ throw new Exception(__METHOD__.': http code '.$code);
+
+ curl_close($ch);
+ return trim($body);
+ }
+
+ private static function getLink($arguments) {
+ global $config;
+
+ $url = 'http://'.$config['openwrt_ip'].'/cgi-bin/luci/command/cfg099944';
+ if (!empty($arguments)) {
+ $arguments = array_map(function($arg) {
+ $arg = str_replace('/', '_', $arg);
+ return urlencode($arg);
+ }, $arguments);
+ $arguments = implode('%20', $arguments);
+
+ $url .= '/';
+ $url .= $arguments;
+ }
+
+ // debugLog($url);
+
+ return $url;
+ }
+
+
+ //
+ // parsing functions
+ //
+
+ private static function toList(string $s): array {
+ if ($s == '')
+ return [];
+ return explode("\n", $s);
+ }
+
+ private static function toDHCPLease(string $s): array {
+ $words = explode(' ', $s);
+ $time = array_shift($words);
+ $mac = array_shift($words);
+ $ip = array_shift($words);
+ array_pop($words);
+ $hostname = trim(implode(' ', $words));
+ if (!$hostname)
+ $hostname = '?';
+ return [
+ 'time' => $time,
+ 'time_s' => date('d M, H:i:s', $time),
+ 'mac' => $mac,
+ 'ip' => $ip,
+ 'hostname' => $hostname
+ ];
+ }
+
+} \ No newline at end of file
diff --git a/localwebsite/classes/MySimpleSocketClient.php b/localwebsite/classes/MySimpleSocketClient.php
new file mode 100644
index 0000000..e59efba
--- /dev/null
+++ b/localwebsite/classes/MySimpleSocketClient.php
@@ -0,0 +1,90 @@
+<?php
+
+class MySimpleSocketClient {
+
+ protected $sock;
+
+ public function __construct(string $host, int $port)
+ {
+ if (($socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP)) === false)
+ throw new Exception("socket_create() failed: ".$this->getSocketError());
+
+ $this->sock = $socket;
+
+ if ((socket_connect($socket, $host, $port)) === false)
+ throw new Exception("socket_connect() failed: ".$this->getSocketError());
+ }
+
+ public function __destruct()
+ {
+ $this->close();
+ }
+
+ /**
+ * @throws Exception
+ */
+ public function send(string $data)
+ {
+ $data .= "\r\n";
+ $remained = strlen($data);
+
+ while ($remained > 0) {
+ $result = socket_write($this->sock, $data);
+ if ($result === false)
+ throw new Exception(__METHOD__ . ": socket_write() failed: ".$this->getSocketError());
+
+ $remained -= $result;
+ if ($remained > 0)
+ $data = substr($data, $result);
+ }
+ }
+
+ /**
+ * @throws Exception
+ */
+ public function recv()
+ {
+ $recv_buf = '';
+ $buf = '';
+
+ while (true) {
+ $result = socket_recv($this->sock, $recv_buf, 1024, 0);
+ if ($result === false)
+ throw new Exception(__METHOD__ . ": socket_recv() failed: " . $this->getSocketError());
+
+ // peer disconnected
+ if ($result === 0)
+ break;
+
+ $buf .= $recv_buf;
+ if (endsWith($buf, "\r\n"))
+ break;
+ }
+
+ return trim($buf);
+ }
+
+ /**
+ * Close connection.
+ */
+ public function close()
+ {
+ if (!$this->sock)
+ return;
+
+ socket_close($this->sock);
+ $this->sock = null;
+ }
+
+ /**
+ * @return string
+ */
+ protected function getSocketError(): string
+ {
+ $sle_args = [];
+ if ($this->sock !== null)
+ $sle_args[] = $this->sock;
+ return socket_strerror(socket_last_error(...$sle_args));
+ }
+
+} \ No newline at end of file
diff --git a/localwebsite/classes/Si7021dClient.php b/localwebsite/classes/Si7021dClient.php
new file mode 100644
index 0000000..b976448
--- /dev/null
+++ b/localwebsite/classes/Si7021dClient.php
@@ -0,0 +1,31 @@
+<?php
+
+class Si7021dClient extends MySimpleSocketClient {
+
+ public string $name;
+ public float $temp;
+ public float $humidity;
+
+ /**
+ * @throws Exception
+ */
+ public function __construct(string $host, int $port, string $name) {
+ parent::__construct($host, $port);
+ $this->name = $name;
+
+ socket_set_timeout($this->sock, 3);
+ }
+
+ public function readSensor(): void {
+ $this->send('read');
+
+ $data = jsonDecode($this->recv());
+
+ $temp = round((float)$data['temp'], 3);
+ $hum = round((float)$data['humidity'], 3);
+
+ $this->temp = $temp;
+ $this->humidity = $hum;
+ }
+
+} \ No newline at end of file