294 lines
11 KiB
PHP
294 lines
11 KiB
PHP
<?php
|
|
|
|
namespace Menking\Meshcore;
|
|
|
|
use RuntimeException;
|
|
|
|
class CoreProtocol {
|
|
/**
|
|
* Writes a frame to the serial port
|
|
*
|
|
* @param string $payload
|
|
* @return void
|
|
*/
|
|
public static function writeFrame($fp, string $payload) {
|
|
if( Environment::getDebug() ) echo "Writing frame:\n" . \Menking\Meshcore\Util\Debug::hexDump($payload) . "\n";
|
|
$len = strlen($payload);
|
|
$frame = chr(0x3C) . pack('v', $len) . $payload; // '<' + uint16 LE + payload
|
|
fwrite($fp, $frame);
|
|
}
|
|
|
|
/**
|
|
* Reads a frame from the serial port
|
|
*
|
|
* @return string
|
|
*/
|
|
public static function readFrame($fp, $timeout_ms = 1000) {
|
|
$read = [$fp];
|
|
$write = null;
|
|
$except = null;
|
|
|
|
$sec = intdiv($timeout_ms, 1000);
|
|
$usec = ($timeout_ms % 1000) * 1000;
|
|
|
|
$changed = stream_select($read, $write, $except, $sec, $usec);
|
|
|
|
if( $changed > 0 ) {
|
|
while (true) {
|
|
$b = self::read_exact($fp, 1);
|
|
if ($b === chr(0x3E)) { // radio -> app
|
|
break;
|
|
}
|
|
}
|
|
|
|
$lenBytes = self::read_exact($fp, 2);
|
|
$len = unpack('v', $lenBytes)[1];
|
|
|
|
$result = self::read_exact($fp, $len);
|
|
if( Environment::getDebug() ) echo "Reading frame:\n" . \Menking\Meshcore\Util\Debug::hexDump($result) . "\n";
|
|
return $result;
|
|
}
|
|
else {
|
|
return chr(0x01) . chr(0xff);
|
|
}
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param mixed $fp
|
|
* @param int $n
|
|
* @return string
|
|
*/
|
|
private static function read_exact($fp, int $n): string {
|
|
$buf = '';
|
|
while (strlen($buf) < $n) {
|
|
$chunk = fread($fp, $n - strlen($buf));
|
|
if ($chunk === false || $chunk === '') {
|
|
usleep(10000);
|
|
continue;
|
|
}
|
|
$buf .= $chunk;
|
|
}
|
|
return $buf;
|
|
}
|
|
|
|
/**
|
|
* Changes the serial port
|
|
*
|
|
* @param string $device
|
|
* @return bool
|
|
* @throws RuntimeException
|
|
*/
|
|
public static function configureTty(string $device): bool {
|
|
if( PHP_OS_FAMILY == 'Linux' ) {
|
|
$cmd = sprintf(
|
|
'stty -F %s %d raw -echo -icanon min 0 time 10',
|
|
escapeshellarg($device),
|
|
115200
|
|
);
|
|
|
|
exec($cmd . ' 2>&1', $output, $code);
|
|
|
|
if ($code !== 0) {
|
|
throw new \RuntimeException(
|
|
"stty failed: " . implode("\n", $output)
|
|
);
|
|
}
|
|
else {
|
|
return true;
|
|
}
|
|
}
|
|
else {
|
|
throw new \RuntimeException("This library hasn't been tested on Windows or MacOS");
|
|
}
|
|
}
|
|
|
|
public static function getErrorText(int $error_code) {
|
|
switch($error_code) {
|
|
case self::ERR_CODE_UNSUPPORTED_CMD:
|
|
return "Unsupported command";
|
|
case self::ERR_CODE_NOT_FOUND:
|
|
return "Not found";
|
|
case self::ERR_CODE_TABLE_FULL:
|
|
return "Table full";
|
|
case self::ERR_CODE_BAD_STATE:
|
|
return "Bad state";
|
|
case self::ERR_CODE_FILE_IO_ERROR:
|
|
return "File I/O error";
|
|
case self::ERR_CODE_ILLEGAL_ARG:
|
|
return "Illegal argument";
|
|
case 0xff:
|
|
return "I/O timeout";
|
|
default:
|
|
return "Unknown error: 0x" . dechex($error_code);
|
|
}
|
|
}
|
|
|
|
public static function getLppType(int $type) {
|
|
switch($type) {
|
|
case self::LPP_TEMP:
|
|
return 'temperature';
|
|
case self::LPP_VOLTAGE:
|
|
return 'voltage';
|
|
case self::LPP_ILLUMINANCE:
|
|
return 'illuminance';
|
|
case self::LPP_PRESENCE:
|
|
return 'presence';
|
|
case self::LPP_HUMIDITY:
|
|
return 'humidity';
|
|
case self::LPP_ACCEL:
|
|
return 'accelerometer';
|
|
case self::LPP_BAROMETER:
|
|
return 'barometer';
|
|
case self::LPP_GYRO:
|
|
return 'gyrometer';
|
|
case self::LPP_GPS:
|
|
return 'gps';
|
|
default:
|
|
return 'unknown';
|
|
}
|
|
}
|
|
|
|
const PUB_KEY_SIZE = 32;
|
|
const MAX_PATH_SIZE = 64;
|
|
|
|
const CMD_APP_START = 1;
|
|
const CMD_SEND_TXT_MSG = 2;
|
|
const CMD_SEND_CHANNEL_TXT_MSG = 3;
|
|
const CMD_GET_CONTACTS = 4;
|
|
const CMD_GET_DEVICE_TIME = 5;
|
|
const CMD_SET_DEVICE_TIME = 6;
|
|
const CMD_SEND_SELF_ADVERT = 7;
|
|
const CMD_SET_ADVERT_NAME = 8;
|
|
const CMD_ADD_UPDATE_CONTACT = 9;
|
|
const CMD_SYNC_NEXT_MESSAGE = 10;
|
|
const CMD_SET_RADIO_PARAMS = 11;
|
|
const CMD_SET_RADIO_TX_POWER = 12;
|
|
const CMD_RESET_PATH = 13;
|
|
const CMD_SET_ADVERT_LATLON = 14;
|
|
const CMD_REMOVE_CONTACT = 15;
|
|
const CMD_SHARE_CONTACT = 16;
|
|
const CMD_EXPORT_CONTACT = 17;
|
|
const CMD_IMPORT_CONTACT = 18;
|
|
const CMD_REBOOT = 19;
|
|
const CMD_GET_BATT_AND_STORAGE = 20; // was CMD_GET_BATTERY_VOLTAGE
|
|
const CMD_SET_TUNING_PARAMS = 21;
|
|
const CMD_DEVICE_QUERY = 22;
|
|
const CMD_EXPORT_PRIVATE_KEY = 23;
|
|
const CMD_IMPORT_PRIVATE_KEY = 24;
|
|
const CMD_SEND_RAW_DATA = 25;
|
|
const CMD_SEND_LOGIN = 26;
|
|
const CMD_SEND_STATUS_REQ = 27;
|
|
const CMD_HAS_CONNECTION = 28;
|
|
const CMD_LOGOUT = 29; // 'Disconnect'
|
|
const CMD_GET_CONTACT_BY_KEY = 30;
|
|
const CMD_GET_CHANNEL = 31;
|
|
const CMD_SET_CHANNEL = 32;
|
|
const CMD_SIGN_START = 33;
|
|
const CMD_SIGN_DATA = 34;
|
|
const CMD_SIGN_FINISH = 35;
|
|
const CMD_SEND_TRACE_PATH = 36;
|
|
const CMD_SET_DEVICE_PIN = 37;
|
|
const CMD_SET_OTHER_PARAMS = 38;
|
|
const CMD_SEND_TELEMETRY_REQ = 39; // can deprecate this
|
|
const CMD_GET_CUSTOM_VARS = 40;
|
|
const CMD_SET_CUSTOM_VAR = 41;
|
|
const CMD_GET_ADVERT_PATH = 42;
|
|
const CMD_GET_TUNING_PARAMS = 43;
|
|
const CMD_SEND_BINARY_REQ = 50;
|
|
const CMD_FACTORY_RESET = 51;
|
|
const CMD_SEND_PATH_DISCOVERY_REQ = 52;
|
|
const CMD_SET_FLOOD_SCOPE_KEY = 54; // v8+
|
|
const CMD_SEND_CONTROL_DATA = 55; // v8+
|
|
const CMD_GET_STATS = 56; // v8+, second byte is stats type
|
|
const CMD_SEND_ANON_REQ = 57;
|
|
const CMD_SET_AUTOADD_CONFIG = 58;
|
|
const CMD_GET_AUTOADD_CONFIG = 59;
|
|
const CMD_GET_ALLOWED_REPEAT_FREQ = 60;
|
|
const CMD_SET_PATH_HASH_MODE = 61;
|
|
const CMD_SEND_CHANNEL_DATA = 62;
|
|
const CMD_SET_DEFAULT_FLOOD_SCOPE = 63;
|
|
const CMD_GET_DEFAULT_FLOOD_SCOPE = 64;
|
|
|
|
const RESP_CODE_OK = 0;
|
|
const RESP_CODE_ERR = 1;
|
|
const RESP_CODE_CONTACTS_START = 2; // first reply to CMD_GET_CONTACTS
|
|
const RESP_CODE_CONTACT = 3; // multiple of these (after CMD_GET_CONTACTS)
|
|
const RESP_CODE_END_OF_CONTACTS = 4; // last reply to CMD_GET_CONTACTS
|
|
const RESP_CODE_SELF_INFO = 5; // reply to CMD_APP_START
|
|
const RESP_CODE_SENT = 6; // reply to CMD_SEND_TXT_MSG
|
|
const RESP_CODE_CONTACT_MSG_RECV = 7; // a reply to CMD_SYNC_NEXT_MESSAGE (ver < 3)
|
|
const RESP_CODE_CHANNEL_MSG_RECV = 8; // a reply to CMD_SYNC_NEXT_MESSAGE (ver < 3)
|
|
const RESP_CODE_CURR_TIME = 9; // a reply to CMD_GET_DEVICE_TIME
|
|
const RESP_CODE_NO_MORE_MESSAGES = 10; // a reply to CMD_SYNC_NEXT_MESSAGE
|
|
const RESP_CODE_EXPORT_CONTACT = 11;
|
|
const RESP_CODE_BATT_AND_STORAGE = 12; // a reply to a CMD_GET_BATT_AND_STORAGE
|
|
const RESP_CODE_DEVICE_INFO = 13; // a reply to CMD_DEVICE_QEURY
|
|
const RESP_CODE_PRIVATE_KEY = 14; // a reply to CMD_EXPORT_PRIVATE_KEY
|
|
const RESP_CODE_DISABLED = 15;
|
|
const RESP_CODE_CONTACT_MSG_RECV_V3 = 16; // a reply to CMD_SYNC_NEXT_MESSAGE (ver >= 3)
|
|
const RESP_CODE_CHANNEL_MSG_RECV_V3 = 17; // a reply to CMD_SYNC_NEXT_MESSAGE (ver >= 3)
|
|
const RESP_CODE_CHANNEL_INFO = 18; // a reply to CMD_GET_CHANNEL
|
|
const RESP_CODE_SIGN_START = 19;
|
|
const RESP_CODE_SIGNATURE = 20;
|
|
const RESP_CODE_CUSTOM_VARS = 21;
|
|
const RESP_CODE_ADVERT_PATH = 22;
|
|
const RESP_CODE_TUNING_PARAMS = 23;
|
|
const RESP_CODE_STATS = 24; // v8+, second byte is stats type
|
|
const RESP_CODE_AUTOADD_CONFIG = 25;
|
|
const RESP_ALLOWED_REPEAT_FREQ = 26;
|
|
const RESP_CODE_CHANNEL_DATA_RECV = 27;
|
|
const RESP_CODE_DEFAULT_FLOOD_SCOPE = 28;
|
|
|
|
const ERR_CODE_UNSUPPORTED_CMD = 1;
|
|
const ERR_CODE_NOT_FOUND = 2;
|
|
const ERR_CODE_TABLE_FULL = 3;
|
|
const ERR_CODE_BAD_STATE = 4;
|
|
const ERR_CODE_FILE_IO_ERROR = 5;
|
|
const ERR_CODE_ILLEGAL_ARG = 6;
|
|
|
|
// these are _pushed_ to client app at any time
|
|
const PUSH_CODE_ADVERT = 0x80;
|
|
const PUSH_CODE_PATH_UPDATED = 0x81;
|
|
const PUSH_CODE_SEND_CONFIRMED = 0x82;
|
|
const PUSH_CODE_MSG_WAITING = 0x83;
|
|
const PUSH_CODE_RAW_DATA = 0x84;
|
|
const PUSH_CODE_LOGIN_SUCCESS = 0x85;
|
|
const PUSH_CODE_LOGIN_FAIL = 0x86;
|
|
const PUSH_CODE_STATUS_RESPONSE = 0x87;
|
|
const PUSH_CODE_LOG_RX_DATA = 0x88;
|
|
const PUSH_CODE_TRACE_DATA = 0x89;
|
|
const PUSH_CODE_NEW_ADVERT = 0x8A;
|
|
const PUSH_CODE_TELEMETRY_RESPONSE = 0x8B;
|
|
const PUSH_CODE_BINARY_RESPONSE = 0x8C;
|
|
const PUSH_CODE_PATH_DISCOVERY_RESPONSE = 0x8D;
|
|
const PUSH_CODE_CONTROL_DATA = 0x8E; // v8+
|
|
const PUSH_CODE_CONTACT_DELETED = 0x8F; // used to notify client app of deleted contact when overwriting oldest
|
|
const PUSH_CODE_CONTACTS_FULL = 0x90; // used to notify client app that contacts storage is full
|
|
|
|
const BINREQ_STATUS = 0x01;
|
|
const BINREQ_KEEP_ALIVE = 0x02;
|
|
const BINREQ_TELEMETRY = 0x03;
|
|
const BINREQ_MMA = 0x04;
|
|
const BINREQ_ACL = 0x05;
|
|
const BINREQ_NEIGHBORS = 0x06;
|
|
|
|
const ANONREQ_REGIONS = 0x01;
|
|
const ANONREQ_OWNER = 0x02;
|
|
const ANONREQ_BASIC = 0x03;
|
|
|
|
const LPP_VOLTAGE = 0x74;
|
|
const LPP_TEMP = 0x67;
|
|
const LPP_ILLUMINANCE = 0x65;
|
|
const LPP_PRESENCE = 0x66;
|
|
const LPP_HUMIDITY = 0x68;
|
|
const LPP_ACCEL = 0x71;
|
|
const LPP_BAROMETER = 0x73;
|
|
const LPP_GYRO = 0x86;
|
|
const LPP_GPS = 0x88;
|
|
|
|
const STATS_TYPE_CORE = 0;
|
|
const STATS_TYPE_RADIO = 1;
|
|
const STATS_TYPE_PACKETS = 2;
|
|
}
|