398 lines
14 KiB
PHP
398 lines
14 KiB
PHP
<?php
|
|
|
|
namespace Menking\Meshcore;
|
|
|
|
use Exception;
|
|
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;
|
|
|
|
class CoreParser {
|
|
/**
|
|
*
|
|
* @param string $payload
|
|
* @throws Exception
|
|
*/
|
|
public static function parseResponse(string $payload): mixed {
|
|
switch(ord($payload[0])) {
|
|
case CoreProtocol::RESP_CODE_OK:
|
|
return true;
|
|
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 (object)['code'=>CoreProtocol::RESP_CODE_NO_MORE_MESSAGES];
|
|
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 ['code'=>CoreProtocol::PUSH_CODE_MSG_WAITING];
|
|
case CoreProtocol::RESP_CODE_CONTACT_MSG_RECV_V3:
|
|
case CoreProtocol::RESP_CODE_CHANNEL_MSG_RECV_V3:
|
|
case CoreProtocol::RESP_CODE_CHANNEL_MSG_RECV:
|
|
return $payload;
|
|
default:
|
|
echo "Unparsed response: " . \Menking\Meshcore\Util\Debug::hexDump($payload) . "\n";
|
|
return $payload;
|
|
}
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @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 = base64_encode(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 = base64_encode(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->log = 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 ChannelResponse
|
|
*/
|
|
protected static function parseGetChannel(string $payload): ChannelResponse {
|
|
$detail = explode("\0", substr($payload, 2, 32));
|
|
|
|
$data = new ChannelResponse();
|
|
|
|
$data->code = ord($payload[0]);
|
|
$data->channel_idx = ord($payload[1]);
|
|
$data->detail = $detail;
|
|
$data->channel_secret = base64_encode(substr($payload, 34, 16));
|
|
|
|
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;
|
|
}
|
|
|
|
public static function getContact(string $payload) {
|
|
$contact = [
|
|
'code'=>ord($payload[0]),
|
|
'pub_key'=>base64_encode(substr($payload, 1, CoreProtocol::PUB_KEY_SIZE)),
|
|
'type'=>ord($payload[33]),
|
|
'flags'=>ord($payload[34]),
|
|
'out_path_len'=>ord($payload[35]),
|
|
'out_path'=>trim(substr($payload, 36, 64)),
|
|
'contact_name'=>trim(substr($payload, 100, 32)),
|
|
'last_advert_timestamp'=>date('r', unpack('V', substr($payload, 132, 4))[1]),
|
|
'gps_lat'=>unpack('l', substr($payload, 136, 4))[1] / 1000000.0,
|
|
'gps_lon'=>unpack('l', substr($payload, 140, 4))[1] / 1000000.0,
|
|
'lastmod'=>date('r', unpack('V', substr($payload, 144, 4))[1])
|
|
];
|
|
|
|
return $contact;
|
|
}
|
|
|
|
public static function parseAppStart(string $payload) {
|
|
$info = [
|
|
'response_code'=>ord($payload[0]),
|
|
'advert_type'=>ord($payload[1]),
|
|
'tx_power_dbm'=>ord($payload[2]),
|
|
'max_tx_power'=>ord($payload[3]),
|
|
'public_key'=>base64_encode(substr($payload, 4, CoreProtocol::PUB_KEY_SIZE)),
|
|
'lat'=>unpack('l', substr($payload, 36, 4))[1] / 1000000.0,
|
|
'long'=>unpack('l', substr($payload, 40, 4))[1] / 1000000.0,
|
|
'multi_acks'=>ord($payload[44]),
|
|
'advert_loc_policy'=>ord($payload[45]),
|
|
];
|
|
|
|
return $info;
|
|
}
|
|
|
|
public static function parseAdvertPathResponse(string $payload) {
|
|
$info = [
|
|
'code'=>ord($payload[0]),
|
|
'received_timestamp'=>unpack('V', substr($payload, 1, 4))[1],
|
|
'path_len'=>ord($payload[5]),
|
|
'path'=>substr($payload, 6, ord($payload[5]))
|
|
];
|
|
|
|
return $info;
|
|
}
|
|
|
|
public static function parseCodeStatusResponse(string $payload) {
|
|
$info = [
|
|
'code'=>ord($payload[0]),
|
|
'type'=>ord($payload[1]),
|
|
];
|
|
|
|
switch($info['type']) {
|
|
case CoreProtocol::STATS_TYPE_CORE:
|
|
$info['battery_mv'] = unpack('v', substr($payload, 2, 2))[1];
|
|
$info['uptime_secs'] = unpack('V', substr($payload, 4, 4))[1];
|
|
$info['err_flags'] = unpack('v', substr($payload, 8, 2))[1];
|
|
$info['queue_len'] = ord($payload[10]);
|
|
break;
|
|
case CoreProtocol::STATS_TYPE_RADIO:
|
|
$info['noise_floor'] = unpack('v', substr($payload, 2, 2))[1];
|
|
$info['last_rssi'] = (ord($payload[4]) >= 128) ? (ord($payload[4]) - 256): ord($payload[4]);
|
|
$info['last_snr'] = ord($payload[5]);
|
|
$info['tx_air_secs'] = unpack('V', substr($payload, 6, 4))[1];
|
|
$info['rx_air_secs'] = unpack('V', substr($payload, 10, 4))[1];
|
|
break;
|
|
case CoreProtocol::STATS_TYPE_PACKETS:
|
|
$info['recv'] = unpack('V', substr($payload, 2, 4))[1];
|
|
$info['sent'] = unpack('V', substr($payload, 6, 4))[1];
|
|
$info['n_sent_flood'] = unpack('V', substr($payload, 10, 4))[1];
|
|
$info['n_sent_direct'] = unpack('V', substr($payload, 14, 4))[1];
|
|
$info['n_recv_flood'] = unpack('V', substr($payload, 18, 4))[1];
|
|
$info['n_recv_direct'] = unpack('V', substr($payload, 22, 4))[1];
|
|
$info['n_rec_errors'] = unpack('V', substr($payload, 26, 4))[1];
|
|
break;
|
|
}
|
|
|
|
return $info;
|
|
}
|
|
|
|
public static function parseDeviceInfoResponse(string $payload) {
|
|
$info = [
|
|
'code'=>ord($payload[0]),
|
|
'fw_ver_code'=>ord($payload[1]),
|
|
'max_contacts'=>ord($payload[2]) * 2,
|
|
'max_group_channels'=>ord($payload[3]),
|
|
'ble_pin'=>unpack('V', substr($payload, 4, 4))[1],
|
|
'fw_build_date'=>trim(substr($payload, 8, 12)),
|
|
'manufacturer_name'=>trim(substr($payload, 20, 40)),
|
|
'fw_version'=>trim(substr($payload, 60, 20)),
|
|
'client_repeat'=>ord($payload[80]),
|
|
'path_hash_mode'=>ord($payload[81])
|
|
];
|
|
|
|
return $info;
|
|
}
|
|
|
|
public static function parseChannelInfoResponse(string $payload) {
|
|
$info = [
|
|
'code'=>ord($payload[0]),
|
|
'channel_idx'=>ord($payload[1]),
|
|
'channel_name'=>self::readCstring(substr($payload, 2, 32)),
|
|
'channel_secret'=>substr($payload, 34, 16)
|
|
];
|
|
|
|
return $info;
|
|
}
|
|
|
|
public static function parseMessage(string $payload) {
|
|
$data = [
|
|
'code'=>ord($payload[0]),
|
|
'channel_idx'=>ord($payload[1]),
|
|
'path_len'=>ord($payload[2]),
|
|
'type'=>ord($payload[3]),
|
|
'timestamp'=>date('r', unpack('V', substr($payload, 4, 4))[1]),
|
|
'message'=>substr($payload, 8, strlen($payload))
|
|
];
|
|
|
|
return $data;
|
|
}
|
|
|
|
public static function readCstring($str) {
|
|
$x = strpos($str, "\0");
|
|
|
|
if( $x === false ) {
|
|
return null;
|
|
}
|
|
else {
|
|
return trim(substr($str, 0, $x));
|
|
}
|
|
}
|
|
} |