EquipmentDataModel.cs 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687
  1. using SWRIS.Controls;
  2. using SWRIS.Dtos;
  3. using SWRIS.Enums;
  4. using System;
  5. using System.Collections.Generic;
  6. using System.Collections.ObjectModel;
  7. using System.ComponentModel;
  8. using System.Linq;
  9. using System.Net.Sockets;
  10. using System.Threading.Tasks;
  11. using System.Windows;
  12. namespace SWRIS.Models
  13. {
  14. public class EquipmentDataModel : INotifyPropertyChanged
  15. {
  16. private int ropeNumber;
  17. private string ropeName;
  18. private int trolleyId;
  19. private string ipAddress;
  20. private string serialNo;
  21. private bool isConnect = false;
  22. private bool isDataActive = false;
  23. private int sensorCount = 4;
  24. private double liftHightRatio = 1;
  25. private double ropeLength = 100f; // 默认长度为100米
  26. private RunningStatus runningStatus = RunningStatus.Stopped;
  27. private DirectionState direction;
  28. private FaultType? faultType;
  29. private double speed;
  30. private double position;
  31. private double absolutePosition;
  32. private double liftHeight;
  33. private double maxDamageValue;
  34. private double avgDamageValue;
  35. private int damageCount;
  36. private int mildCount;
  37. private int lightCount;
  38. private int moderateCount;
  39. private int severeCount;
  40. private int criticalCount;
  41. private int extremeCount;
  42. private DetectionDataModel detectionData;
  43. private RiskLevel riskLevel = RiskLevel.Normal;
  44. private Socket clientSocket;
  45. private double samplingStep;
  46. private DebugMessageModel debugMessage = new DebugMessageModel(string.Empty);
  47. private bool isAlerting = false;
  48. private bool isSpeedStable = true;
  49. private double variationThreshold = 0.25;
  50. private bool isHeartbeatReceived;
  51. private uint detectionNo;
  52. private bool disableAlarm;
  53. private int[] inUseSensor = new int[] { 1, 2, 3, 4 };
  54. private RealTimeLineChart chart;
  55. /// <summary>
  56. /// 钢丝绳绳号
  57. /// </summary>
  58. public int RopeNumber
  59. {
  60. get => ropeNumber;
  61. set { ropeNumber = value; OnPropertyChanged(nameof(RopeNumber)); }
  62. }
  63. /// <summary>
  64. /// 钢丝绳名称
  65. /// </summary>
  66. public string RopeName
  67. {
  68. get => ropeName;
  69. set { ropeName = value; OnPropertyChanged(nameof(RopeName)); }
  70. }
  71. /// <summary>
  72. /// 小车Id
  73. /// </summary>
  74. public int TrolleyId
  75. {
  76. get => trolleyId;
  77. set { trolleyId = value; OnPropertyChanged(nameof(TrolleyId)); }
  78. }
  79. /// <summary>
  80. /// 设备Ip地址
  81. /// </summary>
  82. public string IpAddress
  83. {
  84. get => ipAddress;
  85. set { ipAddress = value; OnPropertyChanged(nameof(IpAddress)); }
  86. }
  87. /// <summary>
  88. /// 设备序列号
  89. /// </summary>
  90. public string SerialNo
  91. {
  92. get => serialNo;
  93. set { serialNo = value; OnPropertyChanged(nameof(SerialNo)); }
  94. }
  95. /// <summary>
  96. /// 连接状态
  97. /// </summary>
  98. public bool IsConnect
  99. {
  100. get => isConnect;
  101. set { isConnect = value; OnPropertyChanged(nameof(IsConnect)); }
  102. }
  103. /// <summary>
  104. /// 数据激活状态:最近有正常数据收发。
  105. /// 用于在心跳缺失但数据仍在上报时,区分链路真实静默与被误判离线。
  106. /// </summary>
  107. public bool IsDataActive
  108. {
  109. get => isDataActive;
  110. set { isDataActive = value; OnPropertyChanged(nameof(IsDataActive)); }
  111. }
  112. /// <summary>
  113. /// 传感器数量
  114. /// </summary>
  115. public int SensorCount
  116. {
  117. get => sensorCount;
  118. set { sensorCount = value; OnPropertyChanged(nameof(SensorCount)); }
  119. }
  120. /// <summary>
  121. /// 钢丝绳绳长
  122. /// </summary>
  123. public double RopeLength
  124. {
  125. get => ropeLength;
  126. set { ropeLength = value; OnPropertyChanged(nameof(RopeLength)); }
  127. }
  128. public double LiftHightRatio
  129. {
  130. get => liftHightRatio;
  131. set { liftHightRatio = value; OnPropertyChanged(nameof(LiftHightRatio)); }
  132. }
  133. /// <summary>
  134. /// 采样步长
  135. /// </summary>
  136. public double SamplingStep
  137. {
  138. get => samplingStep;
  139. set { samplingStep = value; OnPropertyChanged(nameof(SamplingStep)); }
  140. }
  141. /// <summary>
  142. /// 运行状态
  143. /// </summary>
  144. public RunningStatus RunningStatus
  145. {
  146. get => runningStatus;
  147. set { runningStatus = value; OnPropertyChanged(nameof(RunningStatus)); }
  148. }
  149. /// <summary>
  150. /// 预状态挂起时间戳(UtcNow.Ticks),0 表示无挂起的预状态
  151. /// </summary>
  152. public long PreStatusTimestamp { get; set; }
  153. /// <summary>
  154. /// 预运行请求的检测模式(TemporalNormal=时域,SpatialNormal=空域),用于 0x06 应答确认时恢复正确模式
  155. /// </summary>
  156. public RunningStatus PreStatusRunningMode { get; set; }
  157. /// <summary>
  158. /// 标记预状态位并记录挂起时间戳(本地先行、等待设备确认)
  159. /// </summary>
  160. public void MarkPreStatus(RunningStatus flag)
  161. {
  162. RunningStatus |= flag;
  163. PreStatusTimestamp = DateTime.UtcNow.Ticks;
  164. }
  165. /// <summary>
  166. /// 标记预运行状态并记录本次请求的检测模式(时域/空域),清除旧模式位避免切换时残留
  167. /// </summary>
  168. public void MarkPreRunning(RunningStatus mode)
  169. {
  170. RunningStatus = (RunningStatus & ~(RunningStatus.SpatialNormal | RunningStatus.TemporalNormal)) | mode | RunningStatus.PreRunning;
  171. PreStatusRunningMode = mode;
  172. PreStatusTimestamp = DateTime.UtcNow.Ticks;
  173. }
  174. /// <summary>
  175. /// 设备已确认命令:按位清除对应预状态位并合并实际状态,保留报警/故障等其他状态位
  176. /// </summary>
  177. public void ConfirmPreStatus(RunningStatus preFlag, RunningStatus actualStatus)
  178. {
  179. RunningStatus = (RunningStatus & ~preFlag) | actualStatus;
  180. if (preFlag == RunningStatus.PreRunning)
  181. {
  182. PreStatusRunningMode = 0;
  183. }
  184. // 仅当没有其他挂起的预状态位时才清零挂起时间戳,避免影响其他预状态的超时判断
  185. if ((RunningStatus & (RunningStatus.PreStop | RunningStatus.PreRunning | RunningStatus.PreCalibration)) == 0)
  186. {
  187. PreStatusTimestamp = 0;
  188. }
  189. }
  190. /// <summary>
  191. /// 清除所有挂起的预状态位(用于断线、超时等场景)
  192. /// </summary>
  193. public void ClearAllPreStatus()
  194. {
  195. RunningStatus &= ~(RunningStatus.PreStop | RunningStatus.PreRunning | RunningStatus.PreCalibration);
  196. PreStatusRunningMode = 0;
  197. PreStatusTimestamp = 0;
  198. }
  199. /// <summary>
  200. /// 运行方向
  201. /// </summary>
  202. public DirectionState Direction
  203. {
  204. get => direction;
  205. set { direction = value; OnPropertyChanged(nameof(Direction)); }
  206. }
  207. /// <summary>
  208. /// 故障类型
  209. /// </summary>
  210. public FaultType? FaultType
  211. {
  212. get => faultType;
  213. set { faultType = value; OnPropertyChanged(nameof(FaultType)); }
  214. }
  215. /// <summary>
  216. /// 速度
  217. /// </summary>
  218. public double Speed
  219. {
  220. get => speed;
  221. set { speed = value; OnPropertyChanged(nameof(Speed)); }
  222. }
  223. /// <summary>
  224. /// 速度变化率阈值
  225. /// </summary>
  226. public double VariationThreshold
  227. {
  228. get => variationThreshold;
  229. set { variationThreshold = value; OnPropertyChanged(nameof(VariationThreshold)); }
  230. }
  231. /// <summary>
  232. /// 速度是否稳定
  233. /// </summary>
  234. public bool IsSpeedStable
  235. {
  236. get => isSpeedStable;
  237. set { isSpeedStable = value; OnPropertyChanged(nameof(IsSpeedStable)); }
  238. }
  239. /// <summary>
  240. /// 位置
  241. /// </summary>
  242. public double Position
  243. {
  244. get => position;
  245. set { position = value; OnPropertyChanged(nameof(Position)); }
  246. }
  247. /// <summary>
  248. /// 绝对位置
  249. /// </summary>
  250. public double AbsolutePosition
  251. {
  252. get => absolutePosition;
  253. set { absolutePosition = value; OnPropertyChanged(nameof(AbsolutePosition)); }
  254. }
  255. /// <summary>
  256. /// 起升高度
  257. /// </summary>
  258. public double LiftHeight
  259. {
  260. get => liftHeight;
  261. set { liftHeight = value; OnPropertyChanged(nameof(LiftHeight)); }
  262. }
  263. /// <summary>
  264. /// 健康程度
  265. /// </summary>
  266. public RiskLevel RiskLevel
  267. {
  268. get => riskLevel;
  269. set { riskLevel = value; OnPropertyChanged(nameof(RiskLevel)); }
  270. }
  271. /// <summary>
  272. /// 平均损伤值
  273. /// </summary>
  274. public double AvgDamageValue
  275. {
  276. get => avgDamageValue;
  277. set { avgDamageValue = value; OnPropertyChanged(nameof(AvgDamageValue)); }
  278. }
  279. /// <summary>
  280. /// 最大损伤值
  281. /// </summary>
  282. public double MaxDamageValue
  283. {
  284. get => maxDamageValue;
  285. set { maxDamageValue = value; OnPropertyChanged(nameof(MaxDamageValue)); }
  286. }
  287. /// <summary>
  288. /// 损伤报警数量
  289. /// </summary>
  290. public int DamageCount
  291. {
  292. get => damageCount;
  293. set { damageCount = value; OnPropertyChanged(nameof(DamageCount)); }
  294. }
  295. /// <summary>
  296. /// 轻微损伤数量
  297. /// </summary>
  298. public int MildCount
  299. {
  300. get => mildCount;
  301. set { mildCount = value; OnPropertyChanged(nameof(MildCount)); }
  302. }
  303. /// <summary>
  304. /// 轻度损伤数量
  305. /// </summary>
  306. public int LightCount
  307. {
  308. get => lightCount;
  309. set { lightCount = value; OnPropertyChanged(nameof(LightCount)); }
  310. }
  311. /// <summary>
  312. /// 中度损伤数量
  313. /// </summary>
  314. public int ModerateCount
  315. {
  316. get => moderateCount;
  317. set { moderateCount = value; OnPropertyChanged(nameof(ModerateCount)); }
  318. }
  319. /// <summary>
  320. /// 较重损伤数量
  321. /// </summary>
  322. public int SevereCount
  323. {
  324. get => severeCount;
  325. set { severeCount = value; OnPropertyChanged(nameof(SevereCount)); }
  326. }
  327. /// <summary>
  328. /// 严重损伤数量
  329. /// </summary>
  330. public int CriticalCount
  331. {
  332. get => criticalCount;
  333. set { criticalCount = value; OnPropertyChanged(nameof(CriticalCount)); }
  334. }
  335. /// <summary>
  336. /// 超限损伤数量
  337. /// </summary>
  338. public int ExtremeCount
  339. {
  340. get => extremeCount;
  341. set { extremeCount = value; OnPropertyChanged(nameof(ExtremeCount)); }
  342. }
  343. /// <summary>
  344. /// 最后一次的检测数据
  345. /// </summary>
  346. public DetectionDataModel DetectionData
  347. {
  348. get => detectionData;
  349. set { detectionData = value; OnPropertyChanged(nameof(DetectionData)); }
  350. }
  351. /// <summary>
  352. /// 调试信息
  353. /// </summary>
  354. public DebugMessageModel DebugMessage
  355. {
  356. get => debugMessage;
  357. set { debugMessage = value; OnPropertyChanged(nameof(DebugMessage)); }
  358. }
  359. /// <summary>
  360. /// TCP 客户端
  361. /// </summary>
  362. public Socket ClientSocket
  363. {
  364. get => clientSocket;
  365. set { clientSocket = value; OnPropertyChanged(nameof(ClientSocket)); }
  366. }
  367. /// <summary>
  368. /// 是否报警中
  369. /// </summary>
  370. public bool IsAlerting
  371. {
  372. get => isAlerting;
  373. set { isAlerting = value; OnPropertyChanged(nameof(IsAlerting)); }
  374. }
  375. public bool IsHeartbeatReceived
  376. {
  377. get => isHeartbeatReceived;
  378. set
  379. {
  380. isHeartbeatReceived = value;
  381. OnPropertyChanged(nameof(IsHeartbeatReceived));
  382. if (value)
  383. {
  384. // 延迟一段时间后重置,以便动画可以重复触发
  385. Task.Delay(600).ContinueWith(_ =>
  386. {
  387. Application.Current.Dispatcher.Invoke(() =>
  388. {
  389. IsHeartbeatReceived = false;
  390. });
  391. });
  392. }
  393. }
  394. }
  395. /// <summary>
  396. /// 检测编号
  397. /// </summary>
  398. public uint DetectionNo
  399. {
  400. get => detectionNo;
  401. set { detectionNo = value; OnPropertyChanged(nameof(DetectionNo)); }
  402. }
  403. /// <summary>
  404. /// 禁用报警
  405. /// </summary>
  406. public bool DisableAlarm
  407. {
  408. get => disableAlarm;
  409. set { disableAlarm = value; OnPropertyChanged(nameof(DisableAlarm)); }
  410. }
  411. /// <summary>
  412. /// 在用传感器
  413. /// </summary>
  414. public int[] InUseSensor
  415. {
  416. get => inUseSensor;
  417. set { inUseSensor = value; OnPropertyChanged(nameof(InUseSensor)); }
  418. }
  419. public RealTimeLineChart Chart
  420. {
  421. get => chart;
  422. set
  423. {
  424. chart = value;
  425. }
  426. }
  427. public ObservableCollection<FaultDataModel> Faults { get; set; } = new ObservableCollection<FaultDataModel>();
  428. public ObservableCollection<AlarmDataModel> Alarms { get; set; } = new ObservableCollection<AlarmDataModel>();
  429. public ObservableCollection<LimitModel> Limits { get; set; } = new ObservableCollection<LimitModel>();
  430. public Queue<double> RecentSpeeds { get; internal set; } = new Queue<double>();
  431. private readonly object _liveStreamLock = new object();
  432. public Queue<int[]> LiveStreams { get; internal set; } = new Queue<int[]>();
  433. public ObservableCollection<int> LiveStreamValues { get; set; } = new ObservableCollection<int>();
  434. public void AddLiveStream(LiveStreamDataModel liveStream)
  435. {
  436. if (liveStream?.Data == null || liveStream.Data.Length == 0)
  437. return;
  438. lock (_liveStreamLock)
  439. {
  440. // 新数据到来时清空之前的实时流数据
  441. LiveStreams.Clear();
  442. int sensorCount = liveStream.SensorCount;
  443. int sampleCount = liveStream.Data.Length;
  444. // 本次与上次接收的时间间隔对应的采样点数量(每50ms 1个点)
  445. int targetCount = Math.Max(1, liveStream.SampleInterval / 50);
  446. int actualCount = Math.Min(targetCount, sampleCount);
  447. // 从 sampleCount 个采样点中平均抽取 actualCount 个点
  448. for (int i = 0; i < actualCount; i++)
  449. {
  450. int sourceIndex = actualCount == 1
  451. ? 0
  452. : (int)Math.Round((double)i * (sampleCount - 1) / (actualCount - 1));
  453. var point = new int[sensorCount];
  454. for (int s = 0; s < sensorCount; s++)
  455. {
  456. point[s] = liveStream.Data[sourceIndex][s];
  457. }
  458. LiveStreams.Enqueue(point);
  459. }
  460. }
  461. }
  462. /// <summary>
  463. /// 从实时流队列取出并移除一项(取出即移出)
  464. /// </summary>
  465. public bool TryDequeueLiveStream(out int[] sample)
  466. {
  467. lock (_liveStreamLock)
  468. {
  469. if (LiveStreams.Count > 0)
  470. {
  471. sample = LiveStreams.Dequeue();
  472. return true;
  473. }
  474. }
  475. sample = null;
  476. return false;
  477. }
  478. public void AddFault(FaultDataModel fault)
  479. {
  480. if (fault == null)
  481. return;
  482. // 无故障(HasFault 为 false):取消对应的故障(含编码器故障)
  483. if (!fault.HasFault)
  484. {
  485. RemoveFault(fault.FaultType);
  486. return;
  487. }
  488. // 有故障:查找同类型的已有故障
  489. var existingFault = Faults.FirstOrDefault(c => c.FaultType == fault.FaultType);
  490. if (existingFault == null)
  491. {
  492. // 无同类型故障,直接添加
  493. Faults.Add(fault);
  494. AddFaultMessage(fault);
  495. return;
  496. }
  497. // 已有同类型故障:判断故障传感器是否一致
  498. if (!existingFault.FaultySensors.SequenceEqual(fault.FaultySensors))
  499. {
  500. // 故障传感器不一致,更新为最新的故障信息
  501. existingFault.FaultCode = fault.FaultCode;
  502. UpdateFaultMessage(fault.FaultType, fault.FaultCode);
  503. }
  504. // 传感器一致则忽略,不重复添加
  505. }
  506. /// <summary>
  507. /// 取消指定类型的故障(含编码器故障)
  508. /// </summary>
  509. private void RemoveFault(FaultType faultType)
  510. {
  511. var existing = Faults.FirstOrDefault(c => c.FaultType == faultType);
  512. if (existing != null)
  513. {
  514. Faults.Remove(existing);
  515. }
  516. var message = App.DataCenter.Messages.FirstOrDefault(m => m.RopeName == RopeName);
  517. if (message != null)
  518. {
  519. Application.Current.Dispatcher.Invoke(() =>
  520. {
  521. message.Messages.RemoveAll(c => c.FaultType == faultType);
  522. });
  523. }
  524. }
  525. /// <summary>
  526. /// 添加故障到消息中心
  527. /// </summary>
  528. private void AddFaultMessage(FaultDataModel fault)
  529. {
  530. var message = App.DataCenter.Messages.FirstOrDefault(m => m.RopeName == RopeName);
  531. if (message == null)
  532. {
  533. Application.Current.Dispatcher.Invoke(() =>
  534. {
  535. App.DataCenter.Messages.Add(new FaultsMessageDto
  536. {
  537. RopeName = RopeName,
  538. Messages = new List<FaultDataModel> { fault }
  539. });
  540. });
  541. }
  542. else
  543. {
  544. Application.Current.Dispatcher.Invoke(() =>
  545. {
  546. message.Messages.Add(fault);
  547. });
  548. }
  549. }
  550. /// <summary>
  551. /// 更新消息中心中指定类型的故障码
  552. /// </summary>
  553. private void UpdateFaultMessage(FaultType faultType, int faultCode)
  554. {
  555. var message = App.DataCenter.Messages.FirstOrDefault(m => m.RopeName == RopeName);
  556. if (message != null)
  557. {
  558. Application.Current.Dispatcher.Invoke(() =>
  559. {
  560. var msgFault = message.Messages.FirstOrDefault(c => c.FaultType == faultType);
  561. if (msgFault != null)
  562. {
  563. msgFault.FaultCode = faultCode;
  564. }
  565. });
  566. }
  567. }
  568. public void AddSpeedData(double speed)
  569. {
  570. // 更新数据窗口
  571. RecentSpeeds.Enqueue(speed);
  572. if (RecentSpeeds.Count > 3)
  573. RecentSpeeds.Dequeue();
  574. if (RecentSpeeds.Count < 2)
  575. return;
  576. double avgSpeed = RecentSpeeds.Average();
  577. if (avgSpeed == 0)
  578. {
  579. IsSpeedStable = true;
  580. return;
  581. }
  582. // 计算当前速度相对于平均值的偏差
  583. bool _isStable = (Math.Abs(speed - avgSpeed) / avgSpeed) <= VariationThreshold;
  584. if (IsSpeedStable != _isStable)
  585. {
  586. IsSpeedStable = _isStable;
  587. }
  588. }
  589. public void AddAlarm(AlarmDataModel alarm)
  590. {
  591. DamageCount++;
  592. MaxDamageValue = alarm.DamageValue > MaxDamageValue ? alarm.DamageValue : MaxDamageValue;
  593. AvgDamageValue = (AvgDamageValue * (DamageCount - 1) + alarm.DamageValue) / DamageCount;
  594. switch (alarm.DamageLevel)
  595. {
  596. case DamageLevel.Mild:
  597. MildCount++;
  598. break;
  599. case DamageLevel.Light:
  600. LightCount++;
  601. break;
  602. case DamageLevel.Moderate:
  603. ModerateCount++;
  604. break;
  605. case DamageLevel.Severe:
  606. SevereCount++;
  607. break;
  608. case DamageLevel.Critical:
  609. CriticalCount++;
  610. break;
  611. case DamageLevel.ExceededLimit:
  612. ExtremeCount++;
  613. break;
  614. }
  615. Alarms.Add(alarm);
  616. RiskLevel = Alarms.Any() ? (RiskLevel)Alarms.Max(c => (int)c.DamageLevel) : RiskLevel.Normal;
  617. IsAlerting = true;
  618. Task.Run(() =>
  619. {
  620. Task.Delay(3000).Wait();
  621. IsAlerting = false;
  622. });
  623. }
  624. public void ClearData()
  625. {
  626. Speed = 0;
  627. MaxDamageValue = 0;
  628. AvgDamageValue = 0;
  629. DamageCount = 0;
  630. MildCount = 0;
  631. LightCount = 0;
  632. ModerateCount = 0;
  633. SevereCount = 0;
  634. CriticalCount = 0;
  635. ExtremeCount = 0;
  636. RiskLevel = RiskLevel.Normal;
  637. DetectionNo += 1;
  638. Application.Current.Dispatcher.Invoke(() => Alarms.Clear());
  639. Task.Run(async () =>
  640. {
  641. // 持续监控直到条件满足
  642. while (DetectionData?.RawDataReceiving == true)
  643. {
  644. await Task.Delay(100); // 避免CPU占用过高
  645. }
  646. // 条件满足时执行
  647. DetectionData = null;
  648. });
  649. }
  650. public event PropertyChangedEventHandler PropertyChanged;
  651. protected internal virtual void OnPropertyChanged(string propertyName)
  652. {
  653. PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
  654. }
  655. }
  656. }