diff --git a/src/CoreParser.php b/src/CoreParser.php index 8da67f8..bcab6cc 100644 --- a/src/CoreParser.php +++ b/src/CoreParser.php @@ -15,14 +15,13 @@ use Menking\Meshcore\Model\ErrorResponse; use Menking\Meshcore\Model\LoginResponse; use Menking\Meshcore\Model\LogReceiveResponse; use Menking\Meshcore\Model\BatteryStorageResponse; -use Menking\Meshcore\Model\CodeStatusTypeCoreResponse; -use Menking\Meshcore\Model\CodeStatusTypePacketResponse; -use Menking\Meshcore\Model\CodeStatusTypeRadioResponse; +use Menking\Meshcore\Model\CodeStatusResponse; use Menking\Meshcore\Model\ContactResponse; use Menking\Meshcore\Model\ContactsFullResponse; use Menking\Meshcore\Model\MessageResponse; use Menking\Meshcore\Model\MessagesWaitingResponse; use Menking\Meshcore\Model\NoMoreMessagesResponse; +use Menking\Meshcore\Model\OkResponse; class CoreParser { /** @@ -33,7 +32,7 @@ class CoreParser { public static function parseResponse(string $payload): mixed { switch(ord($payload[0])) { case CoreProtocol::RESP_CODE_OK: - return true; + return self::parseOk($payload); case CoreProtocol::RESP_CODE_ERR: //throw new \Exception("Protocol error: " . CoreProtocol::getErrorText(ord($payload[1]))); return self::parseError($payload); @@ -82,6 +81,13 @@ class CoreParser { } } + protected static function parseOk(string $payload): OkResponse { + $m = new OkResponse(); + $m->code = ord($payload[0]); + + return $m; + } + protected static function parseMsg(string $payload): mixed { $m = new MessageResponse(); @@ -384,21 +390,23 @@ class CoreParser { * @param string $payload * @return CodeStatusTypeCoreResponse|CodeStatusTypeRadioResponse|CodeStatusTypePacketResponse */ - protected static function parseCodeStatusResponse(string $payload): CodeStatusTypeCoreResponse|CodeStatusTypeRadioResponse|CodeStatusTypePacketResponse { + protected static function parseCodeStatusResponse(string $payload): CodeStatusResponse { $m = null; switch(ord($payload[1])) { case CoreProtocol::STATS_TYPE_CORE: - $m = new CodeStatusTypeCoreResponse(); + $m = new CodeStatusResponse(); $m->code = ord($payload[0]); + $m->type = CodeStatusResponse::RESP_CORE; $m->battery_mv = unpack('v', substr($payload, 2, 2))[1]; $m->uptime_secs = unpack('V', substr($payload, 4, 4))[1]; $m->err_flags = unpack('v', substr($payload, 8, 2))[1]; $m->queue_len = ord($payload[10]); break; case CoreProtocol::STATS_TYPE_RADIO: - $m = new CodeStatusTypeRadioResponse(); + $m = new CodeStatusResponse(); $m->code = ord($payload[0]); + $m->type = CodeStatusResponse::RESP_RADIO; $m->noise_floor = unpack('v', substr($payload, 2, 2))[1]; $m->last_rssi = (ord($payload[4]) >= 128) ? (ord($payload[4]) - 256): ord($payload[4]); $m->last_snr = ord($payload[5]);; @@ -406,8 +414,9 @@ class CoreParser { $m->rx_air_secs = unpack('V', substr($payload, 10, 4))[1]; break; case CoreProtocol::STATS_TYPE_PACKETS: - $m = new CodeStatusTypePacketResponse(); + $m = new CodeStatusResponse(); $m->code = ord($payload[0]); + $m->type = CodeStatusResponse::RESP_PACKET; $m->recv = unpack('V', substr($payload, 2, 4))[1]; $m->sent = unpack('V', substr($payload, 6, 4))[1]; $m->n_sent_flood = unpack('V', substr($payload, 10, 4))[1]; diff --git a/src/Meshcore.php b/src/Meshcore.php index 87c79fe..cc12ad4 100644 --- a/src/Meshcore.php +++ b/src/Meshcore.php @@ -2,24 +2,28 @@ namespace Menking\Meshcore; +use InvalidArgumentException; +use Menking\Meshcore\Model\AdvertPathResponse; +use Menking\Meshcore\Model\AppStartResponse; +use Menking\Meshcore\Model\BatteryStorageResponse; use RuntimeException; use Menking\Meshcore\Model\BinaryResponse; +use Menking\Meshcore\Model\ChannelResponse; +use Menking\Meshcore\Model\CodeSentResponse; +use Menking\Meshcore\Model\CodeStatusResponse; +use Menking\Meshcore\Model\CodeStatusTypeCoreResponse; +use Menking\Meshcore\Model\CodeStatusTypePacketResponse; +use Menking\Meshcore\Model\CodeStatusTypeRadioResponse; +use Menking\Meshcore\Model\CurrentTimeResponse; +use Menking\Meshcore\Model\DeviceInfoResponse; +use Menking\Meshcore\Model\MessageResponse; +use Menking\Meshcore\Model\OkResponse; class Meshcore { private $serial; static private ?Meshcore $instance = null; private $msg_queue = []; - private function __construct() { - CoreProtocol::configureTty(Environment::getDevice()); - - $this->serial = fopen(Environment::getDevice(), 'r+b'); - - if( !$this->serial ) { - throw new RuntimeException("Could not open " . Environment::getDevice()); - } - } - /** * * @return Meshcore @@ -35,28 +39,26 @@ class Meshcore { return self::$instance; } - + /** * * @param string $app_name - * @return mixed + * @return null|AppStartResponse */ - public function appStart(string $app_name): mixed { + public function appStart(string $app_name): ?AppStartResponse { CoreProtocol::writeFrame($this->serial, chr(CoreProtocol::CMD_APP_START) . chr(0x00) . " " . $app_name); - $response = CoreProtocol::readFrame($this->serial); - return CoreParser::parseResponse($response); + return $this->waitForResponse(AppStartResponse::class); } /** * - * @return mixed + * @return null|BatteryStorageResponse */ - public function getBatteryAndStorage(): mixed { + public function getBatteryAndStorage(): ?BatteryStorageResponse { CoreProtocol::writeFrame($this->serial, chr(CoreProtocol::CMD_GET_BATT_AND_STORAGE)); - $response = CoreProtocol::readFrame($this->serial); - - return CoreParser::parseResponse($response); + + return $this->waitForResponse(BatteryStorageResponse::class); } /** @@ -69,36 +71,34 @@ class Meshcore { /** * - * @return mixed + * @return null|DeviceInfoResponse */ - public function getDeviceInfo(): mixed { + public function getDeviceInfo(): ?DeviceInfoResponse { CoreProtocol::writeFrame($this->serial, chr(CoreProtocol::CMD_DEVICE_QUERY) . 0x03); - $response = CoreProtocol::readFrame($this->serial); - - return CoreParser::parseResponse($response); + + return $this->waitForResponse(DeviceInfoResponse::class); } /** * * @param int $channel_idx - * @return mixed + * @return null|ChannelResponse */ - public function getChannel(int $channel_idx): mixed { + public function getChannel(int $channel_idx): ?ChannelResponse { CoreProtocol::writeFrame($this->serial, chr(CoreProtocol::CMD_GET_CHANNEL) . chr($channel_idx)); - $response = CoreProtocol::readFrame($this->serial); - - return CoreParser::parseResponse($response); + + return $this->waitForResponse(ChannelResponse::class); } /** * - * @param float $freq + * @param float $freq Frequency of radio. * @param float $bw * @param int $sf * @param int $cr - * @return mixed + * @return null|OkResponse */ - public function setRadioParams(float $freq, float $bw, int $sf, int $cr): mixed { + public function setRadioParams(float $freq, float $bw, int $sf, int $cr): ?OkResponse { $payload = chr(CoreProtocol::CMD_SET_RADIO_PARAMS) . pack('V', $freq * 1000) . pack('V', $bw * 1000) @@ -107,9 +107,8 @@ class Meshcore { ; CoreProtocol::writeFrame($this->serial, $payload); - $response = CoreProtocol::readFrame($this->serial); - return CoreParser::parseResponse($response); + return $this->waitForResponse(OkResponse::class); } /** @@ -136,181 +135,152 @@ class Meshcore { /** * - * @return mixed + * @return null|MessageResponse */ - public function getNextMessage(): mixed { + public function getNextMessage(): ?MessageResponse { CoreProtocol::writeFrame($this->serial, chr(CoreProtocol::CMD_SYNC_NEXT_MESSAGE)); - $response = CoreProtocol::readFrame($this->serial); - return CoreParser::parseResponse($response); + return $this->waitForResponse(MessageResponse::class); } /** * * @param string $message * @param int $channel_idx - * @return mixed + * @return null|CodeSentResponse */ - public function sendChannelTxtMessage(string $message, int $channel_idx): mixed { + public function sendChannelTxtMessage(string $message, int $channel_idx): ?CodeSentResponse { $payload = chr(CoreProtocol::CMD_SEND_CHANNEL_TXT_MSG) . chr(0x00) . chr($channel_idx) . pack('V', time()) . "$message\0"; - CoreProtocol::writeFrame($this->serial, $payload); - $response = CoreProtocol::readFrame($this->serial); - return CoreParser::parseResponse($response); + return $this->waitForResponse(CodeSentResponse::class); } /** * - * @return mixed + * @return null|CurrentTimeResponse */ - public function getDeviceTime(): mixed { + public function getDeviceTime(): ?CurrentTimeResponse { CoreProtocol::writeFrame($this->serial, chr(CoreProtocol::CMD_GET_DEVICE_TIME)); - $response = CoreProtocol::readFrame($this->serial); - return CoreParser::parseResponse($response); + return $this->waitForResponse(CurrentTimeResponse::class); } /** * - * @return mixed + * @return null|OkResponse */ - public function setDeviceTime(): mixed { + public function setDeviceTime(): ?OkResponse { CoreProtocol::writeFrame($this->serial, chr(CoreProtocol::CMD_SET_DEVICE_TIME) . pack('V', time())); - $response = CoreProtocol::readFrame($this->serial); - return CoreParser::parseResponse($response); + return $this->waitForResponse(OkResponse::class); } /** * * @param string $contact_pk * @param string $password - * @return mixed + * @return null|CodeSentResponse */ - public function login(string $contact_pk, string $password): mixed { + public function login(string $contact_pk, string $password): ?CodeSentResponse { $payload = chr(CoreProtocol::CMD_SEND_LOGIN) . str_pad(base64_decode($contact_pk), CoreProtocol::PUB_KEY_SIZE, "\0") . $password . chr(0x00); CoreProtocol::writeFrame($this->serial, $payload); - $response = CoreProtocol::readFrame($this->serial); - do { - usleep(250000); - $response = CoreProtocol::readFrame($this->serial); - } - while(ord($response[0]) != 0x85 && ord($response[0]) != 0x86 && ord($response[0]) != 0x01); - - return CoreParser::parseResponse($response); + return $this->waitForResponse(CodeSentResponse::class); } /** * * @param string $contact_pk - * @return mixed + * @return null|OkResponse */ - public function disconnect(string $contact_pk): mixed { + public function disconnect(string $contact_pk): ?OkResponse { CoreProtocol::writeFrame($this->serial, chr(CoreProtocol::CMD_LOGOUT) . base64_decode($contact_pk) . "\0"); - $response = CoreProtocol::readFrame($this->serial); - - return CoreParser::parseResponse($response); + + return $this->waitForResponse(OkResponse::class); } /** * * @param string $contact_pk - * @return mixed + * @return null|CodeSentResponse */ - public function statusRequest(string $contact_pk ): mixed { + public function statusRequest(string $contact_pk ): ?CodeSentResponse { $payload = chr(CoreProtocol::CMD_SEND_STATUS_REQ) . base64_decode($contact_pk); CoreProtocol::writeFrame($this->serial, $payload); - $response = CoreProtocol::readFrame($this->serial); - - return CoreParser::parseResponse($response); + + return $this->waitForResponse(CodeSentResponse::class); } /** * * @param string $contact_pk - * @return bool + * @return null|OkResponse */ - public function connected(string $contact_pk): bool { + public function connected(string $contact_pk): ?OkResponse { CoreProtocol::writeFrame($this->serial, chr(CoreProtocol::CMD_HAS_CONNECTION) . base64_decode($contact_pk) . "\0"); - $response = CoreProtocol::readFrame($this->serial); - - return ord($response[0]) != 0x01; + + return $this->waitForResponse(OkResponse::class); } /** * * @param string $contact_pk - * @return mixed + * @return null|OkResponse */ - public function getTelemetryRequest(string $contact_pk): mixed { + public function getTelemetryRequest(string $contact_pk): ?OkResponse { CoreProtocol::writeFrame($this->serial, chr(CoreProtocol::CMD_SEND_TELEMETRY_REQ) . base64_decode($contact_pk)); - $response = CoreProtocol::readFrame($this->serial); - - return CoreParser::parseResponse($response); + + return $this->waitForResponse(OkResponse::class); } /** * * @param string $contact_pk - * @return mixed + * @return null|OkResponse */ - public function sendAnonRequest(string $contact_pk): mixed { + public function sendAnonRequest(string $contact_pk): ?OkResponse { CoreProtocol::writeFrame($this->serial, chr(CoreProtocol::CMD_SEND_ANON_REQ) . base64_decode($contact_pk) . "\0"); - $response = CoreProtocol::readFrame($this->serial); - - return CoreParser::parseResponse($response); + + return $this->waitForResponse(OkResponse::class); } /** * * @param string $request - * @return mixed + * @return null|CodeSentResponse */ - public function sendBinaryRequest(string $request): mixed { + public function sendBinaryRequest(string $request): ?CodeSentResponse { CoreProtocol::writeFrame($this->serial, chr(CoreProtocol::CMD_SEND_BINARY_REQ) . $request); - do { - $response = CoreProtocol::readFrame($this->serial); - - } - while( - ord($response) != CoreProtocol::RESP_CODE_SENT && - ord($response) != CoreProtocol::ERR_CODE_NOT_FOUND && - ord($response) != CoreProtocol::ERR_CODE_NOT_FOUND - ); - - return CoreParser::parseResponse($response); + return $this->waitForResponse(CodeSentResponse::class); } /** * * @param string $contact_pk - * @return mixed + * @return null|AdvertPathResponse */ - public function getAdvertPath(string $contact_pk): mixed { + public function getAdvertPath(string $contact_pk): ?AdvertPathResponse { CoreProtocol::writeFrame($this->serial, chr(CoreProtocol::CMD_GET_ADVERT_PATH) . chr(0x00) . base64_decode($contact_pk)); - $response = CoreProtocol::readFrame($this->serial); - return CoreParser::parseResponse($response); + return $this->waitForResponse(AdvertPathResponse::class); } /** * * @param int $type - * @return mixed + * @return null|CodeStatusResponse */ - public function getStats(int $type): mixed { + public function getStats(int $type): ?CodeStatusResponse { CoreProtocol::writeFrame($this->serial, chr(CoreProtocol::CMD_GET_STATS) . chr($type)); - $response = CoreProtocol::readFrame($this->serial); - return CoreParser::parseResponse($response); + return $this->waitForResponse(CodeStatusResponse::class); } - public function setChannel(int $idx, string $name, string $secret): mixed { + public function setChannel(int $idx, string $name, string $secret): ?OkResponse { echo "secret length: " . strlen($secret) . "\n"; $payload = chr(CoreProtocol::CMD_SET_CHANNEL) . chr($idx) @@ -321,45 +291,72 @@ class Meshcore { echo \Menking\Meshcore\Util\Debug::hexDump($payload) . "\n"; CoreProtocol::writeFrame($this->serial, $payload); - $response = CoreProtocol::readFrame($this->serial); - return CoreParser::parseResponse($response); + return $this->waitForResponse(OkResponse::class); } + /** + * + * @return bool + */ + public function hasQueuedMessages(): bool { + return count($this->msg_queue) > 0; + } + + /** + * + * @return mixed + */ + public function popMessage(): mixed { + return array_pop($this->msg_queue); + } + + /** + * + * @return mixed + */ public function readFrame(): mixed { return CoreProtocol::readFrame($this->serial); } /** - * Poll for messages, returning after timeout or if a message with is received. This function can - * return zero to many results in an array. Not all messages may match . * - * @param string $tag - * @param int $timeout_ms defaults to 5 seconds - * @return array + * @return void + * @throws RuntimeException */ - public function pollForMessage(string $tag, int $timeout_ms = 5000): array { - $mark = time(); - $messages = []; + private function __construct() { + CoreProtocol::configureTty(Environment::getDevice()); - do { - $msg = self::getNextMessage(); + $this->serial = fopen(Environment::getDevice(), 'r+b'); - if( $msg->code != 10) { - $messages[] = $msg; - - if( $msg instanceof BinaryResponse && $msg->tag == $tag ) { - return $messages; - } - } - - usleep(250000); - - $response = $this->readFrame(); + if( !$this->serial ) { + throw new RuntimeException("Could not open " . Environment::getDevice()); } - while( ((time() - $mark) * 1000) < $timeout_ms ); - - return $messages; } + /** + * + * @param string $class_expected + * @return mixed + * @throws InvalidArgumentException + */ + private function waitForResponse(string $class_expected): mixed { + if( !class_exists($class_expected) ) { + throw new InvalidArgumentException("Class $class_expected does not exist"); + } + + $mark = time(); + + do { + $response = CoreProtocol::readFrame($this->serial); + $obj = CoreParser::parseResponse($response); + + if( !is_a($obj, $class_expected) ) { + array_push($this->msg_queue, $obj); + } + } + while((time() - $mark) < 5000 || is_a($obj, $class_expected)); + + return (is_a($obj, $class_expected))?$obj:null; + } } \ No newline at end of file diff --git a/src/Model/CodeStatusResponse.php b/src/Model/CodeStatusResponse.php new file mode 100644 index 0000000..812520d --- /dev/null +++ b/src/Model/CodeStatusResponse.php @@ -0,0 +1,35 @@ +