PUMP_MQTT_PUBLISH.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478
  1. <?php
  2. require('../vendor/autoload.php');
  3. use \PhpMqtt\Client\MqttClient;
  4. use \PhpMqtt\Client\ConnectionSettings;
  5. use think\facade\Cache;
  6. // date_default_timezone_set("America/Bahia");
  7. // define('HOST', '127.0.0.1');
  8. // define('PORT', '6379');
  9. // define('PASSWORD', '123456');
  10. // define('DATABASE', 2);
  11. define('HOST', 'r-bp1eebab79320044pd.redis.rds.aliyuncs.com');
  12. define('PORT', '6379');
  13. define('PASSWORD', '7e2b5c91e438be3c!');
  14. define('DATABASE', 4);
  15. function app_redis()
  16. {
  17. static $redis = null;
  18. static $conn = false;
  19. if (!$conn) {
  20. connect: //定义标签
  21. $redis = new Redis();
  22. try {
  23. //建立的Redis短连接,在请求结束后不会自动关闭,相当于持久连接.
  24. $conn = $redis->connect(HOST, PORT);
  25. $conn = $redis->auth(PASSWORD);
  26. $conn = $redis->select(DATABASE);
  27. // 连接成功,返回$redis对象,连接失败,返回false.
  28. return ($conn === true) ? $redis : false;
  29. } catch (Exception $e) {
  30. return false;
  31. }
  32. } else {
  33. // 这里假设PHP-FPM在处理一个请求的时间内,Redis连接都是可用的.
  34. // 所以只在PHP-CLI下检查Redis连接的状态,进行断线重连.
  35. if (php_sapi_name() === 'cli') {
  36. try {
  37. // ping用于检查当前连接的状态,成功时返回+PONG,失败时抛出一个RedisException对象.
  38. // ping失败时警告:
  39. // Warning: Redis::ping(): connect() failed: Connection refused
  40. // var_dump('AAAAAAAAA', $redis);
  41. // echo 'Redis 连接状态' . $redis->ping() . PHP_EOL;
  42. @$redis->ping();
  43. if (!$redis->ping()) {
  44. goto connect; //跳转到标签出继续执行连接操作
  45. }
  46. } catch (Exception $e) {
  47. // 信息如 Connection lost 或 Redis server went away
  48. echo $e->getMessage();
  49. echo 'Redis 连接失败 重新连接:' . PHP_EOL;
  50. // 断线重连
  51. goto connect;
  52. }
  53. }
  54. return $redis;
  55. }
  56. }
  57. function sendConfig($topic,$config)
  58. {
  59. $server = '116.62.220.88';
  60. $port = 1883;
  61. $clientId = 'local_mqtt_pump_config';
  62. $username = 'rl517';
  63. $password = "rlian2022";
  64. $clean_session = false;
  65. $connectionSettings = new ConnectionSettings();
  66. $connectionSettings = $connectionSettings
  67. ->setUsername($username)
  68. ->setPassword($password)
  69. ->setKeepAliveInterval(60)
  70. // Last Will 设置
  71. // ->setLastWillTopic('emqx/test/last-will')
  72. // ->setLastWillMessage('client disconnect')
  73. // ->setLastWillQualityOfService(1)
  74. ;
  75. $mqtt = new MqttClient($server, $port, $clientId);
  76. $mqtt->connect($connectionSettings, $clean_session);
  77. echo 'connect OK'.PHP_EOL;
  78. echo 'topic:'.$topic.PHP_EOL;
  79. echo 'config:'.$config.PHP_EOL;
  80. $res=$mqtt->publish(
  81. $topic,
  82. $config,
  83. 1
  84. );
  85. echo 'publish end'.PHP_EOL;
  86. $mqtt->loop(true,true);
  87. $mqtt->disconnect();
  88. return $res;
  89. }
  90. function updateSendResult($msgid){
  91. $conn = new mysqli('rm-bp1h3uqkzy66ckt8y125010.mysql.rds.aliyuncs.com', 'dev', '711e7D69f9d0c3f1', 'catch_pump');
  92. // $conn = new mysqli('127.0.0.1', 'root', 'root', 'catch_pump');
  93. if ($conn -> connect_errno) {
  94. printf("Connect failed: %s\n", $conn->connect_error);
  95. exit();
  96. }
  97. $sql = "UPDATE send_config_log SET result='1' WHERE id=".$msgid;
  98. if ($conn->query($sql) === TRUE) {
  99. echo "send_config_log update success";
  100. } else {
  101. echo "更新失败: " . $conn->error;
  102. }
  103. }
  104. function rlog(...$args)
  105. {
  106. //函数参数是可变参数 使用前查下static开始的大写变量配置是否正确
  107. //rlog('info', [1,2,3], $a = null, '[]', $this, false);
  108. $args = func_get_args();
  109. // if (empty($args[0])) {
  110. // return;
  111. // }
  112. static $LOG_CONSOLE = true;//是否输出到控制台 fpm需要为false cli可以为true
  113. if (!empty(getenv('PM2_HOME'))) { //pm2
  114. $LOG_CONSOLE = false;
  115. } else if (PHP_SAPI == 'fpm-fcgi') { //php-fpm
  116. $LOG_CONSOLE = false;
  117. } else if (PHP_SAPI == 'cgi') { //cli
  118. $LOG_CONSOLE = true;
  119. } else if (PHP_SAPI == 'cli-server') { //php think run
  120. $LOG_CONSOLE = false;
  121. } else {
  122. $LOG_CONSOLE = true;
  123. }
  124. static $LOG_NAME = "./pubsub_tcp_iiot.log";//值为空时 不写入文件 第一个参数是imlog时也可以更改
  125. if (stripos(PHP_OS, 'WIN') !== false) {
  126. $LOG_NAME = "C:\Windows\Temp\php.log";
  127. }
  128. static $LOG_SIZE = 128 * 1024 * 1024;//文件最大尺寸 大于这个尺寸时 会生成个后缀.old的文件
  129. static $LOG_CACHE = false;//是否缓存日志内容 用于批量写入文件 如需强制刷新第一个参数传sync
  130. static $CACHE_DURATION = 10;//缓存最大时间 秒
  131. static $CACHE_SIZE = 1024;//缓存大小
  132. static $cacheStartTime = 0;
  133. static $cacheBuf = '';
  134. static $LOG_TIMES = 10;//调用这个函数最大次数 超过次数或$logCount==1 会判断下文件大小 决定是否新生成文件
  135. static $logCount = 0;
  136. static $MAX_LEN = 1024 * 4;//数据不能超过这个 不然就截断了
  137. $sync = false;//如果是true 使用$LOG_CACHE时会把数据保存到磁盘
  138. $implicit0 = ['imsync', 'imtrace', 'imlog'];//这个函数的参数0 隐藏用法
  139. $buf = '';
  140. if (count($args) == 1 && $args[0] === "\n") {//只有换行时 不写入时间戳了
  141. $buf = "\n";
  142. } else {
  143. $sync = is_string($args[0]) && strtolower($args[0]) == $implicit0[0];
  144. if (is_string($args[0]) && strpos($args[0], $implicit0[2]) === 0) {//设置日志名
  145. $LOG_NAME = str_replace($implicit0[2], '', $args[0]);
  146. // unset($args[0]);//记入日志也可以
  147. } else {
  148. // if ($LOG_NAME == 'php.log') {//每个文件会生成不同的文件名
  149. // $LOG_NAME = str_replace(".php", '', basename(__FILE__)) . '.log';
  150. // }
  151. }
  152. $pid = '';//进程id
  153. if (function_exists('posix_getpid')) {
  154. $pid = ' ' . posix_getpid() . ' ';
  155. }
  156. $fileLine = '';//文件名:行号
  157. {
  158. $debug = debug_backtrace();
  159. $backtrace = 0;
  160. if (is_string($args[0]) && strpos($args[0], $implicit0[1]) === 0) {
  161. $backtrace = intval(str_replace($implicit0[1], '', $args[0]));
  162. unset($args[0]);
  163. } else if ($sync) {
  164. unset($args[0]);
  165. }
  166. $fileLine = ($pid == '' ? ' ' : '') . str_replace(".php", '', basename($debug[$backtrace]['file']))
  167. . ':' . $debug[$backtrace]['line'] . '';
  168. }
  169. $allPara = '';
  170. foreach ($args as $para) {
  171. if (is_array($para)) {
  172. $allPara .= json_encode($para) . ' ';
  173. } else if (is_object($para)) {
  174. if (method_exists($para, '__toString')) {
  175. $allPara .= $para . ' ';
  176. } else {
  177. $allPara .= get_class($para) . json_encode($para) . ' ';
  178. }
  179. } else if (is_bool($para)) {
  180. $allPara .= $para ? 'true ' : 'false ';
  181. } else if (is_null($para)) {
  182. $allPara .= 'null ';
  183. } else {
  184. $allPara .= $para . ' ';
  185. }
  186. }
  187. $len = strlen($allPara);
  188. if ($len > $MAX_LEN) {
  189. $allPara = substr($allPara, 0, $MAX_LEN) . "({$len})......";
  190. }
  191. $buf = "[" . date("y-m-d H:i:s.") . substr(microtime(FALSE), 2, 3) . "{$pid}{$fileLine}]" . $allPara . "\n";
  192. }
  193. $logCount++;
  194. if (!empty($LOG_NAME)) {
  195. if ($LOG_CACHE) {
  196. if ($cacheBuf == '') {
  197. $cacheStartTime = time();
  198. }
  199. $cacheBuf .= $buf;
  200. //超过缓存尺寸 或者 超过缓存时长 写缓存到文件
  201. if (strlen($cacheBuf) > $CACHE_SIZE || time() - $cacheStartTime > $CACHE_DURATION) {
  202. $cacheStartTime = time();
  203. goto write;
  204. } else {
  205. if ($sync) {
  206. goto write;
  207. } else {
  208. goto skipWrite;
  209. }
  210. }
  211. } else {
  212. $cacheBuf = $buf;
  213. }
  214. write: {
  215. if ($logCount > 100) {//会有缓存
  216. clearstatcache();
  217. }
  218. //超过尺寸后 删除旧文件 把新文件重命名为旧文件
  219. if (($logCount == 1 || $logCount > $LOG_TIMES)
  220. && file_exists($LOG_NAME) && filesize($LOG_NAME) > $LOG_SIZE) {
  221. //获取独占锁
  222. $fp = fopen($LOG_NAME . '.lock', 'a');
  223. $k = 0;
  224. do {
  225. $isLock = flocK($fp, LOCK_EX);
  226. $k++;
  227. if (!$isLock && $k > 1000) {
  228. echo "lock 1000\n";
  229. goto PUT;
  230. }
  231. } while (!$isLock);
  232. //另外一个进程进入时 重新判断下
  233. clearstatcache();
  234. if (filesize($LOG_NAME) <= $LOG_SIZE) {
  235. goto UN;
  236. }
  237. //删除旧文件和重命名文件
  238. $oldLogName = $LOG_NAME . '.old';
  239. if (file_exists($oldLogName)) {
  240. if (!unlink($oldLogName)) {
  241. echo "unlink err\n";
  242. }
  243. }
  244. if (!rename($LOG_NAME, $oldLogName)) {
  245. echo "rename err\n";
  246. }
  247. //解锁
  248. UN:
  249. flock($fp, LOCK_UN);
  250. fclose($fp);
  251. $logCount = 0;
  252. }
  253. PUT:
  254. if (!file_put_contents($LOG_NAME, $cacheBuf, FILE_APPEND)) {
  255. echo "file_put_contents err," . getcwd() . "/{$LOG_NAME} \n";
  256. }
  257. $cacheBuf = '';
  258. }
  259. skipWrite:{
  260. }
  261. }
  262. if ($LOG_CONSOLE) {
  263. echo $buf;
  264. }
  265. }
  266. function printChars($str) {
  267. $result = '';
  268. $length = strlen($str);
  269. for ($i = 0; $i < $length; $i++) {
  270. $char = $str[$i];
  271. $ordValue = ord($char);
  272. // 如果是控制字符(ASCII 值小于32 或者 是换行、回车、制表符)
  273. if ($ordValue >= 32 && $ordValue <= 126) {
  274. $result .= $char;
  275. } else {
  276. // 对于其他字符直接输出
  277. $result .= '.';
  278. // switch ($ordValue) {
  279. // case 9: // 制表符
  280. // $result .= '\t';
  281. // break;
  282. // case 10: // 换行符
  283. // $result .= '\n';
  284. // break;
  285. // case 13: // 回车符
  286. // $result .= '\r';
  287. // break;
  288. // default:
  289. // // 其他控制字符可以使用其 ASCII 值的十六进制形式
  290. // $result .= '\\x' . dechex($ordValue);
  291. // break;
  292. // }
  293. }
  294. }
  295. return $result;
  296. }
  297. class MQTT_TYPE {/*对应从1开始*/
  298. public static $PUB_MEASURE = 'a'; //设备->平台 设备上报 的压力值 /*1*/
  299. public static $SUB_ALARM = 'b'; //平台->设备 平台下发 的告警值
  300. public static $SUB_ALARM_REPLAY = 'c'; //设备->平台 设备上报 对平台下发的告警值的回复
  301. public static $GET_ALARM = 'd'; //平台下发 获取设备告警值
  302. //设备->平台 设备上报 下面6个 顺序不要变 数据库里面存的是1-6 本页面insertAlarm函数有ord转对应的数字
  303. public static $PUB_UPPER_ALARM_START_EVENT = 'e'; /*5*/
  304. public static $PUB_UPPER_ALARM_STOP_EVENT = 'f';
  305. public static $PUB_LOWER_ALARM_START_EVENT = 'g';
  306. public static $PUB_LOWER_ALARM_STOP_EVENT = 'h';
  307. public static $PUB_UPPER_ALARM_SETTING_CHANGE = 'i';
  308. public static $PUB_LOWER_ALARM_SETTING_CHANGE = 'j'; /*10*/
  309. public static $UP_ALARM = 'k';//设备->平台
  310. //平台->设备 校准0
  311. public static $PUB_CALIBRATION_ZERO = 'l';
  312. //设备->平台 校准0回复
  313. public static $PUB_CALIBRATION_ZERO_REPLAY = 'm';
  314. }
  315. define("ALTER_SEP", ",");
  316. //成功返回二进制, 失败返回false
  317. function buildMqttData($mqttType, $msgId/*整数 0-4294967295*/, $data) {
  318. /**test**/
  319. // $mqttType = MQTT_TYPE::$SUB_ALARM;
  320. // $data['lower'] = '12.34';
  321. // $data['upper'] = '56.789';
  322. //
  323. // $mqttType = MQTT_TYPE::$GET_ALARM;
  324. /**test**/
  325. rlog("build", $mqttType, $msgId, $data);
  326. $msgId = intval($msgId);
  327. if ($msgId < 0 || $msgId > 0xffffffff) {
  328. rlog("err msgId");
  329. return false;
  330. }
  331. $MIN = 0;
  332. $MAX = 39.99;
  333. $ALERT_LEN = 6;//值最大
  334. $bin = pack("C1N1", ord($mqttType), $msgId/*time()*/ /*这个时间大小端和设备端相反*/);
  335. if ($mqttType == MQTT_TYPE::$SUB_ALARM) {
  336. /*
  337. * 可以单独设置一个值,不需要设置的值 可像下面这样置空
  338. * $data['lower'] = ''
  339. * */
  340. if (!isset($data['lower']) || strlen($data['lower']) >= $ALERT_LEN) {
  341. rlog("lower val err, max len " . $ALERT_LEN);
  342. return false;
  343. }
  344. if (!isset($data['upper']) || strlen($data['upper']) >= $ALERT_LEN) {
  345. rlog("upper val err, max len " . $ALERT_LEN);
  346. return false;
  347. }
  348. $data['lower'] = trim($data['lower']);
  349. $data['upper'] = trim($data['upper']);
  350. if ($data['lower'] == '' && $data['upper'] == '') {
  351. rlog("val empty");
  352. return false;
  353. }
  354. //0-39.999 值范围
  355. //char str[] = "23.556,44.25";//设置下限和上限
  356. //char str[] = " ,44.25"; //设置上限 前面有个空格
  357. //char str[] = "23.556,"; //设置下限
  358. if ($data['lower'] == '') {
  359. $data['lower'] = ' ';//要有个空格
  360. } else {
  361. if (!preg_match('/^-?\d+(\.\d+)?$/', $data['lower'])) {
  362. rlog("err lower " . $data['lower']);
  363. return false;
  364. } else {
  365. if (floatval($data['lower']) < $MIN || floatval($data['lower']) > $MAX) {
  366. rlog("err lower range " . $data['lower']);
  367. return false;
  368. }
  369. }
  370. }
  371. if (!empty($data['upper'])) {
  372. if (!preg_match('/^-?\d+(\.\d+)?$/', $data['upper'])) {
  373. rlog("err upper " . $data['upper']);
  374. return false;
  375. } else {
  376. if (floatval($data['upper']) < $MIN || floatval($data['upper']) > $MAX) {
  377. rlog("err upper range " . $data['upper']);
  378. return false;
  379. }
  380. }
  381. }
  382. $buff = $data['lower'] . ALTER_SEP . $data['upper'];/*1.236,10.256*/
  383. $bin = $bin . $buff . chr(0);//补零 设备解析是以为\0结尾
  384. } else if ($mqttType == MQTT_TYPE::$GET_ALARM) {
  385. } else if ($mqttType == MQTT_TYPE::$PUB_CALIBRATION_ZERO) {
  386. } else {
  387. rlog("warn unknown type " . ord($mqttType));
  388. return false;
  389. }
  390. rlog('build ' . bin2hex($bin) . '(' . printChars($bin) . ')');
  391. return $bin;
  392. }
  393. while (1) {
  394. $jsonData= app_redis()->rpop("pump_mqtt_config_list");
  395. if(!$jsonData){
  396. sleep(3);
  397. continue;
  398. }
  399. $data=json_decode($jsonData,true);
  400. var_dump($data);
  401. $topic="DEV/DATA/".$data['mac'];
  402. $config_json=$data['config'];
  403. $sendData=false;
  404. if($data['type']=='config'){
  405. $sendData = buildMqttData(MQTT_TYPE::$SUB_ALARM, $config_json['msgId'], ['lower' =>$config_json['lower'], 'upper' =>$config_json['upper']]);
  406. }elseif($data['type']=='alarm'){
  407. $sendData = buildMqttData(MQTT_TYPE::$GET_ALARM, $config_json['msgId'], null);
  408. }elseif($data['type']=='calibration'){
  409. $sendData = buildMqttData(MQTT_TYPE::$PUB_CALIBRATION_ZERO, $config_json['msgId'], null);
  410. }else{
  411. }
  412. var_dump($sendData);
  413. if($sendData){
  414. updateSendResult($config_json['msgId']);
  415. $res=sendConfig($topic,$sendData);
  416. }
  417. }