From b3aff2b1637848d43104d505da867672cfdb1732 Mon Sep 17 00:00:00 2001
From: zjj <3272660260@qq.com>
Date: 星期三, 22 五月 2024 15:25:34 +0800
Subject: [PATCH] #

---
 src/main/webapp/views/realtimeWatch/index2.html           |   94 ++++----
 src/main/webapp/static/wcs/js/console.map.js              |   93 +++++----
 src/main/webapp/views/realtimeWatch/console.html          |   48 ++--
 src/main/webapp/views/realtimeWatch/news.html             |  114 +++++++++++
 src/main/java/com/zy/asrs/controller/NewsController.java  |   17 +
 src/main/java/com/zy/common/utils/News.java               |  195 +++++++++++++++++++
 src/main/java/com/zy/core/thread/SiemensRgvThread.java    |   13 +
 src/main/java/com/zy/core/model/protocol/RgvProtocol.java |    2 
 src/main/webapp/static/wcs/css/render.css                 |    2 
 9 files changed, 462 insertions(+), 116 deletions(-)

diff --git a/src/main/java/com/zy/asrs/controller/NewsController.java b/src/main/java/com/zy/asrs/controller/NewsController.java
new file mode 100644
index 0000000..f8e626d
--- /dev/null
+++ b/src/main/java/com/zy/asrs/controller/NewsController.java
@@ -0,0 +1,17 @@
+package com.zy.asrs.controller;
+
+import com.core.common.R;
+import com.zy.common.utils.News;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@Slf4j
+@RestController
+@RequestMapping("/news")
+public class NewsController {
+    @RequestMapping("/print")
+    public synchronized R print(){
+        return R.ok().add(News.print());
+    }
+}
diff --git a/src/main/java/com/zy/common/utils/News.java b/src/main/java/com/zy/common/utils/News.java
new file mode 100644
index 0000000..63c8762
--- /dev/null
+++ b/src/main/java/com/zy/common/utils/News.java
@@ -0,0 +1,195 @@
+package com.zy.common.utils;
+
+import lombok.extern.slf4j.Slf4j;
+
+import java.lang.reflect.Array;
+import java.text.SimpleDateFormat;
+import java.util.*;
+
+/**
+ * news stories for zoneyung
+ * Created by vincent on 2022/12/22
+ */
+@Slf4j
+public class News {
+
+    public static void main(String[] args) {
+        News.info("info{}", 1);
+        News.warn("warn{}", 2);
+        News.error("error{}", 3);
+        System.out.println(News.print());
+    }
+
+    interface NewsSupport<T> { boolean execute(T t); }
+
+    private static final NewsQueue<NewsDomain> NEWS_QUEUE = new NewsQueue<>(NewsDomain.class, 1024);
+
+    @SuppressWarnings({"unchecked"})
+    static class NewsQueue<T> {
+
+        private final transient Class<T> cls;
+        private final T[] arr;
+        private final int capacity;
+        private int head;
+        private int tail;
+
+        { this.head = 0; this.tail = 0; }
+
+        public NewsQueue(Class<T> cls, int capacity) {
+            this.cls = cls;
+            this.arr = (T[]) Array.newInstance(cls, capacity);
+            this.capacity = capacity;
+        }
+
+        public synchronized boolean offer(T t) {
+            if (this.tail == this.capacity) {
+                this.peek();
+            }
+            this.reform();
+            this.arr[this.tail] = t;
+            this.tail ++;
+            return true;
+        }
+
+        public synchronized boolean put(T t) {
+            if (this.tail == this.capacity) {
+                return false;
+            } else {
+                this.reform();
+            }
+            this.arr[this.tail] = t;
+            this.tail ++;
+            return true;
+        }
+
+        public synchronized T peek() {
+            if (this.head == this.tail) {
+                return null;
+            }
+            T t = this.arr[this.head];
+            this.head ++;
+            this.reform();
+            return t;
+        }
+
+        private void reform() {
+            for (int i = this.head; i < this.tail; i++) {
+                this.arr[i-this.head] = this.arr[i];
+            }
+            this.tail -= this.head;
+            this.head = 0;
+        }
+
+        public synchronized int size() {
+            return this.tail - this.head;
+        }
+
+        public synchronized List<T> data() {
+            T[] ts = (T[]) Array.newInstance(this.cls, size());
+            if (this.tail - this.head >= 0) {
+                System.arraycopy(this.arr, this.head, ts, 0, this.tail - this.head);
+            }
+            return Arrays.asList(ts);
+        }
+
+    }
+
+    public static void info(String format, Object... arguments) {
+        log.info(format, arguments);
+        offer(NewsLevel.INFO, format, arguments);
+    }
+
+    public static void warn(String format, Object... arguments) {
+        log.warn(format, arguments);
+        offer(NewsLevel.WARN, format, arguments);
+    }
+
+    public static void error(String format, Object... arguments) {
+        log.error(format, arguments);
+        offer(NewsLevel.ERROR, format, arguments);
+    }
+
+    public static void infoNoLog(String format, Object... arguments) {
+        offer(NewsLevel.INFO, format, arguments);
+    }
+
+    public static void warnNoLog(String format, Object... arguments) {
+        offer(NewsLevel.WARN, format, arguments);
+    }
+
+    public static void errorNoLog(String format, Object... arguments) {
+        offer(NewsLevel.ERROR, format, arguments);
+    }
+
+    public static String printStr() {
+        StringBuilder sb = new StringBuilder("[");
+        List<NewsDomain> domains = NEWS_QUEUE.data();
+        for (int i = 0; i < domains.size(); i++) {
+            NewsDomain domain = domains.get(i);
+            sb.append("{");
+            sb.append("\"l\":").append(domain.level.idx).append(",");
+            sb.append("\"v\":\"").append(domain.content).append("\"").append(",");
+            sb.append("\"t\":\"").append(domain.date).append("\"");
+            sb.append("}");
+            if (i < domains.size() - 1) {
+                sb.append(",");
+            }
+        }
+        sb.append("]");
+        return sb.toString();
+    }
+
+    public static List<Map<String, Object>> print() {
+        List<Map<String, Object>> res = new ArrayList<>();
+        for (NewsDomain datum : NEWS_QUEUE.data()) {
+            Map<String, Object> map = new HashMap<>();
+            map.put("l", datum.level.idx);
+            map.put("v", datum.content);
+            map.put("t", datum.date);
+            res.add(map);
+        }
+        return res;
+    }
+
+    private static boolean offer(NewsLevel level, String msg, Object[] args) {
+        return NEWS_QUEUE.offer(new NewsDomain(level, replace(msg, args), (new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")).format(new Date())));
+    }
+
+    private static String replace(String str, Object[] objs){
+        if (null == objs || objs.length == 0 || null == str || "".equals(str.trim())) {
+            return str;
+        } else {
+            StringBuilder sb = new StringBuilder(str);
+            for (Object obj : objs) {
+                int idx = sb.indexOf("{}");
+                if (idx == -1) { break; }
+                sb.replace(idx, idx + 2, String.valueOf(obj));
+            }
+            return sb.toString();
+        }
+    }
+
+    static class NewsDomain {
+        public NewsLevel level;
+        public String content;
+        public String date;
+
+        public NewsDomain(NewsLevel level, String content, String date) {
+            this.level = level;
+            this.content = content;
+            this.date = date;
+        }
+    }
+
+    enum NewsLevel {
+        INFO(1),
+        WARN(2),
+        ERROR(3),
+        ;
+        public int idx;
+        NewsLevel(int idx) {
+            this.idx = idx;
+        }
+    }
+
+}
diff --git a/src/main/java/com/zy/core/model/protocol/RgvProtocol.java b/src/main/java/com/zy/core/model/protocol/RgvProtocol.java
index ec6111b..e63db96 100644
--- a/src/main/java/com/zy/core/model/protocol/RgvProtocol.java
+++ b/src/main/java/com/zy/core/model/protocol/RgvProtocol.java
@@ -65,7 +65,7 @@
     /**
      * 宸ヤ綅1鏈夌墿
      */
-    public Short loaded1;
+    public boolean loaded1;
 
     /**
      * RGV褰撳墠浣嶇疆
diff --git a/src/main/java/com/zy/core/thread/SiemensRgvThread.java b/src/main/java/com/zy/core/thread/SiemensRgvThread.java
index edc891f..618325f 100644
--- a/src/main/java/com/zy/core/thread/SiemensRgvThread.java
+++ b/src/main/java/com/zy/core/thread/SiemensRgvThread.java
@@ -9,8 +9,10 @@
 import com.core.common.SpringUtils;
 import com.zy.asrs.entity.BasRgv;
 
+import com.zy.asrs.service.BasRgvErrService;
 import com.zy.asrs.service.BasRgvService;
 
+import com.zy.common.utils.News;
 import com.zy.core.RgvThread;
 import com.zy.core.cache.MessageQueue;
 import com.zy.core.cache.OutputQueue;
@@ -136,7 +138,7 @@
         rgvProtocol.setStatus((short)-1);
         rgvProtocol.setTaskNo1((short)0);
         rgvProtocol.setStatus1((short)-1);
-        rgvProtocol.setLoaded1((short)0);
+        rgvProtocol.setLoaded1(false);
         rgvProtocol.setWalkPos((short)0);
         rgvProtocol.setRgvPos(0);
 //        rgvProtocol.setTaskNo2((short)0);
@@ -188,10 +190,17 @@
                 rgvProtocol.setAlarm(siemensNet.getByteTransform().TransInt16(result.Content, 8));
                 rgvProtocol.setStatus(siemensNet.getByteTransform().TransInt16(result.Content, 10));
                 rgvProtocol.setSpeed(siemensNet.getByteTransform().TransInt16(result.Content, 12));
-                rgvProtocol.setRgvPos((int) siemensNet.getByteTransform().TransInt16(result.Content, 14));
+                rgvProtocol.setRgvPos(siemensNet.getByteTransform().TransInt32(result.Content, 14));
+                rgvProtocol.setLoaded1(siemensNet.getByteTransform().TransBool(result.Content,18));
 
 
                 OutputQueue.RGV.offer(MessageFormat.format("銆恵0}銆慬id:{1}] <<<<< 瀹炴椂鏁版嵁鏇存柊鎴愬姛", DateUtils.convert(new Date()), slave.getId()));
+                if (rgvProtocol.getAlarm() > 0 ){
+                    BasRgvErrService basRgvErrService = SpringUtils.getBean(BasRgvErrService.class);
+                    News.error("RGV 寮傚父 ===>> [id:{}] [ip:{}],鎶ヨ浠g爜:{},鎶ヨ鏂囨湰",slave.getId(), slave.getIp(),rgvProtocol.getAlarm(),basRgvErrService.selectById(rgvProtocol.getAlarm()));
+                }else {
+                    News.info("姝e父");
+                }
 
                 // 宸ヤ綅1澶嶄綅淇″彿
 //                if (rgvProtocol.getStatusType1().equals(RgvStatusType.WAITING)
diff --git a/src/main/webapp/static/wcs/css/render.css b/src/main/webapp/static/wcs/css/render.css
index 773e21a..c3c4da6 100644
--- a/src/main/webapp/static/wcs/css/render.css
+++ b/src/main/webapp/static/wcs/css/render.css
@@ -13,7 +13,7 @@
     background-color: rgb(108,167,168);
 }
 .head {
-    height: 10%;
+    height: 5%;
     width: 100%;
     color: #FFFFFF;
 }
diff --git a/src/main/webapp/static/wcs/js/console.map.js b/src/main/webapp/static/wcs/js/console.map.js
index 9ecb2db..5308003 100644
--- a/src/main/webapp/static/wcs/js/console.map.js
+++ b/src/main/webapp/static/wcs/js/console.map.js
@@ -518,23 +518,25 @@
                 "left": 401,
                 "width": 49,
                 "height": 20
-            }, {
-                "type": "track",
-                "id": "lb_trCart21",
-                "text": "",
-                "top": 45,
-                "left": 1490,
-                "width": 6,
-                "height": 630
-            }, {
-                "type": "track",
-                "id": "lb_trCart22",
-                "text": "",
-                "top": 45,
-                "left": 1544,
-                "width": 6,
-                "height": 630
-            }, {
+            },
+            //  {
+            //     "type": "track",
+            //     "id": "lb_trCart21",
+            //     "text": "",
+            //     "top": 45,
+            //     "left": 1490,
+            //     "width": 6,
+            //     "height": 630
+            // }, {
+            //     "type": "track",
+            //     "id": "lb_trCart22",
+            //     "text": "",
+            //     "top": 45,
+            //     "left": 1544,
+            //     "width": 6,
+            //     "height": 630
+            // },
+                {
                 "type": "stn",
                 "id": "site-403",
                 "text": "403",
@@ -932,47 +934,64 @@
                 "id": "lb_trCart21",
                 "text": "",
                 "top": 45,
-                "left": 1490,
+                "left": 1510,
                 "width": 6,
-                "height": 630
+                "height": 800
             }, {
                 "type": "track",
                 "id": "lb_trCart22",
                 "text": "",
                 "top": 45,
-                "left": 1544,
+                "left": 1554,
                 "width": 6,
-                "height": 630
+                "height": 800
             }, {
+                "type": "track",
+                "id": "lb_trCart23",
+                "text": "",
+                "top": 45,
+                "left": 1510,
+                "width": 44,
+                "height": 6
+            }, {
+                "type": "track",
+                "id": "lb_trCart24",
+                "text": "",
+                "top": 839,
+                "left": 1510,
+                "width": 44,
+                "height": 6
+            }
+            , {
                 "type": "stn",
                 "id": "site-407",
                 "text": "407",
-                "top": 480,
-                "left": 1554,
+                "top": 770,
+                "left": 1437,
                 "width": 49,
                 "height": 20
             }, {
                 "type": "stn",
                 "id": "site-406",
                 "text": "406",
-                "top": 528,
-                "left": 1554,
+                "top": 795,
+                "left": 1437,
                 "width": 49,
                 "height": 20
             }, {
                 "type": "stn",
                 "id": "site-307",
                 "text": "307",
-                "top": 594,
-                "left": 1554,
+                "top": 700,
+                "left": 1437,
                 "width": 49,
                 "height": 20
             }, {
                 "type": "stn",
                 "id": "site-306",
                 "text": "306",
-                "top": 642,
-                "left": 1554,
+                "top": 725,
+                "left": 1437,
                 "width": 49,
                 "height": 20
             }, {
@@ -981,7 +1000,7 @@
                 "text": "1",
                 "top": 157,
                 "left": 1489,
-                "width": 62,
+                "width": 40,
                 "height": 20
             }, {
                 "type": "stn",
@@ -989,7 +1008,7 @@
                 "text": "2",
                 "top": 257,
                 "left": 1489,
-                "width": 62,
+                "width": 40,
                 "height": 20
             }, {
                 "type": "stn",
@@ -997,7 +1016,7 @@
                 "text": "3",
                 "top": 357,
                 "left": 1489,
-                "width": 62,
+                "width": 40,
                 "height": 20
             }, {
                 "type": "stn",
@@ -1005,15 +1024,7 @@
                 "text": "4",
                 "top": 457,
                 "left": 1489,
-                "width": 62,
-                "height": 20
-            }, {
-                "type": "stn",
-                "id": "site-5",
-                "text": "5",
-                "top": 557,
-                "left": 1489,
-                "width": 62,
+                "width": 40,
                 "height": 20
             }]
         }]
diff --git a/src/main/webapp/views/realtimeWatch/console.html b/src/main/webapp/views/realtimeWatch/console.html
index aa3bb0a..fe263a9 100644
--- a/src/main/webapp/views/realtimeWatch/console.html
+++ b/src/main/webapp/views/realtimeWatch/console.html
@@ -119,33 +119,33 @@
                     <span class="site-unauto">闈炶嚜鍔�/鎵嬪姩</span>
                 </div>
             </div>
-            <div class="bar-code">
-                <div class="body-head" id="code">鏉$爜鎵弿鍣�</div>
-                <div class="tablebox">
-                    <div class="table-head">
-                        <li><span>鏉$爜鍚嶇О</span><span class="right">鎵爜鏃堕棿</span></li>
-                    </div>
-                    <div id="barcode1" class="table-body">
+<!--            <div class="bar-code">-->
+<!--                <div class="body-head" id="code">鏉$爜鎵弿鍣�</div>-->
+<!--                <div class="tablebox">-->
+<!--                    <div class="table-head">-->
+<!--                        <li><span>鏉$爜鍚嶇О</span><span class="right">鎵爜鏃堕棿</span></li>-->
+<!--                    </div>-->
+<!--                    <div id="barcode1" class="table-body">-->
 
-                    </div>
-                </div>
-                <div class="tablebox">
-                    <div class="table-head">
-                        <li><span>鏉$爜鍚嶇О</span><span class="right">鎵爜鏃堕棿</span></li>
-                    </div>
-                    <div id="barcode2" class="table-body">
+<!--                    </div>-->
+<!--                </div>-->
+<!--                <div class="tablebox">-->
+<!--                    <div class="table-head">-->
+<!--                        <li><span>鏉$爜鍚嶇О</span><span class="right">鎵爜鏃堕棿</span></li>-->
+<!--                    </div>-->
+<!--                    <div id="barcode2" class="table-body">-->
 
-                    </div>
-                </div>
-                <div class="tablebox">
-                    <div class="table-head">
-                        <li><span>鏉$爜鍚嶇О</span><span class="right">鎵爜鏃堕棿</span></li>
-                    </div>
-                    <div id="barcode3" class="table-body">
+<!--                    </div>-->
+<!--                </div>-->
+<!--                <div class="tablebox">-->
+<!--                    <div class="table-head">-->
+<!--                        <li><span>鏉$爜鍚嶇О</span><span class="right">鎵爜鏃堕棿</span></li>-->
+<!--                    </div>-->
+<!--                    <div id="barcode3" class="table-body">-->
 
-                    </div>
-                </div>
-            </div>
+<!--                    </div>-->
+<!--                </div>-->
+<!--            </div>-->
         </div>
         <!-- 鍫嗗灈鏈哄脊绐� -->
         <div id="crnWindow" style="display: none;" class="animate__animated animate__fadeIn">
diff --git a/src/main/webapp/views/realtimeWatch/index2.html b/src/main/webapp/views/realtimeWatch/index2.html
index 849e351..8cf9d9b 100644
--- a/src/main/webapp/views/realtimeWatch/index2.html
+++ b/src/main/webapp/views/realtimeWatch/index2.html
@@ -56,52 +56,52 @@
     var systemRunning = true;
 
 
-    // news();layx.min('wcs-news');
-    // function news() {
-    //     layx.iframe(
-    //         'wcs-news' // id
-    //         , '绯荤粺鍒嗘瀽鎶ュ憡'
-    //         , "news.html"
-    //         , {
-    //             shadow:false
-    //             , storeStatus:false
-    //             // , skin: 'news'
-    //             , width:800
-    //             , height:600
-    //             , position:'rb'
-    //             // , control:false
-    //             , opacity:0.9
-    //             , border:false
-    //             , icon:'<img src="../../static/wcs/images/zy-logo.png" style="height:22px;display:block;"  alt=""/>'
-    //             , stickMenu:true
-    //             , maxMenu:false
-    //             , closeMenu:false
-    //             , moveLimit:{
-    //                 leftOut: false,
-    //                 rightOut: false,
-    //                 topOut: false,
-    //                 bottomOut: false,
-    //             }
-    //             , minWidth:300
-    //             , minHeight:300
-    //             , borderRadius: '8px'
-    //             , shadeDestroy:true
-    //             , escKey: false
-    //             , event:{
-    //                 onmin: {
-    //                     after: function () {
-    //                         $('.layx-min-statu').css("left", "inherit").css("right", "10px")
-    //                     }
-    //                 }
-    //                 , onrestore:{
-    //                     after: function () {
-    //                         let win = layx.getFrameContext('wcs-news');
-    //                         win.autoScroll = true
-    //                     }
-    //                 }
-    //             }
-    //         }
-    //     );
-    // }
+    news();layx.open('wcs-news');
+    function news() {
+        layx.iframe(
+            'wcs-news' // id
+            , '绯荤粺鍒嗘瀽鎶ュ憡'
+            , "news.html"
+            , {
+                shadow:false
+                , storeStatus:false
+                // , skin: 'news'
+                , width:600
+                , height:400
+                , position:'rb'
+                // , control:false
+                , opacity:0.9
+                , border:false
+                , icon:'<img src="../../static/wcs/images/zy-logo.png" style="height:22px;display:block;"  alt=""/>'
+                , stickMenu:true
+                , maxMenu:false
+                , closeMenu:false
+                , moveLimit:{
+                    leftOut: false,
+                    rightOut: false,
+                    topOut: false,
+                    bottomOut: false,
+                }
+                , minWidth:300
+                , minHeight:300
+                , borderRadius: '8px'
+                , shadeDestroy:true
+                , escKey: false
+                , event:{
+                    onmin: {
+                        after: function () {
+                            $('.layx-min-statu').css("left", "inherit").css("right", "10px")
+                        }
+                    }
+                    , onrestore:{
+                        after: function () {
+                            let win = layx.getFrameContext('wcs-news');
+                            win.autoScroll = true
+                        }
+                    }
+                }
+            }
+        );
+    }
 </script>
 </html>
diff --git a/src/main/webapp/views/realtimeWatch/news.html b/src/main/webapp/views/realtimeWatch/news.html
new file mode 100644
index 0000000..f9062d7
--- /dev/null
+++ b/src/main/webapp/views/realtimeWatch/news.html
@@ -0,0 +1,114 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <title>NEWS</title>
+    <style>
+        /** {*/
+        /*    padding: 0;*/
+        /*    margin: 0;*/
+        /*}*/
+        .container {
+            height: 100%;
+            width: 100%;
+        }
+        /*=============== SCROLL UP ===============*/
+        .scrollup {
+            text-decoration: none;
+            text-align: center;
+            width: 25px;
+            height: 18px;
+            position: fixed;
+            right: 1rem;
+            bottom: -55%;
+            background-color: rgb(108,167,168);
+            box-shadow: 0 8px 12px hsla(228, 66%, 45%, .1);
+            display: inline-flex;
+            padding: .35rem;
+            border-radius: .25rem;
+            color: #ffffff;
+            z-index: 10;
+            transition: .3s;
+            font-size: 8px;
+        }
+        .scrollup:hover {
+            transform: translateY(-.25rem);
+        }
+        /* Show Scroll Up*/
+        .show-scroll {
+            bottom: 5%;
+        }
+
+        .news-desc {
+            font-size: 14px;
+        }
+        .level-1 {
+            color: #333333;
+        }
+        .level-2 {
+            color: #0087ec;
+        }
+        .level-3 {
+            color: red;
+        }
+    </style>
+</head>
+<body>
+<div class="container">
+
+</div>
+<a class="scrollup" id="scroll-up">
+    <span>椤堕儴</span>
+</a>
+</body>
+<script src="../../static/wcs/js/jquery/jquery-3.3.1.min.js"></script>
+<script src="../../static/wcs/js/handlebars/handlebars-v4.5.3.js"></script>
+<script src="../../static/wcs/js/common.js"></script>
+<script>
+    var autoScroll = true;
+
+    function scrollUp(){
+        const scrollUp = document.getElementById('scroll-up');
+        if(this.scrollY >= 100) scrollUp.classList.add('show-scroll'); else scrollUp.classList.remove('show-scroll')
+    }
+    window.addEventListener('scroll', scrollUp)
+
+    $(document).on('click ','#scroll-up', function () {
+        window.scrollTo(0, 0);
+        autoScroll = false;
+    })
+
+    $(document).on('click ','body', function () {
+        autoScroll = false;
+    })
+
+    setInterval(()=>{
+        $.ajax({
+            url: baseUrl + "/news/print",
+            // headers: {'token': localStorage.getItem('token')},
+            method: 'GET',
+            success: function (res) {
+                if (res.code === 200) {
+                    let template = Handlebars.compile($('#newsTpl').html());
+                    $('.container').html(template({list: res.data}));
+                    if (autoScroll) {
+                        window.scrollTo(0, document.body.scrollHeight)
+                    }
+                } else if (res.code === 403) {
+                    window.location.href = baseUrl + "/login";
+                } else {
+                    console.error(res.msg);
+                }
+            }
+        })
+    }, 1000)
+
+</script>
+<script type="text/template" id="newsTpl">
+    {{#each list}}
+    <div class="news-desc level-{{l}}">
+        <span>{{t}}</span>&nbsp;-&nbsp;<span>{{v}}</span>
+    </div>
+    {{/each}}
+</script>
+</html>

--
Gitblit v1.9.1