494 lines
17 KiB
PHP
494 lines
17 KiB
PHP
<?php
|
|
|
|
namespace Menking\Meshcore;
|
|
|
|
use Exception;
|
|
use Menking\Meshcore\Model\AdvertPathResponse;
|
|
use Menking\Meshcore\Model\AdvertResponse;
|
|
use Menking\Meshcore\Model\AppStartResponse;
|
|
use Menking\Meshcore\Model\BinaryResponse;
|
|
use Menking\Meshcore\Model\ChannelResponse;
|
|
use Menking\Meshcore\Model\CodeSentResponse;
|
|
use Menking\Meshcore\Model\CurrentTimeResponse;
|
|
use Menking\Meshcore\Model\DeviceInfoResponse;
|
|
use Menking\Meshcore\Model\ErrorResponse;
|
|
use Menking\Meshcore\Model\LoginResponse;
|
|
use Menking\Meshcore\Model\LogReceiveResponse;
|
|
use Menking\Meshcore\Model\BatteryStorageResponse;
|
|
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 {
|
|
/**
|
|
*
|
|
* @param string $payload
|
|
* @throws Exception
|
|
*/
|
|
public static function parseResponse(string $payload): mixed {
|
|
switch(ord($payload[0])) {
|
|
case CoreProtocol::RESP_CODE_OK:
|
|
return self::parseOk($payload);
|
|
case CoreProtocol::RESP_CODE_ERR:
|
|
//throw new \Exception("Protocol error: " . CoreProtocol::getErrorText(ord($payload[1])));
|
|
return self::parseError($payload);
|
|
case CoreProtocol::RESP_CODE_CONTACT:
|
|
return self::getContact($payload);
|
|
case CoreProtocol::RESP_CODE_SELF_INFO:
|
|
return self::parseAppStart($payload);
|
|
case CoreProtocol::RESP_CODE_SENT:
|
|
return self::parseCodeSent($payload);
|
|
case CoreProtocol::PUSH_CODE_LOGIN_SUCCESS:
|
|
return self::parseLoginSuccess($payload);
|
|
case CoreProtocol::PUSH_CODE_LOG_RX_DATA:
|
|
return self::parseLogRxData($payload);
|
|
case CoreProtocol::PUSH_CODE_LOGIN_FAIL:
|
|
return self::parseLoginFail($payload);
|
|
case CoreProtocol::RESP_CODE_CURR_TIME:
|
|
return self::parseCurrentTime($payload);
|
|
case CoreProtocol::PUSH_CODE_BINARY_RESPONSE:
|
|
return self::parseBinaryResponse($payload);
|
|
case CoreProtocol::RESP_CODE_BATT_AND_STORAGE:
|
|
return self::parseBatteryAndStorage($payload);
|
|
case CoreProtocol::RESP_CODE_NO_MORE_MESSAGES:
|
|
return new NoMoreMessagesResponse();
|
|
case CoreProtocol::RESP_CODE_ADVERT_PATH:
|
|
return self::parseAdvertPathResponse($payload);
|
|
case CoreProtocol::RESP_CODE_STATS:
|
|
return self::parseCodeStatusResponse($payload);
|
|
case CoreProtocol::RESP_CODE_DEVICE_INFO:
|
|
return self::parseDeviceInfoResponse($payload);
|
|
case CoreProtocol::RESP_CODE_CHANNEL_INFO:
|
|
return self::parseChannelInfoResponse($payload);
|
|
case CoreProtocol::PUSH_CODE_MSG_WAITING:
|
|
return new MessagesWaitingResponse();
|
|
case CoreProtocol::RESP_CODE_CONTACT_MSG_RECV_V3:
|
|
case CoreProtocol::RESP_CODE_CHANNEL_MSG_RECV_V3:
|
|
case CoreProtocol::RESP_CODE_CHANNEL_MSG_RECV:
|
|
return self::parseMsg($payload);
|
|
case CoreProtocol::PUSH_CODE_ADVERT:
|
|
case CoreProtocol::PUSH_CODE_NEW_ADVERT:
|
|
return self::parseAdvert($payload);
|
|
case CoreProtocol::PUSH_CODE_CONTACTS_FULL:
|
|
return new ContactsFullResponse();
|
|
default:
|
|
echo "Unparsed response: " . \Menking\Meshcore\Util\Debug::hexDump($payload) . "\n";
|
|
return $payload;
|
|
}
|
|
}
|
|
|
|
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();
|
|
|
|
$idx = 0;
|
|
$m->code = ord($payload[$idx]);
|
|
$idx++;
|
|
|
|
$m->snr = 0;
|
|
|
|
if( $m->code == CoreProtocol::RESP_CODE_CHANNEL_MSG_RECV_V3 ) {
|
|
$m->snr = ord($payload) / 4;
|
|
$idx += 1;
|
|
// two reserved bytes
|
|
$idx += 2;
|
|
}
|
|
|
|
$m->channel_idx = ord($payload[$idx]);
|
|
$m->path_len = ord($payload[$idx+1]);
|
|
$m->type = ord($payload[$idx+2]);
|
|
$m->timestamp = unpack('V', substr($payload, $idx+3, 4))[1];
|
|
$m->message = substr($payload, $idx+7);
|
|
|
|
return $m;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param string $payload
|
|
* @return mixed
|
|
*/
|
|
protected static function parseAdvert(string $payload): mixed {
|
|
$m = new AdvertResponse();
|
|
|
|
$m->code = ord($payload[0]);
|
|
$m->pub_key = self::formatPublicKey(substr($payload, 1, CoreProtocol::PUB_KEY_SIZE));
|
|
|
|
if( strlen($payload) > CoreProtocol::PUB_KEY_SIZE + 2 ) {
|
|
$m->type = ord($payload[33]);
|
|
$m->flags = ord($payload[34]);
|
|
$m->out_path = self::formatPublicKey(substr($payload, 35, CoreProtocol::MAX_PATH_SIZE+1));
|
|
$m->name = rtrim(substr($payload, 100, 32));
|
|
|
|
if( strlen($payload) > 132 ) {
|
|
$m->last_advert_time = unpack('V', substr($payload, 132, 4))[1];
|
|
$m->lat = unpack('V', substr($payload, 136, 4))[1];
|
|
$m->lon = unpack('V', substr($payload, 140, 4))[1];
|
|
$m->last_mod = unpack('V', substr($payload, 144, 4))[1];
|
|
}
|
|
}
|
|
|
|
return $m;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param string $payload
|
|
* @return BinaryResponse
|
|
*/
|
|
protected static function parseBinaryResponse(string $payload): BinaryResponse {
|
|
$data = new BinaryResponse();
|
|
|
|
$data->code = ord($payload[0]);
|
|
$data->reserved = ord($payload[1]);
|
|
$data->tag = unpack('V', substr($payload, 2, 4))[1];
|
|
$data->lpp = [];
|
|
|
|
$lpp = substr($payload, 6);
|
|
// Cayenne LPP proto
|
|
$offset = 0;
|
|
while($offset < strlen($lpp) ) {
|
|
$block = substr($lpp, $offset, 4);
|
|
$q = [
|
|
'channel' => ord($block[0]),
|
|
'type' => ord($block[1]),
|
|
'type_name'=>CoreProtocol::getLppType(ord($block[1])),
|
|
'value' => unpack('n', substr($block, 2, 2))[1]
|
|
];
|
|
|
|
switch($q['type']) {
|
|
case CoreProtocol::LPP_VOLTAGE:
|
|
$q['value'] /= 100;
|
|
break;
|
|
case CoreProtocol::LPP_TEMP:
|
|
$q['value'] /= 10;
|
|
$q['fahrenheit'] = (($q['value'] * 9.0 / 5.0) + 32.0);
|
|
break;
|
|
}
|
|
|
|
$offset += 4;
|
|
|
|
if( $q['channel'] != 0 && $q['type'] != 0 ) {
|
|
$data->lpp[] = $q;
|
|
}
|
|
}
|
|
|
|
return $data;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param string $payload
|
|
* @return ErrorResponse
|
|
*/
|
|
protected static function parseError(string $payload): ErrorResponse {
|
|
$e = new ErrorResponse();
|
|
$e->code = ord($payload[0]);
|
|
$e->error_code = ord($payload[1]);
|
|
$e->error_text = CoreProtocol::getErrorText(ord($payload[1]));
|
|
|
|
return $e;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param string $payload
|
|
* @return CurrentTimeResponse
|
|
*/
|
|
protected static function parseCurrentTime(string $payload): CurrentTimeResponse {
|
|
$data = new CurrentTimeResponse();
|
|
|
|
$data->code = ord($payload[0]);
|
|
$data->current_time = unpack('V', substr($payload, 1, 4))[1];
|
|
|
|
return $data;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param string $payload
|
|
* @return LoginResponse
|
|
*/
|
|
protected static function parseLoginFail(string $payload): LoginResponse {
|
|
$data = new LoginResponse();
|
|
|
|
$data->code = ord($payload[0]);
|
|
$data->status = false;
|
|
$data->is_admin = ord($payload[1]);
|
|
$data->pub_key_prefix = self::formatPublicKey(substr($payload, 2, 6));
|
|
|
|
return $data;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param string $payload
|
|
* @return LoginResponse
|
|
*/
|
|
protected static function parseLoginSuccess(string $payload): LoginResponse {
|
|
$data = new LoginResponse();
|
|
|
|
$data->code = ord($payload[0]);
|
|
$data->status = true;
|
|
$data->is_admin = ord($payload[1]);
|
|
$data->pub_key_prefix = self::formatPublicKey(substr($payload, 2, 6));
|
|
|
|
if( strlen($payload) > 8 ) {
|
|
$data->timestamp = unpack('V', substr($payload, 8, 4))[1];
|
|
$data->acl = ord($payload[12]);
|
|
$data->firmware_version = ord($payload[13]);
|
|
}
|
|
|
|
return $data;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param string $payload
|
|
* @return LogReceiveResponse
|
|
*/
|
|
protected static function parseLogRxData(string $payload): LogReceiveResponse {
|
|
$data = new LogReceiveResponse();
|
|
|
|
$data->code = ord($payload[0]);
|
|
$data->snr = ord($payload[1]) / 4;
|
|
$data->rssi = (ord($payload[2]) >= 128) ? (ord($payload[2]) - 256): ord($payload[2]);
|
|
|
|
//$data->flags = ord($payload[1]);
|
|
//$data->snr = ord($payload[2]) / 4;
|
|
$data->raw = base64_encode(substr($payload, 3, strlen($payload)));
|
|
|
|
return $data;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param string $payload
|
|
* @return CodeSentResponse
|
|
*/
|
|
protected static function parseCodeSent(string $payload): CodeSentResponse {
|
|
$data = new CodeSentResponse();
|
|
|
|
$data->code = ord($payload[0]);
|
|
$data->sent_as_flood = ord($payload[1]);
|
|
$data->tag= unpack('V', substr($payload, 2, 4))[1];
|
|
$data->est_timeout = unpack('V', substr($payload, 6, 4))[1];
|
|
|
|
return $data;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param string $payload
|
|
* @return DeviceInfoResponse
|
|
*/
|
|
protected static function parseDeviceInfo(string $payload): DeviceInfoResponse {
|
|
$data = new DeviceInfoResponse();
|
|
|
|
$data->code = ord($payload[0]);
|
|
$data->firmware_version = ord($payload[1]);
|
|
$data->max_contacts_raw = ord($payload[2]);
|
|
$data->max_channels = ord($payload[3]);
|
|
$data->ble_pin = unpack('v', substr($payload, 4, 4))[1];
|
|
$data->firmware_build = trim(substr($payload, 8, 12));
|
|
$data->model = trim(substr($payload, 20, 40));
|
|
$data->version = trim(substr($payload, 60, 20));
|
|
$data->client_repeat_enabled = ord($payload[80]);
|
|
$data->path_hash_mode = ord($payload[81]);
|
|
|
|
return $data;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param string $payload
|
|
* @return BatteryStorageResponse
|
|
*/
|
|
protected static function parseBatteryAndStorage(string $payload): BatteryStorageResponse {
|
|
$data = new BatteryStorageResponse();
|
|
|
|
$data->code = ord($payload[0]);
|
|
$data->battery_millivolt = unpack('v', substr($payload, 1, 2))[1];
|
|
$data->storage_used = unpack('V', substr($payload, 3, 4))[1];
|
|
$data->storage_total = unpack('V', substr($payload, 7, 4))[1];
|
|
|
|
return $data;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param string $payload
|
|
* @return ContactResponse
|
|
*/
|
|
public static function getContact(string $payload): ContactResponse {
|
|
$m = new ContactResponse();
|
|
|
|
$m->code = ord($payload[0]);
|
|
$m->pub_key = self::formatPublicKey(substr($payload, 1, CoreProtocol::PUB_KEY_SIZE));
|
|
$m->type = ord($payload[33]);
|
|
$m->flags = ord($payload[34]);
|
|
$m->out_path_len = ord($payload[35]);
|
|
$m->out_path = self::formatPublicKey(substr($payload, 36, 64));
|
|
$m->name = trim(substr($payload, 100, 32), ' ');
|
|
$m->last_advert_timestamp = date('r', unpack('V', substr($payload, 132, 4))[1]);
|
|
$m->lat = unpack('l', substr($payload, 136, 4))[1] / 1000000.0;
|
|
$m->lon = unpack('l', substr($payload, 140, 4))[1] / 1000000.0;
|
|
$m->lastmod = date('r', unpack('V', substr($payload, 144, 4))[1]);
|
|
|
|
return $m;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param string $payload
|
|
* @return AppStartResponse
|
|
*/
|
|
protected static function parseAppStart(string $payload): AppStartResponse {
|
|
$m = new AppStartResponse();
|
|
|
|
$m->code = ord($payload[0]);
|
|
$m->advert_type = ord($payload[1]);
|
|
$m->tx_power_dbm = ord($payload[2]);
|
|
$m->max_tx_power = ord($payload[3]);
|
|
$m->pub_key = self::formatPublicKey(substr($payload, 4, CoreProtocol::PUB_KEY_SIZE));
|
|
$m->lat = unpack('l', substr($payload, 36, 4))[1] / 1000000.0;
|
|
$m->lon = unpack('l', substr($payload, 40, 4))[1] / 1000000.0;
|
|
$m->multi_acks = ord($payload[44]);
|
|
$m->advert_loc_policy = ord($payload[45]);
|
|
|
|
return $m;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param string $payload
|
|
* @return AdvertPathResponse
|
|
*/
|
|
protected static function parseAdvertPathResponse(string $payload): AdvertPathResponse {
|
|
$m = new AdvertPathResponse();
|
|
|
|
$m->code = ord($payload[0]);
|
|
$m->received_timestamp = unpack('V', substr($payload, 1, 4))[1];
|
|
$m->path_len = ord($payload[5]);
|
|
$m->path = substr($payload, 6, ord($payload[5]));
|
|
|
|
return $m;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param string $payload
|
|
* @return CodeStatusTypeCoreResponse|CodeStatusTypeRadioResponse|CodeStatusTypePacketResponse
|
|
*/
|
|
protected static function parseCodeStatusResponse(string $payload): CodeStatusResponse {
|
|
$m = null;
|
|
|
|
switch(ord($payload[1])) {
|
|
case CoreProtocol::STATS_TYPE_CORE:
|
|
$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 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]);;
|
|
$m->tx_air_secs = unpack('V', substr($payload, 6, 4))[1];
|
|
$m->rx_air_secs = unpack('V', substr($payload, 10, 4))[1];
|
|
break;
|
|
case CoreProtocol::STATS_TYPE_PACKETS:
|
|
$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];
|
|
$m->n_sent_direct = unpack('V', substr($payload, 14, 4))[1];
|
|
$m->n_recv_flood = unpack('V', substr($payload, 18, 4))[1];
|
|
$m->n_recv_direct = unpack('V', substr($payload, 22, 4))[1];
|
|
$m->n_recv_errors = unpack('V', substr($payload, 26, 4))[1];
|
|
break;
|
|
}
|
|
|
|
return $m;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param string $payload
|
|
* @return DeviceInfoResponse
|
|
*/
|
|
protected static function parseDeviceInfoResponse(string $payload): DeviceInfoResponse {
|
|
$m = new DeviceInfoResponse();
|
|
$m->code = ord($payload[0]);
|
|
$m->firmware_version = ord($payload[1]);
|
|
$m->max_contacts_raw = ord($payload[2]) * 2;
|
|
$m->max_channels = ord($payload[3]);
|
|
$m->ble_pin = unpack('V', substr($payload, 4, 4))[1];
|
|
$m->firmware_build = trim(substr($payload, 8, 12));
|
|
$m->model = trim(substr($payload, 20, 40));
|
|
$m->version =trim(substr($payload, 60, 20));
|
|
$m->client_repeat_enabled = ord($payload[80]);
|
|
$m->path_hash_mode = ord($payload[81]);
|
|
|
|
return $m;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param string $payload
|
|
* @return ChannelResponse
|
|
*/
|
|
protected static function parseChannelInfoResponse(string $payload): ChannelResponse {
|
|
$m = new ChannelResponse();
|
|
|
|
$m->code = ord($payload[0]);
|
|
$m->channel_idx = ord($payload[1]);
|
|
$m->channel_name = self::readCstring(substr($payload, 2, 32));
|
|
$m->channel_secret = substr($payload, 34, 16);
|
|
|
|
return $m;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param mixed $str
|
|
* @return null|string
|
|
*/
|
|
public static function readCstring($str) {
|
|
$x = strpos($str, "\0");
|
|
|
|
if( $x === false ) {
|
|
return null;
|
|
}
|
|
else {
|
|
return trim(substr($str, 0, $x));
|
|
}
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param string $pubkey
|
|
* @return string
|
|
*/
|
|
public static function formatPublicKey(string $pubkey): string {
|
|
return bin2hex($pubkey);
|
|
}
|
|
} |