148 lines
4.7 KiB
PHP
148 lines
4.7 KiB
PHP
<?php
|
||
|
||
namespace app\process;
|
||
|
||
use app\service\AudioService;
|
||
use Workerman\Timer as WorkermanTimer;
|
||
use Workerman\Worker;
|
||
|
||
class Timer
|
||
{
|
||
private const VAR_POS = 28;
|
||
private const VAR_OFFSET = 0x0F;
|
||
|
||
private const HEX_DATA = [
|
||
0x55, 0xAA, 0x00, 0x00, 0x00, 0x01, 0x00, 0xC1,
|
||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x00,
|
||
0x00, 0x00, 0x09, 0x00, 0x00, 0x04, 0x00, 0x00,
|
||
0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x0D,
|
||
0x0A
|
||
];
|
||
|
||
private int $port;
|
||
private int $maxValue;
|
||
private float $intervalSec;
|
||
private array $texts = [];
|
||
private array $targets = []; // 目标列表 [['ip'=>'...','port'=>...], ...]
|
||
private AudioService $audio;
|
||
|
||
public function onWorkerStart(Worker $worker): void
|
||
{
|
||
$this->port = (int)(getenv('TIMER_PORT') ?: 10000);
|
||
$this->maxValue = (int)(getenv('TIMER_MAX') ?: 6);
|
||
$this->intervalSec = (float)(getenv('TIMER_INTERVAL') ?: 1);
|
||
|
||
$this->loadTexts();
|
||
$this->targets = $this->loadIps();
|
||
$this->audio = new AudioService();
|
||
|
||
echo "Timer process is running.\n";
|
||
echo "目标: ";
|
||
foreach ($this->targets as $t) {
|
||
echo "{$t['ip']}:{$t['port']} ";
|
||
}
|
||
echo "\n最大值: {$this->maxValue}, 间隔: {$this->intervalSec}s\n";
|
||
|
||
$value = 1;
|
||
|
||
$timerCallback = function () use (&$value) {
|
||
$text = $this->texts[$value] ?? "当前: {$value}";
|
||
|
||
$okImg = $this->sendImage($value);
|
||
$status = $okImg ? '图✓' : '图✗';
|
||
echo "\r[{$value}] {$status} {$text}";
|
||
$okAudio = $this->audio->sendText($text);
|
||
$status .= ' ' . ($okAudio ? '音✓' : '音✗');
|
||
echo "\r[{$value}] {$status} {$text}\n";
|
||
|
||
$value = $value >= $this->maxValue ? 1 : $value + 1;
|
||
};
|
||
|
||
// 立即执行一次,再定时
|
||
$timerCallback();
|
||
WorkermanTimer::add($this->intervalSec, $timerCallback);
|
||
}
|
||
|
||
/**
|
||
* 加载目标 IP 列表
|
||
* 优先检查 TIMER_IPS 环境变量(逗号分隔,可带端口如 192.168.1.1:10001)
|
||
* 否则回退到单个 TIMER_IP + TIMER_PORT
|
||
*/
|
||
private function loadIps(): array
|
||
{
|
||
$targets = [];
|
||
|
||
$ipsEnv = getenv('TIMER_IPS');
|
||
if ($ipsEnv !== false && trim($ipsEnv) !== '') {
|
||
// 多 IP 模式
|
||
$parts = explode(',', $ipsEnv);
|
||
foreach ($parts as $part) {
|
||
$part = trim($part);
|
||
if ($part === '') continue;
|
||
if (strpos($part, ':') !== false) {
|
||
[$ip, $portStr] = explode(':', $part, 2);
|
||
$targets[] = [
|
||
'ip' => trim($ip),
|
||
'port' => (int)trim($portStr)
|
||
];
|
||
} else {
|
||
$targets[] = [
|
||
'ip' => $part,
|
||
'port' => $this->port
|
||
];
|
||
}
|
||
}
|
||
} else {
|
||
// 兼容单 IP 旧配置
|
||
$ip = getenv('TIMER_IP') ?: '192.168.42.1';
|
||
$targets[] = [
|
||
'ip' => $ip,
|
||
'port' => $this->port
|
||
];
|
||
}
|
||
|
||
return $targets;
|
||
}
|
||
|
||
/**
|
||
* 向所有目标发送 HEX 数据包
|
||
* @return bool 全部发送成功返回 true,任一失败返回 false
|
||
*/
|
||
private function sendImage(int $variable): bool
|
||
{
|
||
$data = self::HEX_DATA;
|
||
$data[self::VAR_POS] = ($variable + self::VAR_OFFSET) & 0xFF;
|
||
$packedData = pack('C*', ...$data);
|
||
|
||
$allSuccess = true;
|
||
foreach ($this->targets as $target) {
|
||
try {
|
||
$socket = @fsockopen($target['ip'], $target['port'], $errno, $errstr, 5);
|
||
echo "\r图> 尝试连接 [{$target['ip']}:{$target['port']}]";
|
||
if (!$socket) {
|
||
echo "\r图> 连接失败 [{$target['ip']}:{$target['port']}]: {$errstr} ({$errno})\n";
|
||
$allSuccess = false;
|
||
continue;
|
||
}
|
||
echo "\r图> 已连接 [{$target['ip']}:{$target['port']}]";
|
||
fwrite($socket, $packedData);
|
||
fflush($socket);
|
||
fclose($socket);
|
||
}catch (\Exception $exception){
|
||
echo "发送数据失败 [{$target['ip']}:{$target['port']}]: {$exception->getMessage()}\n";
|
||
$allSuccess = false;
|
||
}
|
||
}
|
||
return $allSuccess;
|
||
}
|
||
|
||
private function loadTexts(): void
|
||
{
|
||
for ($i = 1; $i <= $this->maxValue; $i++) {
|
||
$t = getenv('TIMER_TEXT_' . $i);
|
||
if ($t !== false && $t !== '') {
|
||
$this->texts[$i] = $t;
|
||
}
|
||
}
|
||
}
|
||
} |