From da63ef5b1988c85675d62e853dbce78c163d3736 Mon Sep 17 00:00:00 2001 From: che_shuai Date: Fri, 22 Sep 2023 11:34:10 +0800 Subject: [PATCH 1/7] =?UTF-8?q?=E6=8E=A8=E6=B5=81=E5=88=97=E8=A1=A8-->?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E9=80=9A=E9=81=93=20=E8=A1=A5=E5=85=85?= =?UTF-8?q?=E6=92=AD=E6=94=BE=E9=9C=80=E8=A6=81=E7=9A=84=E9=BB=98=E8=AE=A4?= =?UTF-8?q?=E5=8F=82=E6=95=B0=20=20=E5=AA=92=E4=BD=93=E6=9C=8D=E5=8A=A1?= =?UTF-8?q?=E5=99=A8ID,=E6=98=AF=E5=90=A6=E6=9C=AC=E5=B9=B3=E5=8F=B0?= =?UTF-8?q?=E6=8E=A8=E9=80=81,=E6=98=AF=E5=90=A6=E6=AD=A3=E5=9C=A8?= =?UTF-8?q?=E6=8E=A8=E9=80=81(=E5=BD=B1=E5=93=8D=E6=92=AD=E6=94=BE?= =?UTF-8?q?=E6=8C=89=E9=92=AE=E6=98=AF=E5=90=A6=E6=98=BE=E7=A4=BA)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../genersoft/iot/vmp/service/impl/StreamPushServiceImpl.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/com/genersoft/iot/vmp/service/impl/StreamPushServiceImpl.java b/src/main/java/com/genersoft/iot/vmp/service/impl/StreamPushServiceImpl.java index dcaab9e3..7b9664b0 100755 --- a/src/main/java/com/genersoft/iot/vmp/service/impl/StreamPushServiceImpl.java +++ b/src/main/java/com/genersoft/iot/vmp/service/impl/StreamPushServiceImpl.java @@ -506,6 +506,9 @@ public class StreamPushServiceImpl implements IStreamPushService { stream.setUpdateTime(DateUtil.getNow()); stream.setCreateTime(DateUtil.getNow()); stream.setServerId(userSetting.getServerId()); + stream.setMediaServerId(mediaConfig.getId()); + stream.setSelf(true); + stream.setPushIng(true); // 放在事务内执行 boolean result = false; From f6c1ce15e4ce1e30cf3328bb89cf02b06eadf776 Mon Sep 17 00:00:00 2001 From: AlphaWu Date: Sun, 1 Oct 2023 12:29:48 +0800 Subject: [PATCH 2/7] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E5=BA=93=E8=84=9A=E6=9C=AC=E7=9A=84BUG=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sql/2.6.9更新.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sql/2.6.9更新.sql b/sql/2.6.9更新.sql index 769004d2..f8f44d95 100644 --- a/sql/2.6.9更新.sql +++ b/sql/2.6.9更新.sql @@ -5,4 +5,4 @@ alter table wvp_platform add auto_push_channel bool default false alter table wvp_stream_proxy - add stream_key varying(255) + add stream_key character varying(255) From 4084080a8e8162260afc4733865c347a8a816d49 Mon Sep 17 00:00:00 2001 From: xubinbin <1323875150@qq.com> Date: Tue, 10 Oct 2023 15:17:06 +0800 Subject: [PATCH 3/7] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=9C=A8=E4=BF=AE?= =?UTF-8?q?=E6=94=B9=E8=AE=BE=E5=A4=87=E4=BF=A1=E6=81=AF=E7=9A=84=E7=A7=BB?= =?UTF-8?q?=E5=8A=A8=E4=BD=8D=E7=BD=AE=E8=AE=A2=E9=98=85=E7=94=B1=E9=9D=9E?= =?UTF-8?q?0=E5=80=BC=E6=94=B9=E4=B8=BA0=E5=80=BC=E6=97=B6=E4=B8=8D?= =?UTF-8?q?=E7=94=9F=E6=95=88=E7=9A=84=E9=97=AE=E9=A2=98=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/genersoft/iot/vmp/service/impl/DeviceServiceImpl.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/com/genersoft/iot/vmp/service/impl/DeviceServiceImpl.java b/src/main/java/com/genersoft/iot/vmp/service/impl/DeviceServiceImpl.java index 0d99eccd..f0e3f230 100755 --- a/src/main/java/com/genersoft/iot/vmp/service/impl/DeviceServiceImpl.java +++ b/src/main/java/com/genersoft/iot/vmp/service/impl/DeviceServiceImpl.java @@ -544,6 +544,8 @@ public class DeviceServiceImpl implements IDeviceService { } }else if (device.getSubscribeCycleForMobilePosition() == 0) { if (deviceInStore.getSubscribeCycleForMobilePosition() != 0) { + deviceInStore.setMobilePositionSubmissionInterval(device.getMobilePositionSubmissionInterval()); + deviceInStore.setSubscribeCycleForMobilePosition(device.getSubscribeCycleForMobilePosition()); // 取消订阅 removeMobilePositionSubscribe(deviceInStore); } From f5d89e06a3915a7e3d0032673f2849a42449c190 Mon Sep 17 00:00:00 2001 From: chenzhangyue Date: Wed, 11 Oct 2023 13:52:08 +0800 Subject: [PATCH 4/7] =?UTF-8?q?feat:=E6=94=AF=E6=8C=81=E4=BB=A3=E7=90=86?= =?UTF-8?q?=E6=8B=89=E6=B5=81=E7=8A=B6=E6=80=81=E6=A3=80=E6=B5=8B=EF=BC=8C?= =?UTF-8?q?=E7=A6=BB=E7=BA=BF=E6=9F=A5=E8=AF=A2=E6=B5=81=E6=98=AF=E5=90=A6?= =?UTF-8?q?=E7=A6=BB=E7=BA=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../iot/vmp/media/zlm/ZLMRESTfulUtils.java | 15 +++++++ .../iot/vmp/media/zlm/ZLMRunner.java | 1 + .../service/impl/StreamProxyServiceImpl.java | 43 +++++++++++++++++++ 3 files changed, 59 insertions(+) diff --git a/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRESTfulUtils.java b/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRESTfulUtils.java index 84e9e7e6..52bc9028 100755 --- a/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRESTfulUtils.java +++ b/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRESTfulUtils.java @@ -215,6 +215,21 @@ public class ZLMRESTfulUtils { } } + public JSONObject isMediaOnline(MediaServerItem mediaServerItem, String app, String stream, String schema){ + Map param = new HashMap<>(); + if (app != null) { + param.put("app",app); + } + if (stream != null) { + param.put("stream",stream); + } + if (schema != null) { + param.put("schema",schema); + } + param.put("vhost","__defaultVhost__"); + return sendPost(mediaServerItem, "isMediaOnline", param, null); + } + public JSONObject getMediaList(MediaServerItem mediaServerItem, String app, String stream, String schema, RequestCallback callback){ Map param = new HashMap<>(); if (app != null) { diff --git a/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRunner.java b/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRunner.java index 6e594024..4a781f31 100755 --- a/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRunner.java +++ b/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRunner.java @@ -9,6 +9,7 @@ import com.genersoft.iot.vmp.gb28181.event.EventPublisher; import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeFactory; import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeForServerStarted; import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem; +import com.genersoft.iot.vmp.media.zlm.dto.StreamProxyItem; import com.genersoft.iot.vmp.service.IMediaServerService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/src/main/java/com/genersoft/iot/vmp/service/impl/StreamProxyServiceImpl.java b/src/main/java/com/genersoft/iot/vmp/service/impl/StreamProxyServiceImpl.java index 7fbe7691..eac543a3 100755 --- a/src/main/java/com/genersoft/iot/vmp/service/impl/StreamProxyServiceImpl.java +++ b/src/main/java/com/genersoft/iot/vmp/service/impl/StreamProxyServiceImpl.java @@ -35,15 +35,19 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.datasource.DataSourceTransactionManager; +import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import org.springframework.transaction.TransactionDefinition; import org.springframework.transaction.TransactionStatus; +import org.springframework.util.CollectionUtils; import org.springframework.util.ObjectUtils; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; +import java.util.function.Function; +import java.util.stream.Collectors; /** * 视频代理业务 @@ -560,4 +564,43 @@ public class StreamProxyServiceImpl implements IStreamProxyService { return new ResourceBaseInfo(total, online); } + + + @Scheduled(cron = "* 0/10 * * * ?") + public void asyncCheckStreamProxyStatus() { + + List all = mediaServerService.getAllOnline(); + + if (CollectionUtils.isEmpty(all)){ + return; + } + + Map serverItemMap = all.stream().collect(Collectors.toMap(MediaServerItem::getId, Function.identity(), (m1, m2) -> m1)); + + List list = videoManagerStorager.getStreamProxyListForEnable(true); + + if (CollectionUtils.isEmpty(list)){ + return; + } + + for (StreamProxyItem streamProxyItem : list) { + + MediaServerItem mediaServerItem = serverItemMap.get(streamProxyItem.getMediaServerId()); + + // TODO 支持其他 schema + JSONObject mediaInfo = zlmresTfulUtils.isMediaOnline(mediaServerItem, streamProxyItem.getApp(), streamProxyItem.getStream(), "rtsp"); + + if (mediaInfo == null){ + streamProxyItem.setStatus(false); + } else { + if (mediaInfo.getInteger("code") == 0 && mediaInfo.getBoolean("online")) { + streamProxyItem.setStatus(true); + } else { + streamProxyItem.setStatus(false); + } + } + + updateStreamProxy(streamProxyItem); + } + } } From 613399cc6d14cfe5b8a245d462629ecee5deb2db Mon Sep 17 00:00:00 2001 From: xiaoQQya Date: Tue, 17 Oct 2023 17:49:31 +0800 Subject: [PATCH 5/7] =?UTF-8?q?fix(play):=20=E4=BF=AE=E5=A4=8D=E5=8D=95?= =?UTF-8?q?=E7=AB=AF=E5=8F=A3=E6=8E=A8=E6=B5=81=E4=B8=8B=E7=BA=A7=E8=87=AA?= =?UTF-8?q?=E5=AE=9A=E4=B9=89=20ssrc=20=E6=97=B6,=20=E6=B5=81=E6=B3=A8?= =?UTF-8?q?=E5=86=8C=E5=90=8E=E6=8E=A5=E5=8F=A3=E4=BB=8D=E7=84=B6=E8=B6=85?= =?UTF-8?q?=E6=97=B6=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../genersoft/iot/vmp/service/impl/InviteStreamServiceImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/genersoft/iot/vmp/service/impl/InviteStreamServiceImpl.java b/src/main/java/com/genersoft/iot/vmp/service/impl/InviteStreamServiceImpl.java index d630a2c0..752d0631 100755 --- a/src/main/java/com/genersoft/iot/vmp/service/impl/InviteStreamServiceImpl.java +++ b/src/main/java/com/genersoft/iot/vmp/service/impl/InviteStreamServiceImpl.java @@ -257,7 +257,7 @@ public class InviteStreamServiceImpl implements IInviteStreamService { ":" + inviteInfo.getDeviceId() + ":" + inviteInfo.getChannelId() + ":" + inviteInfo.getStream() + - ":" + inviteInfo.getSsrcInfo().getSsrc(); + ":" + ssrc; if (inviteInfoInDb.getSsrcInfo() != null) { inviteInfoInDb.getSsrcInfo().setSsrc(ssrc); } From 26ca854db578b11d2dbcc475cf475c314cb08ba6 Mon Sep 17 00:00:00 2001 From: lishuyuan Date: Fri, 27 Oct 2023 11:09:42 +0800 Subject: [PATCH 6/7] =?UTF-8?q?bugfix=EF=BC=9A=E5=8E=BB=E6=8E=89=E7=9B=AE?= =?UTF-8?q?=E5=BD=95=E8=AE=A2=E9=98=85=E9=80=9A=E7=9F=A5=E5=A4=84=E7=90=86?= =?UTF-8?q?=E9=87=8D=E5=A4=8D=E9=80=BB=E8=BE=91=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../request/impl/NotifyRequestProcessor.java | 109 ------------------ 1 file changed, 109 deletions(-) diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/NotifyRequestProcessor.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/NotifyRequestProcessor.java index dbe49d5d..d35c6a63 100755 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/NotifyRequestProcessor.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/NotifyRequestProcessor.java @@ -132,7 +132,6 @@ public class NotifyRequestProcessor extends SIPRequestProcessorParent implements if (CmdType.CATALOG.equals(cmd)) { logger.info("接收到Catalog通知"); - processNotifyCatalogList(take.getEvt()); notifyRequestForCatalogProcessor.process(take.getEvt()); } else if (CmdType.ALARM.equals(cmd)) { logger.info("接收到Alarm通知"); @@ -371,114 +370,6 @@ public class NotifyRequestProcessor extends SIPRequestProcessorParent implements } } - /*** - * 处理catalog设备目录列表Notify - * - * @param evt - */ - private void processNotifyCatalogList(RequestEvent evt) { - try { - FromHeader fromHeader = (FromHeader) evt.getRequest().getHeader(FromHeader.NAME); - String deviceId = SipUtils.getUserIdFromFromHeader(fromHeader); - - Device device = redisCatchStorage.getDevice(deviceId); - if (device == null || !device.isOnLine()) { - logger.warn("[收到目录订阅]:{}, 但是设备已经离线", (device != null ? device.getDeviceId():"" )); - return; - } - Element rootElement = getRootElement(evt, device.getCharset()); - if (rootElement == null) { - logger.warn("[ 收到目录订阅 ] content cannot be null, {}", evt.getRequest()); - return; - } - Element deviceListElement = rootElement.element("DeviceList"); - if (deviceListElement == null) { - return; - } - Iterator deviceListIterator = deviceListElement.elementIterator(); - if (deviceListIterator != null) { - - // 遍历DeviceList - while (deviceListIterator.hasNext()) { - Element itemDevice = deviceListIterator.next(); - Element channelDeviceElement = itemDevice.element("DeviceID"); - if (channelDeviceElement == null) { - continue; - } - Element eventElement = itemDevice.element("Event"); - String event; - if (eventElement == null) { - logger.warn("[收到目录订阅]:{}, 但是Event为空, 设为默认值 ADD", (device != null ? device.getDeviceId():"" )); - event = CatalogEvent.ADD; - }else { - event = eventElement.getText().toUpperCase(); - } - DeviceChannel channel = XmlUtil.channelContentHandler(itemDevice, device, event, civilCodeFileConf); - if (channel == null) { - logger.info("[收到目录订阅]:但是解析失败 {}", new String(evt.getRequest().getRawContent())); - continue; - } - if (channel.getParentId() != null && channel.getParentId().equals(sipConfig.getId())) { - channel.setParentId(null); - } - channel.setDeviceId(device.getDeviceId()); - logger.info("[收到目录订阅]:{}/{}", device.getDeviceId(), channel.getChannelId()); - switch (event) { - case CatalogEvent.ON: - // 上线 - logger.info("[收到通道上线通知] 来自设备: {}, 通道 {}", device.getDeviceId(), channel.getChannelId()); - storager.deviceChannelOnline(deviceId, channel.getChannelId()); - break; - case CatalogEvent.OFF : - // 离线 - logger.info("[收到通道离线通知] 来自设备: {}, 通道 {}", device.getDeviceId(), channel.getChannelId()); - if (userSetting.getRefuseChannelStatusChannelFormNotify()) { - storager.deviceChannelOffline(deviceId, channel.getChannelId()); - }else { - logger.info("[收到通道离线通知] 但是平台已配置拒绝此消息,来自设备: {}, 通道 {}", device.getDeviceId(), channel.getChannelId()); - } - break; - case CatalogEvent.VLOST: - // 视频丢失 - logger.info("[收到通道视频丢失通知] 来自设备: {}, 通道 {}", device.getDeviceId(), channel.getChannelId()); - if (userSetting.getRefuseChannelStatusChannelFormNotify()) { - storager.deviceChannelOffline(deviceId, channel.getChannelId()); - }else { - logger.info("[收到通道视频丢失通知] 但是平台已配置拒绝此消息,来自设备: {}, 通道 {}", device.getDeviceId(), channel.getChannelId()); - } - break; - case CatalogEvent.DEFECT: - // 故障 - break; - case CatalogEvent.ADD: - // 增加 - logger.info("[收到增加通道通知] 来自设备: {}, 通道 {}", device.getDeviceId(), channel.getChannelId()); - deviceChannelService.updateChannel(deviceId, channel); - break; - case CatalogEvent.DEL: - // 删除 - logger.info("[收到删除通道通知] 来自设备: {}, 通道 {}", device.getDeviceId(), channel.getChannelId()); - storager.delChannel(deviceId, channel.getChannelId()); - break; - case CatalogEvent.UPDATE: - // 更新 - logger.info("[收到更新通道通知] 来自设备: {}, 通道 {}", device.getDeviceId(), channel.getChannelId()); - deviceChannelService.updateChannel(deviceId, channel); - break; - default: - logger.warn("[ NotifyCatalog ] event not found : {}", event ); - - } - // 转发变化信息 - eventPublisher.catalogEventPublish(null, channel, event); - - } - } - } catch (DocumentException e) { - logger.error("未处理的异常 ", e); - } - } - public void setCmder(SIPCommander cmder) { } From da7889bf67ec400c96c4a68e61f8b6b4fbeef585 Mon Sep 17 00:00:00 2001 From: lishuyuan Date: Fri, 27 Oct 2023 11:15:30 +0800 Subject: [PATCH 7/7] =?UTF-8?q?bugfix=EF=BC=9A=E7=BC=96=E8=BE=91=E5=9B=BD?= =?UTF-8?q?=E6=A0=87=E8=AE=BE=E5=A4=87=E6=97=B6=EF=BC=8C=E8=8B=A5=E7=9B=AE?= =?UTF-8?q?=E5=BD=95=E8=AE=A2=E9=98=85=E5=91=A8=E6=9C=9F=E6=9C=AC=E6=9D=A5?= =?UTF-8?q?=E4=B8=8D=E4=B8=BA0=E6=97=B6=E4=B8=8D=E7=94=9F=E6=95=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../gb28181/task/impl/CatalogSubscribeTask.java | 8 ++++---- .../iot/vmp/service/impl/DeviceServiceImpl.java | 16 +++++++++------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/task/impl/CatalogSubscribeTask.java b/src/main/java/com/genersoft/iot/vmp/gb28181/task/impl/CatalogSubscribeTask.java index 39dff931..2ffbfe40 100755 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/task/impl/CatalogSubscribeTask.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/task/impl/CatalogSubscribeTask.java @@ -89,17 +89,17 @@ public class CatalogSubscribeTask implements ISubscribeTask { ResponseEvent event = (ResponseEvent) eventResult.event; if (event.getResponse().getRawContent() != null) { // 成功 - logger.info("[取消目录订阅订阅]成功: {}", device.getDeviceId()); + logger.info("[取消目录订阅]成功: {}", device.getDeviceId()); }else { // 成功 - logger.info("[取消目录订阅订阅]成功: {}", device.getDeviceId()); + logger.info("[取消目录订阅]成功: {}", device.getDeviceId()); } },eventResult -> { // 失败 - logger.warn("[取消目录订阅订阅]失败,信令发送失败: {}-{} ", device.getDeviceId(), eventResult.msg); + logger.warn("[取消目录订阅]失败,信令发送失败: {}-{} ", device.getDeviceId(), eventResult.msg); }); } catch (InvalidArgumentException | SipException | ParseException e) { - logger.error("[命令发送失败] 取消目录订阅订阅: {}", e.getMessage()); + logger.error("[命令发送失败] 取消目录订阅: {}", e.getMessage()); } } } diff --git a/src/main/java/com/genersoft/iot/vmp/service/impl/DeviceServiceImpl.java b/src/main/java/com/genersoft/iot/vmp/service/impl/DeviceServiceImpl.java index 0d99eccd..d70c2702 100755 --- a/src/main/java/com/genersoft/iot/vmp/service/impl/DeviceServiceImpl.java +++ b/src/main/java/com/genersoft/iot/vmp/service/impl/DeviceServiceImpl.java @@ -520,16 +520,18 @@ public class DeviceServiceImpl implements IDeviceService { // 目录订阅相关的信息 - if (device.getSubscribeCycleForCatalog() > 0) { - if (deviceInStore.getSubscribeCycleForCatalog() == 0 || deviceInStore.getSubscribeCycleForCatalog() != device.getSubscribeCycleForCatalog()) { - deviceInStore.setSubscribeCycleForCatalog(device.getSubscribeCycleForCatalog()); + if (deviceInStore.getSubscribeCycleForCatalog() != device.getSubscribeCycleForCatalog()) { + if (device.getSubscribeCycleForCatalog() > 0) { + // 若已开启订阅,但订阅周期不同,则先取消 + if (deviceInStore.getSubscribeCycleForCatalog() != 0) { + removeCatalogSubscribe(deviceInStore); + } // 开启订阅 - addCatalogSubscribe(deviceInStore); - } - }else if (device.getSubscribeCycleForCatalog() == 0) { - if (deviceInStore.getSubscribeCycleForCatalog() != 0) { deviceInStore.setSubscribeCycleForCatalog(device.getSubscribeCycleForCatalog()); + addCatalogSubscribe(deviceInStore); + }else if (device.getSubscribeCycleForCatalog() == 0) { // 取消订阅 + deviceInStore.setSubscribeCycleForCatalog(device.getSubscribeCycleForCatalog()); removeCatalogSubscribe(deviceInStore); } }