FeederDataGeneratorService.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335
  1. <?php
  2. namespace App\Service;
  3. use App\Model\Equipment;
  4. use App\Model\SystemL;
  5. use Illuminate\Support\Facades\DB;
  6. class FeederDataGeneratorService extends Service
  7. {
  8. // 设备配置参数
  9. const CAPACITY = 0.6; // 容器容量(吨)
  10. const SUCTION_CAPACITY = 0.1; // 容器容量(吨) 下料
  11. const SUCTION_WEIGHT = 0.1; // 每次吸料重量(吨)
  12. const SUCTION_TIME_MIN = 300; // 吸料最短时间(秒) - 5分钟
  13. const SUCTION_TIME_MAX = 360; // 吸料最长时间(秒) - 6分钟
  14. const SUCTION_INTERVAL_MIN = 300; // 下次吸料最小间隔(秒) -
  15. const SUCTION_INTERVAL_MAX = 360; // 下次吸料最大间隔(秒) -
  16. const DISCHARGE_TIME_MIN = 240; // 下料最短时间(秒) - 4分钟
  17. const DISCHARGE_TIME_MAX = 300; // 下料最长时间(秒) - 5分钟
  18. const DISCHARGE_INTERVAL_MIN = 300; // 下次下料最小间隔(秒) -
  19. const DISCHARGE_INTERVAL_MAX = 360; // 下次下料最大间隔(秒) -
  20. private $devices = []; // 存储设备数据
  21. /**
  22. * 处理原始数据并生成设备运行明细
  23. * @param array $rawData 原始数据源
  24. * @return array 生成的设备运行明细数据
  25. */
  26. public function generateData(array $rawData): array
  27. {
  28. // 首先按设备和日期分组汇总产量
  29. $this->processRawData($rawData);
  30. // dd($this->devices);
  31. // 为每个设备生成运行明细
  32. $result = [];
  33. foreach ($this->devices as $deviceId => $deviceData) {
  34. // 为每一天生成数据
  35. foreach ($deviceData['daily_totals'] as $date => $total) {
  36. $details = $this->generateDeviceDayData($deviceData['device'], $date, $total);
  37. $result = array_merge($result, $details);
  38. }
  39. }
  40. try {
  41. DB::beginTransaction();
  42. if(! empty($result)){
  43. $chunkSize = 200; // 每次插入的数量
  44. foreach (array_chunk($result, $chunkSize) as $chunk) {
  45. SystemL::insert($chunk); // 使用模型的 insert 方法批量插入
  46. echo '200条写入成功' . "\n";
  47. }
  48. }
  49. DB::commit();
  50. }catch (\Throwable $exception){
  51. DB::rollBack();
  52. return [false, $exception->getMessage()];
  53. }
  54. return [true, ''];
  55. }
  56. /**
  57. * 处理原始数据,按设备和日期汇总产量
  58. * @param array $rawData 原始数据源
  59. */
  60. private function processRawData(array $rawData)
  61. {
  62. $map = Equipment::select('id','code','title')->get()->toArray();
  63. foreach ($map as $value){
  64. $map1[$value['id']] = $value;
  65. }
  66. foreach ($rawData as $item) {
  67. $deviceId = $item['device_id'];
  68. $t = $map1[$deviceId];
  69. $timestamp = $item['upd_time'];
  70. $finishedNum = floatval($item['finished_num']);
  71. // 解析日期
  72. $date = date('Y-m-d', $timestamp);
  73. // 初始化设备数据
  74. if (!isset($this->devices[$deviceId])) {
  75. $this->devices[$deviceId] = [
  76. 'device' => $t,
  77. 'total_production' => 0,
  78. 'daily_totals' => []
  79. ];
  80. }
  81. // 累加总产量和每日产量
  82. $this->devices[$deviceId]['total_production'] += $finishedNum;
  83. if (!isset($this->devices[$deviceId]['daily_totals'][$date])) {
  84. $this->devices[$deviceId]['daily_totals'][$date] = 0;
  85. }
  86. $this->devices[$deviceId]['daily_totals'][$date] += $finishedNum;
  87. }
  88. }
  89. /**
  90. * 为单个设备的一天生成详细的运行数据
  91. * @param int $deviceId 设备ID
  92. * @param string $date 日期(格式:YYYY-MM-DD)
  93. * @param float $totalProduction 当天总产量
  94. * @return array 生成的详细数据条目
  95. */
  96. private function generateDeviceDayData(array $device, string $date, float $totalProduction): array
  97. {
  98. $result = [];
  99. // 计算当天需要完成多少轮完整的吸料-下料循环
  100. $fullCycles = floor($totalProduction / self::SUCTION_CAPACITY);
  101. $filledAmount = $fullCycles * self::SUCTION_CAPACITY;
  102. $remainingWeight = bcsub($totalProduction, $filledAmount,2);
  103. // 设置当天的起始时间戳(早上7:30)
  104. $currentTime = strtotime($date . ' 07:30:00');
  105. // 执行完整循环
  106. for ($i = 0; $i < $fullCycles; $i++) {
  107. // 执行一次完整的吸料-下料过程
  108. $cycleData = $this->generateFullCycle($device, $currentTime);
  109. $result = array_merge($result, $cycleData);
  110. // 更新当前时间
  111. $currentTime = end($cycleData)['time'];
  112. // // 添加随机间隔时间
  113. // $interval = rand(self::SUCTION_INTERVAL_MIN, self::SUCTION_INTERVAL_MAX);
  114. // $currentTime += $interval;
  115. }
  116. // 如果有剩余量,执行部分吸料过程
  117. if ($remainingWeight > 0) {
  118. $lastKey = array_key_last($result); // 获取最后一个键
  119. $result[$lastKey]['value'] += $remainingWeight;
  120. // $remainingCyleData = $this->generatePartialCycle($device, $currentTime, $remainingWeight);
  121. // $result = array_merge($result, $remainingCyleData);
  122. }
  123. return $result;
  124. }
  125. /**
  126. * 随机插入急停事件,并模拟急停持续时间
  127. *
  128. * @param int $deviceId 设备ID
  129. * @param int &$currentTime 当前时间戳(引用传递,用于更新)
  130. * @param array &$result 结果数组(引用传递,用于添加急停记录)
  131. */
  132. private function maybeInsertEmergencyStop(array $device, int &$currentTime, array &$result, $data_point_id, $data_point_name): void
  133. {
  134. if (rand(1, 100) <= 3) { // 3% 的概率触发急停
  135. $emergencyStopTime = $currentTime;
  136. // 插入急停开始事件
  137. $result[] = [
  138. 'time' => $emergencyStopTime,
  139. 'value' => -1, // 标识为急停
  140. 'device_no' => $device['code'],
  141. 'device_name' => $device['title'],
  142. 'data_point_id' => $data_point_id,
  143. 'data_point_name' => $data_point_name,
  144. 'push_time' => $emergencyStopTime,
  145. // 'push_time_day' => date("Y-m-d H:i:s",$suctionStartTime)
  146. ];
  147. // 模拟急停持续时间(10~15秒)
  148. $stopDuration = rand(10, 15);
  149. $currentTime += $stopDuration;
  150. // 可选:插入急停结束事件(也可以不插,只用时间推移表示)
  151. // $result[] = [
  152. // 'time' => $currentTime,
  153. // 'value' => -2, // 急停恢复
  154. // 'device_no' => $deviceId,
  155. // 'push_time' => $currentTime
  156. // ];
  157. }
  158. }
  159. /**
  160. * 生成一个完整的吸料-下料循环数据
  161. * @param int $deviceId 设备ID
  162. * @param int $startTime 起始时间戳
  163. * @return array 生成的数据条目
  164. */
  165. private function generateFullCycle(array $device, int $startTime): array
  166. {
  167. $result = [];
  168. $currentTime = $startTime;
  169. // 吸料阶段
  170. for ($i = 0; $i < 1; $i++) {
  171. // 【吸料前】尝试插入急停
  172. $this->maybeInsertEmergencyStop($device, $currentTime, $result,5,"吸料急停");
  173. // 吸料开始
  174. $suctionStartTime = $currentTime;
  175. $result[] = [
  176. 'time' => $suctionStartTime,
  177. 'value' => 1,
  178. 'device_no' => $device['code'],
  179. 'device_name' => $device['title'],
  180. 'data_point_id' => 1,
  181. 'data_point_name' => "吸料",
  182. 'push_time' => $suctionStartTime,
  183. // 'push_time_day' => date("Y-m-d H:i:s",$suctionStartTime)
  184. ];
  185. // 吸料结束
  186. $suctionDuration = rand(self::SUCTION_TIME_MIN, self::SUCTION_TIME_MAX);
  187. $currentTime += $suctionDuration;
  188. // 【吸料后】尝试插入急停
  189. $this->maybeInsertEmergencyStop($device, $currentTime, $result,5,"吸料急停");
  190. $result[] = [
  191. 'time' => $currentTime,
  192. 'value' => self::SUCTION_WEIGHT,
  193. 'device_no' => $device['code'],
  194. 'device_name' => $device['title'],
  195. 'data_point_id' => 2,
  196. 'data_point_name' => "吸料结束且本次吸料重量",
  197. 'push_time' => $currentTime,
  198. // 'push_time_day' => date("Y-m-d H:i:s",$currentTime)
  199. ];
  200. // 添加随机间隔时间
  201. if ($i < 1) { // 最后一次吸料后不需要等待
  202. // $interval = rand(self::SUCTION_INTERVAL_MIN, self::SUCTION_INTERVAL_MAX);
  203. $interval = rand(10, 15);
  204. $currentTime += $interval;
  205. }
  206. }
  207. // 【下料前】尝试插入急停
  208. $this->maybeInsertEmergencyStop($device, $currentTime, $result,6,"下料急停");
  209. // 下料阶段
  210. $dischargeStartTime = $currentTime;
  211. $result[] = [
  212. 'time' => $dischargeStartTime,
  213. 'value' => 1,
  214. 'device_no' => $device['code'],
  215. 'device_name' => $device['title'],
  216. 'data_point_id' => 3,
  217. 'data_point_name' => "下料",
  218. 'push_time' => $dischargeStartTime,
  219. // 'push_time_day' => date("Y-m-d H:i:s",$dischargeStartTime)
  220. ];
  221. $dischargeDuration = rand(self::DISCHARGE_TIME_MIN, self::DISCHARGE_TIME_MAX);
  222. $currentTime += $dischargeDuration;
  223. // 【下料后】尝试插入急停
  224. $this->maybeInsertEmergencyStop($device, $currentTime, $result,6,"下料急停");
  225. $result[] = [
  226. 'time' => $currentTime,
  227. 'value' => self::SUCTION_CAPACITY,
  228. 'device_no' => $device['code'],
  229. 'device_name' => $device['title'],
  230. 'data_point_id' => 4,
  231. 'data_point_name' => "下料结束且本次下料重量",
  232. 'push_time' => $currentTime,
  233. // 'push_time_day' => date("Y-m-d H:i:s",$currentTime)
  234. ];
  235. return $result;
  236. }
  237. /**
  238. * 生成一个不完整的循环(只有吸料部分)
  239. * @param int $deviceId 设备ID
  240. * @param int $startTime 起始时间戳
  241. * @param float $requiredWeight 需要吸的重量
  242. * @return array 生成的数据条目
  243. */
  244. private function generatePartialCycle(array $device, int $startTime, float $requiredWeight): array
  245. {
  246. $result = [];
  247. $currentTime = $startTime;
  248. // 计算需要多少次吸料
  249. $suctionsNeeded = ceil($requiredWeight / self::SUCTION_WEIGHT);
  250. $totalSuctionWeight = 0;
  251. for ($i = 0; $i < $suctionsNeeded; $i++) {
  252. // 吸料开始
  253. $suctionStartTime = $currentTime;
  254. $result[] = [
  255. 'time' => $suctionStartTime,
  256. 'value' => 1,
  257. 'device_no' => $device['code'],
  258. 'device_name' => $device['title'],
  259. 'data_point_id' => 1,
  260. 'data_point_name' => "吸料",
  261. 'push_time' => $suctionStartTime,
  262. // 'push_time_day' => => date("Y-m-d H:i:s",$suctionStartTime)
  263. ];
  264. // 吸料结束
  265. $suctionDuration = rand(self::SUCTION_TIME_MIN, self::SUCTION_TIME_MAX);
  266. $currentTime += $suctionDuration;
  267. // 判断这次吸料是否会导致超过所需总量
  268. $addedWeight = ($i == $suctionsNeeded - 1) ? $requiredWeight - $totalSuctionWeight : self::SUCTION_WEIGHT;
  269. $totalSuctionWeight += $addedWeight;
  270. $result[] = [
  271. 'time' => $currentTime,
  272. 'value' => $addedWeight,
  273. 'device_no' => $device['code'],
  274. 'device_name' => $device['title'],
  275. 'data_point_id' => 2,
  276. 'data_point_name' => "吸料结束且本次吸料重量",
  277. 'push_time' => $currentTime,
  278. // 'push_time_day' => => date("Y-m-d H:i:s",$currentTime)
  279. ];
  280. // 添加随机间隔时间(在40-50分钟之间)
  281. if ($i < $suctionsNeeded - 1) {
  282. $interval = rand(self::SUCTION_INTERVAL_MIN, self::SUCTION_INTERVAL_MAX);
  283. $currentTime += $interval;
  284. }
  285. }
  286. return $result;
  287. }
  288. }