initial clean commit
This commit is contained in:
@@ -0,0 +1,2 @@
|
|||||||
|
vendor/*
|
||||||
|
.env
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"name": "menking/meshcore-php",
|
||||||
|
"description": "A library for communicating with Meshcore USB companion firmware",
|
||||||
|
"type": "library",
|
||||||
|
"license": "MIT",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Menking\\Meshcore\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Ben Menking",
|
||||||
|
"email": "ben@menking.net"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"require-dev": {
|
||||||
|
"phpstan/phpstan": "^2.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
Generated
+72
@@ -0,0 +1,72 @@
|
|||||||
|
{
|
||||||
|
"_readme": [
|
||||||
|
"This file locks the dependencies of your project to a known state",
|
||||||
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
|
"This file is @generated automatically"
|
||||||
|
],
|
||||||
|
"content-hash": "13f9192146b31c36272e50ca9ea50814",
|
||||||
|
"packages": [],
|
||||||
|
"packages-dev": [
|
||||||
|
{
|
||||||
|
"name": "phpstan/phpstan",
|
||||||
|
"version": "2.1.54",
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/8be50c3992107dc837b17da4d140fbbdf9a5c5bd",
|
||||||
|
"reference": "8be50c3992107dc837b17da4d140fbbdf9a5c5bd",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": "^7.4|^8.0"
|
||||||
|
},
|
||||||
|
"conflict": {
|
||||||
|
"phpstan/phpstan-shim": "*"
|
||||||
|
},
|
||||||
|
"bin": [
|
||||||
|
"phpstan",
|
||||||
|
"phpstan.phar"
|
||||||
|
],
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"files": [
|
||||||
|
"bootstrap.php"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"description": "PHPStan - PHP Static Analysis Tool",
|
||||||
|
"keywords": [
|
||||||
|
"dev",
|
||||||
|
"static analysis"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"docs": "https://phpstan.org/user-guide/getting-started",
|
||||||
|
"forum": "https://github.com/phpstan/phpstan/discussions",
|
||||||
|
"issues": "https://github.com/phpstan/phpstan/issues",
|
||||||
|
"security": "https://github.com/phpstan/phpstan/security/policy",
|
||||||
|
"source": "https://github.com/phpstan/phpstan-src"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://github.com/ondrejmirtes",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/phpstan",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2026-04-29T13:31:09+00:00"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"aliases": [],
|
||||||
|
"minimum-stability": "stable",
|
||||||
|
"stability-flags": [],
|
||||||
|
"prefer-stable": false,
|
||||||
|
"prefer-lowest": false,
|
||||||
|
"platform": [],
|
||||||
|
"platform-dev": [],
|
||||||
|
"plugin-api-version": "2.6.0"
|
||||||
|
}
|
||||||
@@ -0,0 +1,297 @@
|
|||||||
|
<?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);
|
||||||
|
default:
|
||||||
|
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]);
|
||||||
|
$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 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Menking\Meshcore;
|
||||||
|
|
||||||
|
class Environment {
|
||||||
|
private static ?string $device = null;
|
||||||
|
private static bool $debug = false;
|
||||||
|
|
||||||
|
public static function configure(string $device, bool $debug = false): void {
|
||||||
|
self::$device = $device;
|
||||||
|
self::$debug = $debug;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getDevice(): string { return self::$device; }
|
||||||
|
|
||||||
|
public static function getDebug(): bool { return self::$debug; }
|
||||||
|
|
||||||
|
public static function setDebug(bool $debug) { self::$debug = $debug; }
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,246 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Menking\Meshcore;
|
||||||
|
|
||||||
|
use Menking\Meshcore\Model\BatteryStorageResponse;
|
||||||
|
use Menking\Meshcore\Model\BinaryResponse;
|
||||||
|
use Menking\Meshcore\Model\DeviceInfoResponse;
|
||||||
|
use Menking\Meshcore\Model\Response;
|
||||||
|
use RuntimeException;
|
||||||
|
|
||||||
|
class Meshcore {
|
||||||
|
private $serial;
|
||||||
|
static private ?Meshcore $instance = null;
|
||||||
|
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getInstance(): Meshcore {
|
||||||
|
if( self::$instance == null && Environment::getDevice() == null ) {
|
||||||
|
throw new RuntimeException("Please initialize Meshcore by calling Environment::configure()");
|
||||||
|
}
|
||||||
|
else if( self::$instance == null ) {
|
||||||
|
self::$instance = new Meshcore();
|
||||||
|
}
|
||||||
|
|
||||||
|
return self::$instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the connection. Needed for most commands to succeed.
|
||||||
|
*
|
||||||
|
* @return true|array{code: int, pub_key: string, type: int, flags: int, out_path_len: int, out_path: string, contact_name: string, last_advert_timestamp: string, gps_lat: int|float, gps_lon: int|float, lastmod: string}|array{code: int, sent_flood: int, pending_login: mixed, est_timeout: mixed}|string
|
||||||
|
*/
|
||||||
|
public function appStart(string $app_name) {
|
||||||
|
CoreProtocol::writeFrame($this->serial, chr(CoreProtocol::CMD_APP_START) . chr(0x00) . " " . $app_name);
|
||||||
|
$response = CoreProtocol::readFrame($this->serial);
|
||||||
|
|
||||||
|
return CoreParser::parseResponse($response);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @return BatteryStorageResponse
|
||||||
|
*/
|
||||||
|
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 void
|
||||||
|
*/
|
||||||
|
public function reboot() {
|
||||||
|
CoreProtocol::writeFrame($this->serial, chr(CoreProtocol::CMD_REBOOT) . "reboot");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @return DeviceInfoResponse
|
||||||
|
*/
|
||||||
|
public function getDeviceInfo(): DeviceInfoResponse {
|
||||||
|
CoreProtocol::writeFrame($this->serial, chr(CoreProtocol::CMD_DEVICE_QUERY) . 0x03);
|
||||||
|
$response = CoreProtocol::readFrame($this->serial);
|
||||||
|
|
||||||
|
return CoreParser::parseResponse($response);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param int $channel_idx
|
||||||
|
* @return array{response_code: int, channel_idx: int, channel: string[], channel_secret: string}
|
||||||
|
*/
|
||||||
|
public function getChannel(int $channel_idx) {
|
||||||
|
CoreProtocol::writeFrame($this->serial, chr(CoreProtocol::CMD_GET_CHANNEL) . chr($channel_idx));
|
||||||
|
$response = CoreProtocol::readFrame($this->serial);
|
||||||
|
|
||||||
|
return CoreParser::parseResponse($response);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setRadioParams(float $freq, float $bw, int $sf, int $cr) {
|
||||||
|
/*
|
||||||
|
$params = [
|
||||||
|
'freq'=>910.525,
|
||||||
|
'bw'=>62.5,
|
||||||
|
'sf'=>7,
|
||||||
|
'cr'=>5,
|
||||||
|
];
|
||||||
|
*/
|
||||||
|
|
||||||
|
$payload = chr(CoreProtocol::CMD_SET_RADIO_PARAMS)
|
||||||
|
. pack('V', $freq * 1000)
|
||||||
|
. pack('V', $bw * 1000)
|
||||||
|
. chr($sf)
|
||||||
|
. chr($cr)
|
||||||
|
;
|
||||||
|
|
||||||
|
CoreProtocol::writeFrame($this->serial, $payload);
|
||||||
|
$response = CoreProtocol::readFrame($this->serial);
|
||||||
|
|
||||||
|
return CoreParser::parseResponse($response);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getContacts(): array {
|
||||||
|
CoreProtocol::writeFrame($this->serial, chr(CoreProtocol::CMD_GET_CONTACTS));
|
||||||
|
$response = CoreProtocol::readFrame($this->serial);
|
||||||
|
$contacts = [];
|
||||||
|
|
||||||
|
if( ord($response[0]) == 0x02 ) {
|
||||||
|
do {
|
||||||
|
$response = CoreProtocol::readFrame($this->serial);
|
||||||
|
if( ord($response[0]) == 0x03 ) {
|
||||||
|
$contacts[] = CoreParser::getContact($response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while(ord($response[0]) != 0x04);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $contacts;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getNextMessage(?string $tag = null) {
|
||||||
|
CoreProtocol::writeFrame($this->serial, chr(CoreProtocol::CMD_SYNC_NEXT_MESSAGE));
|
||||||
|
$response = CoreProtocol::readFrame($this->serial);
|
||||||
|
|
||||||
|
if( ord($response[0]) == 0x0a ) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return CoreParser::parseResponse($response);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function sendChannelTxtMessage(string $message, int $channel_idx): bool {
|
||||||
|
$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);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDeviceTime(){
|
||||||
|
CoreProtocol::writeFrame($this->serial, chr(CoreProtocol::CMD_GET_DEVICE_TIME));
|
||||||
|
$response = CoreProtocol::readFrame($this->serial);
|
||||||
|
|
||||||
|
return CoreParser::parseResponse($response);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setDeviceTime() {
|
||||||
|
CoreProtocol::writeFrame($this->serial, chr(CoreProtocol::CMD_SET_DEVICE_TIME) . pack('V', time()));
|
||||||
|
$response = CoreProtocol::readFrame($this->serial);
|
||||||
|
|
||||||
|
return CoreParser::parseResponse($response);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param string $contact_pk
|
||||||
|
* @param string $password
|
||||||
|
* @return true|array{code: int, pub_key: string, type: int, flags: int, out_path_len: int, out_path: string, contact_name: string, last_advert_timestamp: string, gps_lat: int|float, gps_lon: int|float, lastmod: string}|array{code: int, sent_flood: int, pending_login: mixed, est_timeout: mixed}|string
|
||||||
|
*/
|
||||||
|
public function login(string $contact_pk, string $password) {
|
||||||
|
$payload = chr(CoreProtocol::CMD_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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param string $contact_pk
|
||||||
|
* @return true|array{code: int, pub_key: string, type: int, flags: int, out_path_len: int, out_path: string, contact_name: string, last_advert_timestamp: string, gps_lat: int|float, gps_lon: int|float, lastmod: string}|array{code: int, sent_flood: int, pending_login: mixed, est_timeout: mixed}|string
|
||||||
|
*/
|
||||||
|
public function disconnect(string $contact_pk) {
|
||||||
|
CoreProtocol::writeFrame($this->serial, chr(CoreProtocol::CMD_LOGOUT) . base64_decode($contact_pk) . "\0");
|
||||||
|
$response = CoreProtocol::readFrame($this->serial);
|
||||||
|
|
||||||
|
return CoreParser::parseResponse($response);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function statusRequest(string $contact_pk ) {
|
||||||
|
$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);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function connected(string $contact_pk): bool {
|
||||||
|
CoreProtocol::writeFrame($this->serial, chr(CoreProtocol::CMD_HAS_CONNECTION) . base64_decode($contact_pk) . "\0");
|
||||||
|
$response = CoreProtocol::readFrame($this->serial);
|
||||||
|
|
||||||
|
return ord($response[0]) != 0x01;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTelemetryRequest(string $contact_pk) {
|
||||||
|
CoreProtocol::writeFrame($this->serial, chr(CoreProtocol::CMD_SEND_TELEMETRY_REQ) . base64_decode($contact_pk));
|
||||||
|
$response = CoreProtocol::readFrame($this->serial);
|
||||||
|
|
||||||
|
return CoreParser::parseResponse($response);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function sendAnonRequest(string $contact_pk) {
|
||||||
|
CoreProtocol::writeFrame($this->serial, chr(CoreProtocol::CMD_SEND_ANON_REQ) . base64_decode($contact_pk) . "\0");
|
||||||
|
$response = CoreProtocol::readFrame($this->serial);
|
||||||
|
|
||||||
|
return CoreParser::parseResponse($response);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function sendBinaryRequest(string $request): BinaryResponse {
|
||||||
|
CoreProtocol::writeFrame($this->serial, chr(CoreProtocol::CMD_SEND_BINARY_REQ) . $request);
|
||||||
|
$response = CoreProtocol::readFrame($this->serial);
|
||||||
|
|
||||||
|
return CoreParser::parseResponse($response);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function poll(int $timeout_ms = 5000): void {
|
||||||
|
$mark = time();
|
||||||
|
|
||||||
|
do {
|
||||||
|
$response = CoreProtocol::readFrame($this->serial);
|
||||||
|
echo json_encode(CoreParser::parseResponse($response));
|
||||||
|
}
|
||||||
|
while( (time() - $mark) < $timeout_ms ) ; //ord($response[0]) == 0x01 && ord($response[1]) == 0xff);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Menking\Meshcore\Model;
|
||||||
|
|
||||||
|
class BatteryStorageResponse extends Response {
|
||||||
|
public int $code;
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @var int $battery_millivolt
|
||||||
|
*/
|
||||||
|
public int $battery_millivolt; // two bytes
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
public int $storage_used; // 4 bytes
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
public int $storage_total; // 4 bytes
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Menking\Meshcore\Model;
|
||||||
|
|
||||||
|
class BinaryResponse extends Response {
|
||||||
|
public int $code;
|
||||||
|
public int $reserved;
|
||||||
|
public int $tag;
|
||||||
|
public array $lpp = [];
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Menking\Meshcore\Model;
|
||||||
|
|
||||||
|
class ChannelResponse extends Response {
|
||||||
|
public int $code;
|
||||||
|
public int $channel_idx;
|
||||||
|
public array $detail;
|
||||||
|
public string $channel_secret;
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Menking\Meshcore\Model;
|
||||||
|
|
||||||
|
class CodeSentResponse extends Response {
|
||||||
|
public int $code;
|
||||||
|
public int $sent_as_flood;
|
||||||
|
public int $tag;
|
||||||
|
public string $est_timeout;
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Menking\Meshcore\Model;
|
||||||
|
|
||||||
|
class CurrentTimeResponse extends Response {
|
||||||
|
public int $code;
|
||||||
|
public int $current_time;
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Menking\Meshcore\Model;
|
||||||
|
|
||||||
|
class DeviceInfoResponse extends Response {
|
||||||
|
public int $code;
|
||||||
|
public int $firmware_version;
|
||||||
|
public int $max_contacts_raw;
|
||||||
|
public int $max_channels;
|
||||||
|
public int $ble_pin;
|
||||||
|
public string $firmware_build;
|
||||||
|
public string $model;
|
||||||
|
public string $version;
|
||||||
|
public int $client_repeat_enabled;
|
||||||
|
public int $path_hash_mode;
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Menking\Meshcore\Model;
|
||||||
|
|
||||||
|
class ErrorResponse extends Response {
|
||||||
|
public int $code;
|
||||||
|
public int $error_code;
|
||||||
|
public string $error_text;
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Menking\Meshcore\Model;
|
||||||
|
|
||||||
|
class LogReceiveResponse extends Response {
|
||||||
|
public int $code;
|
||||||
|
public int $snr;
|
||||||
|
public int $rssi;
|
||||||
|
public string $log;
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Menking\Meshcore\Model;
|
||||||
|
|
||||||
|
class LoginResponse extends Response {
|
||||||
|
public int $code;
|
||||||
|
public bool $status;
|
||||||
|
public int $is_admin;
|
||||||
|
public string $pub_key_prefix;
|
||||||
|
public int $timestamp;
|
||||||
|
public int $acl;
|
||||||
|
public int $firmware_version;
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Menking\Meshcore\Model;
|
||||||
|
|
||||||
|
class Response {
|
||||||
|
public function __toString() {
|
||||||
|
$output = '';
|
||||||
|
|
||||||
|
$vars = get_class_vars(static::class);
|
||||||
|
|
||||||
|
$output = "Class: " . static::class. "\n";
|
||||||
|
|
||||||
|
$output .= print_r($vars, true);
|
||||||
|
|
||||||
|
return $output;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Menking\Meshcore\Util;
|
||||||
|
|
||||||
|
class Debug {
|
||||||
|
public static function hexDump(string $data, int $width = 16): string {
|
||||||
|
$output = '';
|
||||||
|
$len = strlen($data);
|
||||||
|
|
||||||
|
for ($i = 0; $i < $len; $i += $width) {
|
||||||
|
$chunk = substr($data, $i, $width);
|
||||||
|
|
||||||
|
$hex = '';
|
||||||
|
$ascii = '';
|
||||||
|
|
||||||
|
for ($j = 0; $j < strlen($chunk); $j++) {
|
||||||
|
$byte = ord($chunk[$j]);
|
||||||
|
|
||||||
|
// Hex column
|
||||||
|
$hex .= sprintf('%02X ', $byte);
|
||||||
|
|
||||||
|
// ASCII column (printable range 32–126)
|
||||||
|
$ascii .= ($byte >= 32 && $byte <= 126)
|
||||||
|
? chr($byte)
|
||||||
|
: ' ';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pad last line so ASCII lines up
|
||||||
|
$hex = str_pad($hex, $width * 3);
|
||||||
|
|
||||||
|
$output .= $hex . ' | ' . $ascii . PHP_EOL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $output;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Menking\Meshcore\Environment;
|
||||||
|
use Menking\Meshcore\Meshcore;
|
||||||
|
|
||||||
|
require(__DIR__ . '/../vendor/autoload.php');
|
||||||
|
|
||||||
|
if( !isset($argv[1]) ) die("{$argv[0]} <port>\n");
|
||||||
|
|
||||||
|
Environment::configure($argv[1]);
|
||||||
|
|
||||||
|
$mc = Meshcore::getInstance();
|
||||||
|
|
||||||
|
$resp = $mc->appStart("battery storage");
|
||||||
|
|
||||||
|
$resp = $mc->getBatteryAndStorage();
|
||||||
|
|
||||||
|
echo "Battery voltage: " . $resp->battery_millivolt . " mV\n";
|
||||||
|
echo "Storage used: " . $resp->storage_used . " kB\n";
|
||||||
|
echo "Storage total: " . $resp->storage_total . " kB\n";
|
||||||
|
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Menking\Meshcore\Environment;
|
||||||
|
use Menking\Meshcore\Meshcore;
|
||||||
|
|
||||||
|
require(__DIR__ . '/../vendor/autoload.php');
|
||||||
|
|
||||||
|
if( !isset($argv[1]) ) die("{$argv[0]} <port>");
|
||||||
|
|
||||||
|
Environment::configure($argv[1]);
|
||||||
|
|
||||||
|
$mc = Meshcore::getInstance();
|
||||||
|
|
||||||
|
$mc->appStart("app_get_contacts");
|
||||||
|
|
||||||
|
$resp = $mc->getContacts();
|
||||||
|
|
||||||
|
print_r($resp);
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Menking\Meshcore\Environment;
|
||||||
|
use Menking\Meshcore\Meshcore;
|
||||||
|
|
||||||
|
require(__DIR__ . '/../vendor/autoload.php');
|
||||||
|
|
||||||
|
if( !isset($argv[1]) ) die("{$argv[0]} <port>\n");
|
||||||
|
|
||||||
|
Environment::configure($argv[1]);
|
||||||
|
|
||||||
|
$mc = Meshcore::getInstance();
|
||||||
|
|
||||||
|
$mc->appStart("app_get_contacts");
|
||||||
|
|
||||||
|
$resp = $mc->getDeviceInfo();
|
||||||
|
|
||||||
|
print_r($resp);
|
||||||
|
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Menking\Meshcore\Environment;
|
||||||
|
use Menking\Meshcore\Meshcore;
|
||||||
|
|
||||||
|
require(__DIR__ . '/../vendor/autoload.php');
|
||||||
|
|
||||||
|
if( !isset($argv[1]) ) die("{$argv[0]} <port>\n");
|
||||||
|
|
||||||
|
Environment::configure($argv[1]);
|
||||||
|
|
||||||
|
$mc = Meshcore::getInstance();
|
||||||
|
|
||||||
|
$mc->appStart("getdevicetime");
|
||||||
|
|
||||||
|
$ts = $mc->getDeviceTime();
|
||||||
|
|
||||||
|
echo "Current device time: " . date('r', $ts['current_time']) . "\n";
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Menking\Meshcore\Environment;
|
||||||
|
use Menking\Meshcore\Meshcore;
|
||||||
|
use Menking\Meshcore\CoreProtocol;
|
||||||
|
|
||||||
|
require(__DIR__ . '/../vendor/autoload.php');
|
||||||
|
|
||||||
|
if( !isset($argv[1]) ) die("{$argv[0]} <port>\n");
|
||||||
|
|
||||||
|
Environment::configure($argv[1]);
|
||||||
|
|
||||||
|
$mc = Meshcore::getInstance();
|
||||||
|
|
||||||
|
$mc->appStart("get_messages");
|
||||||
|
|
||||||
|
$resp = $mc->getNextMessage();
|
||||||
|
|
||||||
|
print_r($resp);
|
||||||
|
exit;
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Menking\Meshcore\Environment;
|
||||||
|
use Menking\Meshcore\Meshcore;
|
||||||
|
use Menking\Meshcore\CoreProtocol;
|
||||||
|
|
||||||
|
require(__DIR__ . '/../vendor/autoload.php');
|
||||||
|
|
||||||
|
if( !isset($argv[1]) ) die("{$argv[0]} <port>\n");
|
||||||
|
|
||||||
|
Environment::configure($argv[1]);
|
||||||
|
|
||||||
|
$mc = Meshcore::getInstance();
|
||||||
|
|
||||||
|
$x = $mc->appStart("get remote tele");
|
||||||
|
|
||||||
|
$contacts = $mc->getContacts();
|
||||||
|
$idx = 1;
|
||||||
|
foreach($contacts as $contact) {
|
||||||
|
echo "(" . $idx++ . ") {$contact['contact_name']}\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
$choice = readline("\nPlease select contact to attempt remote login: ");
|
||||||
|
$contact = $contacts[$choice - 1];
|
||||||
|
|
||||||
|
echo "Using {$contact['contact_name']}\n";
|
||||||
|
|
||||||
|
$password = readline("Please enter contact's password: ");
|
||||||
|
|
||||||
|
$resp = $mc->login($contact['pub_key'], $password);
|
||||||
|
|
||||||
|
if( !$resp['status'] ) die("Login failed\n");
|
||||||
|
|
||||||
|
$payload =
|
||||||
|
chr(0x00)
|
||||||
|
. chr(0xff) // count
|
||||||
|
. pack('v', 0x0000) // offset
|
||||||
|
. chr(0x00)
|
||||||
|
. chr(0x04)
|
||||||
|
. pack('V', time());
|
||||||
|
|
||||||
|
echo "Payload: \n" . \Menking\Meshcore\Util\Debug::hexDump($payload) . "\n";
|
||||||
|
CoreProtocol::writeFrame($this->serial, $payload);
|
||||||
|
$response = CoreProtocol::readFrame($this->serial);
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Menking\Meshcore\Environment;
|
||||||
|
use Menking\Meshcore\Meshcore;
|
||||||
|
|
||||||
|
require(__DIR__ . '/../vendor/autoload.php');
|
||||||
|
|
||||||
|
if( !isset($argv[1]) ) die("{$argv[0]} <port>\n");
|
||||||
|
|
||||||
|
Environment::configure($argv[1]);
|
||||||
|
|
||||||
|
$mc = Meshcore::getInstance();
|
||||||
|
|
||||||
|
$mc->appStart("getstatusreq");
|
||||||
|
|
||||||
|
$contacts = $mc->getContacts();
|
||||||
|
$idx = 1;
|
||||||
|
foreach($contacts as $contact) {
|
||||||
|
echo "(" . $idx++ . ") {$contact['contact_name']}\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
$choice = readline("\nPlease select contact to attempt remote login: ");
|
||||||
|
$contact = $contacts[$choice - 1];
|
||||||
|
|
||||||
|
echo "Using {$contact['contact_name']}\n";
|
||||||
|
|
||||||
|
print_r($mc->login($contact['pub_key'], ''));
|
||||||
|
|
||||||
|
$ts = $mc->statusRequest($contact['pub_key']);
|
||||||
|
|
||||||
|
print_r($ts);
|
||||||
|
|
||||||
|
$mc->poll();
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Menking\Meshcore\Environment;
|
||||||
|
use Menking\Meshcore\Meshcore;
|
||||||
|
use Menking\Meshcore\CoreProtocol;
|
||||||
|
|
||||||
|
require(__DIR__ . '/../vendor/autoload.php');
|
||||||
|
|
||||||
|
if( !isset($argv[1]) ) die("{$argv[0]} <port>\n");
|
||||||
|
|
||||||
|
Environment::configure($argv[1]);
|
||||||
|
|
||||||
|
$mc = Meshcore::getInstance();
|
||||||
|
|
||||||
|
$x = $mc->appStart("get remote tele");
|
||||||
|
|
||||||
|
$contacts = $mc->getContacts();
|
||||||
|
$idx = 1;
|
||||||
|
foreach($contacts as $contact) {
|
||||||
|
echo "(" . $idx++ . ") {$contact['contact_name']} [" . substr($contact['pub_key'], 0, 6) . "]\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
$choice = readline("\nPlease select contact to attempt remote login: ");
|
||||||
|
$contact = $contacts[$choice - 1];
|
||||||
|
|
||||||
|
echo "Using {$contact['contact_name']}\n";
|
||||||
|
|
||||||
|
/*
|
||||||
|
if( !$mc->connected($contact['pub_key']) ) {
|
||||||
|
$password = readline("Password: ");
|
||||||
|
|
||||||
|
$mc->login($contact['pub_key'], $password);
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
$req = base64_decode($contact['pub_key']) . chr(CoreProtocol::BINREQ_TELEMETRY);
|
||||||
|
|
||||||
|
$result = $mc->sendBinaryRequest($req);
|
||||||
|
echo "Tag: {$result['tag']}\n";
|
||||||
|
|
||||||
|
$timeout = 0;
|
||||||
|
|
||||||
|
while($timeout++ < 6) {
|
||||||
|
echo "timeout: $timeout\n";
|
||||||
|
$resp = $mc->getSyncNextMessage();
|
||||||
|
if( !empty($resp) ) {
|
||||||
|
print_r($resp);
|
||||||
|
if( $resp['code'] == 0x8c) {
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sleep(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "Complete\n";
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Menking\Meshcore\Environment;
|
||||||
|
use Menking\Meshcore\Meshcore;
|
||||||
|
|
||||||
|
require(__DIR__ . '/../vendor/autoload.php');
|
||||||
|
|
||||||
|
if( !isset($argv[1]) ) die("{$argv[0]} <port>\n");
|
||||||
|
|
||||||
|
Environment::configure($argv[1]);
|
||||||
|
|
||||||
|
$mc = Meshcore::getInstance();
|
||||||
|
|
||||||
|
$mc->appStart("reboot");
|
||||||
|
$mc->reboot();
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Menking\Meshcore\Environment;
|
||||||
|
use Menking\Meshcore\Meshcore;
|
||||||
|
use Menking\Meshcore\Util\Debug;
|
||||||
|
|
||||||
|
require(__DIR__ . '/../vendor/autoload.php');
|
||||||
|
|
||||||
|
Environment::configure('/dev/ttyUSB0');
|
||||||
|
|
||||||
|
$mc = Meshcore::getInstance();
|
||||||
|
|
||||||
|
$mc->appStart("send message");
|
||||||
|
$resp = $mc->sendChannelTxtMessage("Ping", 0); // default channel Public on 0
|
||||||
|
|
||||||
|
echo Debug::hexDump($resp);
|
||||||
|
exit;
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Menking\Meshcore\Environment;
|
||||||
|
use Menking\Meshcore\Meshcore;
|
||||||
|
|
||||||
|
require(__DIR__ . '/../vendor/autoload.php');
|
||||||
|
|
||||||
|
if( !isset($argv[1]) ) die("{$argv[0]} <port>\n");
|
||||||
|
|
||||||
|
Environment::configure($argv[1]);
|
||||||
|
|
||||||
|
$mc = Meshcore::getInstance();
|
||||||
|
|
||||||
|
$mc->appStart("set device");
|
||||||
|
|
||||||
|
echo "Setting device time\n";
|
||||||
|
$result = $mc->setDeviceTime();
|
||||||
|
|
||||||
|
print_r($result);
|
||||||
|
|
||||||
|
$mc->poll();
|
||||||
Reference in New Issue
Block a user