initial clean commit

This commit is contained in:
Ben Menking
2026-05-07 09:48:36 -04:00
commit d59f61d764
29 changed files with 1333 additions and 0 deletions
+232
View File
@@ -0,0 +1,232 @@
<?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 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_SYNC_NEXT_MESSAGE = 10;
const CMD_SET_RADIO_PARAMS = 11;
const CMD_REBOOT = 19;
const CMD_GET_BATT_AND_STORAGE = 20;
const CMD_DEVICE_QUERY = 22;
const CMD_LOGIN = 26;
const CMD_SEND_STATUS_REQ = 27;
const CMD_HAS_CONNECTION = 28;
const CMD_LOGOUT = 29;
const CMD_GET_CHANNEL = 31;
const CMD_SEND_TELEMETRY_REQ = 39;
const CMD_SEND_BINARY_REQ = 50; // 0x32
const CMD_SEND_ANON_REQ = 57;
const RESP_CODE_OK = 0;
const RESP_CODE_ERR = 1;
const RESP_CODE_CONTACTS_START = 2;
const RESP_CODE_CONTACT = 3;
const RESP_CODE_END_OF_CONTACTS = 4;
const RESP_CODE_SELF_INFO = 5;
const RESP_CODE_SENT = 6;
const RESP_CODE_CURR_TIME = 9;
const RESP_CODE_BATT_AND_STORAGE = 12;
const RESP_CODE_DEVICE_INFO = 13;
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;
}