From d45fbf3354d1a2b81d8577c1ebec23c3364f0261 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B0=B7=E6=88=90=E4=BC=9F?= Date: Thu, 12 Dec 2024 09:37:31 +0800 Subject: [PATCH 01/15] =?UTF-8?q?=E6=9B=B4=E6=94=B9=E6=97=A5=E5=BF=97?= =?UTF-8?q?=E7=BA=A7=E5=88=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../das/modules/node/service/impl/NodeMessageServiceImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/das/src/main/java/com/das/modules/node/service/impl/NodeMessageServiceImpl.java b/das/src/main/java/com/das/modules/node/service/impl/NodeMessageServiceImpl.java index 871df869..027c16d0 100644 --- a/das/src/main/java/com/das/modules/node/service/impl/NodeMessageServiceImpl.java +++ b/das/src/main/java/com/das/modules/node/service/impl/NodeMessageServiceImpl.java @@ -238,7 +238,7 @@ public class NodeMessageServiceImpl extends TextWebSocketHandler implements Node @Override public void handleData(TerminalMessage data) { JsonNode jsonNode = data.getData(); - log.info("收到消息:{}",data.getData()); + log.debug("收到消息:{}",data.getData()); String deviceId = jsonNode.get("deviceId").asText(); JsonNode values = jsonNode.get("values"); JsonNode archiveValues = jsonNode.get("archiveValues"); From 7d29d9ba91251f4e07492067578aa1432acede9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B0=B7=E6=88=90=E4=BC=9F?= Date: Thu, 12 Dec 2024 09:42:24 +0800 Subject: [PATCH 02/15] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=BF=83=E8=B7=B3?= =?UTF-8?q?=E6=8A=A5=E6=96=87BUG?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/das/modules/node/command/HeartbeatCommand.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/das/src/main/java/com/das/modules/node/command/HeartbeatCommand.java b/das/src/main/java/com/das/modules/node/command/HeartbeatCommand.java index 59281e29..6487f3a2 100644 --- a/das/src/main/java/com/das/modules/node/command/HeartbeatCommand.java +++ b/das/src/main/java/com/das/modules/node/command/HeartbeatCommand.java @@ -34,7 +34,7 @@ public class HeartbeatCommand implements BaseCommand{ for (JsonNode linkNode : links) { String linkId = linkNode.get("linkId").asText(); boolean online = linkNode.get("online").asBoolean(); - String key = String.format("link:%d:online", linkId, online); + String key = String.format("link:%s:online", linkId); adminRedisTemplate.set(key, online ? 1 : 0); adminRedisTemplate.expire(key, HEARTBEAT_TTL); } From d29319fbfc94e75b20087814b9bc6c79c0ecd4c8 Mon Sep 17 00:00:00 2001 From: fengrong Date: Thu, 12 Dec 2024 09:52:50 +0800 Subject: [PATCH 03/15] =?UTF-8?q?=E9=A6=96=E9=A1=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ui/dasadmin/src/views/backend/dashboard.vue | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/ui/dasadmin/src/views/backend/dashboard.vue b/ui/dasadmin/src/views/backend/dashboard.vue index 726d2645..de45cb83 100644 --- a/ui/dasadmin/src/views/backend/dashboard.vue +++ b/ui/dasadmin/src/views/backend/dashboard.vue @@ -341,23 +341,6 @@ const currentDayStatus = ref({ }) const deviceCode = ref([]) const FanList = ref([]) -const getRealTimeState = (data: any) => { - if (data.iturbineoperationmode) { - if (data.iturbineoperationmode > 1 && data.iturbineoperationmode < 6) { - return 2 - } - if (data.iturbineoperationmode === 21) { - return 20 - } - return data.iturbineoperationmode - } else if (data.iyplevel === 10) { - return 1110 - } else if (data.gridlostdetected === 1) { - return 1111 - } else if (data.ibplevel === 200) { - return 1112 - } -} const StatusListData = () => { getWindTurbineMatrixData().then((res) => { if (res.code == 200) { From 545002aea1e07e0eda5535e8440cc09ac244bb86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B0=B7=E6=88=90=E4=BC=9F?= Date: Thu, 12 Dec 2024 10:01:50 +0800 Subject: [PATCH 04/15] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E6=97=A5=E5=BF=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../modules/node/command/HeartbeatCommand.java | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/das/src/main/java/com/das/modules/node/command/HeartbeatCommand.java b/das/src/main/java/com/das/modules/node/command/HeartbeatCommand.java index 6487f3a2..aad67993 100644 --- a/das/src/main/java/com/das/modules/node/command/HeartbeatCommand.java +++ b/das/src/main/java/com/das/modules/node/command/HeartbeatCommand.java @@ -25,31 +25,46 @@ public class HeartbeatCommand implements BaseCommand{ @Autowired CacheService cacheService; + /** + * 执行命令方法 + * 该方法处理接收到的终端消息,特别是心跳报文中的设备和链路在线状态更新 + * + * @param data 包含心跳报文信息的TerminalMessage对象 + */ @Override public void doCommand(TerminalMessage data) { + log.info("收到[heartbeat]报文"); + + // 解析心跳报文中的数据信息 JsonNode dataInfo = data.getData(); if (!dataInfo.isEmpty()) { + // 处理链路信息 JsonNode links = data.getData().get("links"); if (links != null && links.isArray()) { for (JsonNode linkNode : links) { String linkId = linkNode.get("linkId").asText(); boolean online = linkNode.get("online").asBoolean(); String key = String.format("link:%s:online", linkId); + // 更新链路在线状态到Redis adminRedisTemplate.set(key, online ? 1 : 0); adminRedisTemplate.expire(key, HEARTBEAT_TTL); } } + + // 处理设备信息 JsonNode devices = data.getData().get("devices"); if (devices != null && devices.isArray()) { for (JsonNode device : devices) { long deviceId = device.get("deviceId").asLong(); boolean online = device.get("online").asBoolean(); + // 获取设备缓存信息 DeviceInfoCache deviceInfoCacheById = cacheService.getEquipmentCache().getDeviceInfoCacheById(deviceId); if (deviceInfoCacheById == null || !deviceInfoCacheById.getObjectType().equals(EquipmentTypeIds.EQUIPMENT_TYPE_STATION_WTG)) { continue; } - //判断是不是风机 + // 判断是不是风机 String keyDeviceOnline = String.format("device:%d:online", deviceId); + // 更新设备在线状态到Redis adminRedisTemplate.set(keyDeviceOnline, online ? 1 : 0); adminRedisTemplate.expire(keyDeviceOnline, HEARTBEAT_TTL); @@ -57,6 +72,7 @@ public class HeartbeatCommand implements BaseCommand{ String keyCommFaultState = String.format("RT:%d:commfaultstate",deviceId); Integer plcDeviceStatus = adminRedisTemplate.get(keyPLCDeviceStatus); log.debug("设备ID:{},在线状态:{},通讯状态: {}", deviceId, online, plcDeviceStatus); + // 根据设备在线状态和通讯状态更新通讯故障状态 if (plcDeviceStatus == null){ adminRedisTemplate.set(keyCommFaultState, online ? 0 : 1); } From 3d713221af0dbeb9685ab0e76a7b3dfb9d62d913 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B0=B7=E6=88=90=E4=BC=9F?= Date: Thu, 12 Dec 2024 10:02:40 +0800 Subject: [PATCH 05/15] =?UTF-8?q?=E4=BC=98=E5=8C=96import?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/das/modules/node/command/HeartbeatCommand.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/das/src/main/java/com/das/modules/node/command/HeartbeatCommand.java b/das/src/main/java/com/das/modules/node/command/HeartbeatCommand.java index aad67993..53801235 100644 --- a/das/src/main/java/com/das/modules/node/command/HeartbeatCommand.java +++ b/das/src/main/java/com/das/modules/node/command/HeartbeatCommand.java @@ -2,7 +2,6 @@ package com.das.modules.node.command; import com.das.common.constant.EquipmentTypeIds; import com.das.common.utils.AdminRedisTemplate; -import com.das.common.utils.StringUtils; import com.das.modules.cache.domain.DeviceInfoCache; import com.das.modules.cache.service.CacheService; import com.das.modules.node.constant.NodeConstant; @@ -12,8 +11,6 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; -import java.text.MessageFormat; - @Slf4j @Service(value = NodeConstant.HEARTBEAT) public class HeartbeatCommand implements BaseCommand{ From 4928994d43ac995d290c0cb773d5ed5288dbce9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B0=B7=E6=88=90=E4=BC=9F?= Date: Thu, 12 Dec 2024 10:10:38 +0800 Subject: [PATCH 06/15] =?UTF-8?q?=E5=8A=A0=E5=BF=AB=E5=BF=83=E8=B7=B3TTL?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/das/modules/node/command/HeartbeatCommand.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/das/src/main/java/com/das/modules/node/command/HeartbeatCommand.java b/das/src/main/java/com/das/modules/node/command/HeartbeatCommand.java index 53801235..f4500301 100644 --- a/das/src/main/java/com/das/modules/node/command/HeartbeatCommand.java +++ b/das/src/main/java/com/das/modules/node/command/HeartbeatCommand.java @@ -15,7 +15,7 @@ import org.springframework.stereotype.Service; @Service(value = NodeConstant.HEARTBEAT) public class HeartbeatCommand implements BaseCommand{ - public static final long HEARTBEAT_TTL = 60L; + public static final long HEARTBEAT_TTL = 12L; @Autowired AdminRedisTemplate adminRedisTemplate; From d0a287624de076cc54b49f751a1b36329ea09970 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=AB=98=E4=BA=91=E9=B9=8F?= Date: Thu, 12 Dec 2024 10:23:46 +0800 Subject: [PATCH 07/15] =?UTF-8?q?=E8=8A=82=E7=82=B9=EF=BC=9A=E4=BF=AE?= =?UTF-8?q?=E6=94=B9ads=E5=8D=8F=E8=AE=AE=E5=8F=82=E6=95=B0=E6=98=BE?= =?UTF-8?q?=E7=A4=BA=E9=80=BB=E8=BE=91=20=E5=8D=95=E9=A3=8E=E6=9C=BA?= =?UTF-8?q?=EF=BC=9A=E9=A3=8E=E6=9C=BA=E7=8A=B6=E6=80=81=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E9=80=9A=E8=AE=AF=E4=B8=AD=E6=96=AD=20=E9=A3=8E=E6=9C=BA?= =?UTF-8?q?=E5=88=97=E8=A1=A8=EF=BC=9A=E9=A3=8E=E6=9C=BA=E7=8A=B6=E6=80=81?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E9=80=9A=E8=AE=AF=E4=B8=AD=E6=96=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/views/backend/WindBlower/index.vue | 2 ++ .../src/views/backend/energyManage/index.vue | 12 +++---- .../backend/equipment/airBlower/index.vue | 4 +++ .../src/views/backend/node/protocol.vue | 36 +++++++++---------- 4 files changed, 28 insertions(+), 26 deletions(-) diff --git a/ui/dasadmin/src/views/backend/WindBlower/index.vue b/ui/dasadmin/src/views/backend/WindBlower/index.vue index 8f9708a1..fd2c3e68 100644 --- a/ui/dasadmin/src/views/backend/WindBlower/index.vue +++ b/ui/dasadmin/src/views/backend/WindBlower/index.vue @@ -987,6 +987,8 @@ const realTimeDataState = computed(() => { return '停机' case 11: return '待机' + case 33: + return '通讯中断' case 1110: return '解缆状态' case 1111: diff --git a/ui/dasadmin/src/views/backend/energyManage/index.vue b/ui/dasadmin/src/views/backend/energyManage/index.vue index 730cfd88..853f984d 100644 --- a/ui/dasadmin/src/views/backend/energyManage/index.vue +++ b/ui/dasadmin/src/views/backend/energyManage/index.vue @@ -174,7 +174,7 @@
33
-
MW
+
MVar
@@ -184,7 +184,7 @@
6
-
MW
+
MVar
@@ -227,17 +227,17 @@
-
AVC可增有功
+
AVC可增无功
5
-
MW
+
MVar
-
AVC可减有功
+
AVC可减无功
5
-
MW
+
MVar
diff --git a/ui/dasadmin/src/views/backend/equipment/airBlower/index.vue b/ui/dasadmin/src/views/backend/equipment/airBlower/index.vue index 8be75286..60984ca8 100644 --- a/ui/dasadmin/src/views/backend/equipment/airBlower/index.vue +++ b/ui/dasadmin/src/views/backend/equipment/airBlower/index.vue @@ -272,6 +272,10 @@ const airBlowerSelectOptions = reactive<{ [K in SelectTypeKeyUnionType]: { label label: '停机', value: 2, }, + { + label: '通讯中断', + value: 33, + }, { label: '解缆状态', value: 1110, diff --git a/ui/dasadmin/src/views/backend/node/protocol.vue b/ui/dasadmin/src/views/backend/node/protocol.vue index a9423e26..ae9d6dea 100644 --- a/ui/dasadmin/src/views/backend/node/protocol.vue +++ b/ui/dasadmin/src/views/backend/node/protocol.vue @@ -1052,6 +1052,22 @@ const formColumnList: formColumnType[] = [ value: true, }, }, + { + key: 'adsUser', + data: { + label: 'ADS账号', + type: 'input', + value: '', + }, + }, + { + key: 'adsPassword', + data: { + label: 'ADS密码', + type: 'password', + value: '', + }, + }, { key: 'ftpMode', data: { @@ -1101,26 +1117,6 @@ const formColumnList: formColumnType[] = [ showValue: '1', }, }, - { - key: 'adsUser', - data: { - label: 'ADS账号', - type: 'input', - value: '', - showKey: 'ftpMode', - showValue: '1', - }, - }, - { - key: 'adsPassword', - data: { - label: 'ADS密码', - type: 'password', - value: '', - showKey: 'ftpMode', - showValue: '1', - }, - }, ], timeOutOption: [], otherOption: [], From d0c6aa5b9ba4ceb9f86945853a941b260ab2a66c Mon Sep 17 00:00:00 2001 From: fengrong Date: Thu, 12 Dec 2024 10:40:48 +0800 Subject: [PATCH 08/15] =?UTF-8?q?=E9=A6=96=E9=A1=B5=EF=BC=9A=E4=BF=AE?= =?UTF-8?q?=E6=94=B9=E9=A3=8E=E6=9C=BA=E7=8A=B6=E6=80=81=E5=92=8C=E8=83=8C?= =?UTF-8?q?=E6=99=AF=E8=89=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/views/backend/home/windMatrix.vue | 31 ++++++++++++------- 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/ui/dasadmin/src/views/backend/home/windMatrix.vue b/ui/dasadmin/src/views/backend/home/windMatrix.vue index 5d0ae852..03bfcc45 100644 --- a/ui/dasadmin/src/views/backend/home/windMatrix.vue +++ b/ui/dasadmin/src/views/backend/home/windMatrix.vue @@ -6,32 +6,35 @@ @click="handleClick(item)" @contextmenu.prevent="windContextMenu($event,item)" > -
-
- - +
+ +
+ + {{ item.name }} 并网 待机 启动 维护 + >维护 离线 限功率运行 正常停机 外部因素导致停机 + >外部因素导致停机 停机 解缆状态 电网故障停机 + >电网故障停机 安全链停机 + >安全链停机 + 通讯中断
@@ -254,6 +257,10 @@ const sendManualCommand = (type: 1 | 0) => { background-image: linear-gradient(180deg, #f0f6ff 0%, #ffffff 50%); border: 1px solid #e1edf6; } +.wind-offline { + background-image: linear-gradient(180deg, #eeeeee 0%, #eeeeee 50%); + border: 1px solid #eeeeee; +} .wind-picture { display: none; } From 65b4650f969d6a0b1ba4ca7646532fabb6cd22ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B0=B7=E6=88=90=E4=BC=9F?= Date: Thu, 12 Dec 2024 10:44:32 +0800 Subject: [PATCH 09/15] =?UTF-8?q?=E6=96=B0=E5=A2=9EisOnline=E5=87=BD?= =?UTF-8?q?=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/com/das/modules/calc/service/CalcService.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/das/src/main/java/com/das/modules/calc/service/CalcService.java b/das/src/main/java/com/das/modules/calc/service/CalcService.java index c9669f0e..3b1ed193 100644 --- a/das/src/main/java/com/das/modules/calc/service/CalcService.java +++ b/das/src/main/java/com/das/modules/calc/service/CalcService.java @@ -138,6 +138,9 @@ public class CalcService { FunctionWindSpeedFactor windSpeedFactor = new FunctionWindSpeedFactor(dataService,cacheService); aviator.addFunction(windSpeedFactor); + + FunctionIsOnline isOnline = new FunctionIsOnline(adminRedisTemplate, cacheService); + aviator.addFunction(isOnline); } /** From 9572a597cf0f1de8e4c8ca1b957911bb2639a82a Mon Sep 17 00:00:00 2001 From: geting <13585118195@163.com> Date: Thu, 12 Dec 2024 10:53:43 +0800 Subject: [PATCH 10/15] =?UTF-8?q?=E5=8A=9F=E7=8E=87=E6=9B=B2=E7=BA=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/views/backend/statAnalysis/powerCurveAnalysis.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/dasadmin/src/views/backend/statAnalysis/powerCurveAnalysis.vue b/ui/dasadmin/src/views/backend/statAnalysis/powerCurveAnalysis.vue index 3c2fdfe5..46a52313 100644 --- a/ui/dasadmin/src/views/backend/statAnalysis/powerCurveAnalysis.vue +++ b/ui/dasadmin/src/views/backend/statAnalysis/powerCurveAnalysis.vue @@ -414,7 +414,7 @@ const statAnalysisExport = () => { const calculateAverages = (data: any) => { let maxWindSpeed = Math.max(...data.map((item: any) => item[0])) - let interval = 5 // 每5m/s一个区间 + let interval = 0.5 let result = [] for (let windSpeed = 0; windSpeed <= maxWindSpeed; windSpeed += interval) { From 1590808256923e1b9459e03ae439264aacfe9471 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=AB=98=E4=BA=91=E9=B9=8F?= Date: Thu, 12 Dec 2024 11:11:32 +0800 Subject: [PATCH 11/15] =?UTF-8?q?=E8=8A=82=E7=82=B9=EF=BC=9A=E5=85=AC?= =?UTF-8?q?=E5=85=B1=E5=B1=9E=E6=80=A7=E6=8A=BD=E7=A6=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ui/dasadmin/src/views/backend/node/index.vue | 9 +-------- ui/dasadmin/src/views/backend/node/utils.ts | 9 +++++++++ 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/ui/dasadmin/src/views/backend/node/index.vue b/ui/dasadmin/src/views/backend/node/index.vue index a7f784b5..325f5b44 100644 --- a/ui/dasadmin/src/views/backend/node/index.vue +++ b/ui/dasadmin/src/views/backend/node/index.vue @@ -178,6 +178,7 @@ import { uploadNodeReq, submitNodeConfigReq, } from '/@/api/backend/node/request' +import {protocolList} from '/@/views/backend/node/utils' import { getInstitutionalTreeListReq } from '/@/api/backend/org/request' import { debounce } from 'lodash-es' import ProtocolComponent from './protocol.vue' @@ -530,14 +531,6 @@ const getLinkData = (nodeId: string, linkName?: string) => { }) } -const protocolList = [ - // * 代表需要配置的协议 - { label: 'IEC104从 *', value: 9 }, - { label: 'MODBUSTCP从 *', value: 17 }, - { label: 'MODBUS', value: 80 }, - { label: 'ADS', value: 81 }, - { label: '故障日志', value: 79 }, -] const protocolPartVisible = ref(false) const protocolDisabled = ref(false) diff --git a/ui/dasadmin/src/views/backend/node/utils.ts b/ui/dasadmin/src/views/backend/node/utils.ts index 9b43f74d..c5cc5c34 100644 --- a/ui/dasadmin/src/views/backend/node/utils.ts +++ b/ui/dasadmin/src/views/backend/node/utils.ts @@ -1,5 +1,14 @@ import { LocaleType, BooleanNumber, SheetTypes } from '@univerjs/core' +export const protocolList = [ + // * 代表需要配置的协议 + { label: 'IEC104从 *', value: 9 }, + { label: 'MODBUSTCP从 *', value: 17 }, + { label: 'MODBUS', value: 80 }, + { label: 'ADS', value: 81 }, + { label: '故障日志', value: 79 }, +] + export const excelDefaultConfig: any = { // IEC104从 9: { From 61397c739bb6263adcd6d1057f87f989ff15b880 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=83=81=E4=B8=87=E6=88=90?= Date: Thu, 12 Dec 2024 11:15:40 +0800 Subject: [PATCH 12/15] =?UTF-8?q?=E9=93=BE=E8=B7=AF=E7=8A=B6=E6=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/views/backend/linkMonitor/index.vue | 63 +++++++++++++------ .../src/views/backend/linkMonitor/type.ts | 1 + 2 files changed, 44 insertions(+), 20 deletions(-) diff --git a/ui/dasadmin/src/views/backend/linkMonitor/index.vue b/ui/dasadmin/src/views/backend/linkMonitor/index.vue index ac46229d..db19a60d 100644 --- a/ui/dasadmin/src/views/backend/linkMonitor/index.vue +++ b/ui/dasadmin/src/views/backend/linkMonitor/index.vue @@ -46,12 +46,7 @@ @@ -102,13 +97,13 @@