2018년 11월 29일 목요일

ESP로 오실로스코프를 만들자 - #4 (최소 크기 및 비용편) - 소스코드 #2

이 소스코드는 [ 최소 비용의 오실로스코프 만들기 ] 에 포함된 소스코드이다. 상세 내용은 링크를 참조하자.

4. ./data/web_interface.html
<!DOCTYPE html>
<head>
    <meta charset="utf-8">
    <title>Web Oscilloscope Ver 0.9</title>
    <style>
        html,
        body {
            width: 100%;
            height: 100%;
            margin: 0px;
            border: 0;
            overflow: hidden;
            /*  Disable scrollbars */
            display: block;
            /* No floating content on sides */
        }
    </style>
    <script>
        // ------------------------------------------------------
        // Tiny + Cheap Oscilloscope
        //  [[ AP MODE  Info]]  SSID : OSCIL_V06,  PWD  : 12345678
        // +-------- GPL License --------------------------------+
        // do not remove contact & url
        //   contact :  terminal0070@gmail.com
        //   blog :  https://andy-power.blogspot.com/
        //  ------------------------------------------------------

        //-------------------------------------------------------------------
        // for rounded rectangular
        // x, y, width, height, radius of each conner, fill?, stroke?
        //-------------------------------------------------------------------
        CanvasRenderingContext2D.prototype.roundRect = function (x, y, width, height, radius, fill, stroke) {
            var cornerRadius = { upperLeft: 0, upperRight: 0, lowerLeft: 0, lowerRight: 0 };
            if (typeof stroke == "undefined") { stroke = true; }
            if (typeof radius == "object") {
                for (var side in radius) {
                    cornerRadius[side] = radius[side];
                }
            }

            this.beginPath();
            this.moveTo(x + cornerRadius.upperLeft, y);
            this.lineTo(x + width - cornerRadius.upperRight, y);
            this.quadraticCurveTo(x + width, y, x + width, y + cornerRadius.upperRight);
            this.lineTo(x + width, y + height - cornerRadius.lowerRight);
            this.quadraticCurveTo(x + width, y + height, x + width - cornerRadius.lowerRight, y + height);
            this.lineTo(x + cornerRadius.lowerLeft, y + height);
            this.quadraticCurveTo(x, y + height, x, y + height - cornerRadius.lowerLeft);
            this.lineTo(x, y + cornerRadius.upperLeft);
            this.quadraticCurveTo(x, y, x + cornerRadius.upperLeft, y);
            this.closePath();
            if (stroke) { this.stroke(); }
            if (fill) { this.fill(); }
        }

        //-------------------------------------------------------------------
        // for websocket
        //-------------------------------------------------------------------
        var g_WebSocket = null;                     // web socket
        var g_bForceCloseWebsocket = false;
        function startSocket() {
            g_strConntectionStatus = "Try con..."
            redraw();
            if ("WebSocket" in window) {
                g_WebSocket = new WebSocket("ws://192.168.4.1:8080");   // home
                g_WebSocket.binaryType = 'arraybuffer';
                g_WebSocket.onopen = function (event) {
                    g_strConntectionStatus = "Connected";
                    redraw();
                }

                g_WebSocket.onmessage = function (event) {
                    var wsRecvMsg = event.data;
                    var dataStream = new Uint8Array(wsRecvMsg);

                    // first 1 bytes are control code
                    switch (dataStream[0]) {
                        case 0: // main info
                            // g_aryLastChartDatas[0] == processing code
                            // g_aryLastChartDatas[1] == channel num
                            g_aryLastChartDatas = dataStream;
                            redraw();
                            break;
                        case 1: // additional info (Hz, Volt, ~~)
                            //----------------------------------------------------------
                            // 부가 데이터 순서 ( sub info bytes order)
                            //   0    1               2           3        4           5
                            // code, collapsed flag, sub scale, scale,  freq / 256, freq % 256,
                            //      6, 8, 10                       7,9, 11                  
                            //    volt (Integer part),  volt (Fraction part * 10) 
                            //    12                         13           
                            //  #total_data /  256,      #total_data % 256,
                            //    14                               15
                            //  m_dScrollStart / 256,    m_dScrollStart % 256,
                            //    16
                            //  m_dTracking
                            //----------------------------------------------------------      
                            if (dataStream[1] == 0) {
                                g_bCollaped = false;
                            } else {
                                g_bCollaped = true;
                            }
                            g_dSubScale = dataStream[2];
                            g_dScale = dataStream[3];
                            g_dFrequency = dataStream[4] * 256 + dataStream[5];
                            g_aryVolts[0] = dataStream[6] + dataStream[7] / 10;
                            g_aryVolts[1] = dataStream[8] + dataStream[9] / 10;
                            g_aryVolts[2] = dataStream[10] + dataStream[11] / 10;
                            g_dTotalDataNum = dataStream[12] * 256 + dataStream[13];
                            g_dScrollPos = dataStream[14] * 256 + dataStream[15];
                            g_dTrackingFlag = dataStream[16];
                            redraw();
                            break;
                        default:
                            console.log(dataStream[0]);
                            break;
                    }
                }

                g_WebSocket.onclose = function (event) {
                    if (g_bForceCloseWebsocket == false) {
                        startSocket();
                        return;
                    } else {
                        g_strConntectionStatus = "Connect";
                        g_WebSocket = null;
                        redraw();
                    }
                }
            }

        }

        //-------------------------------------------------------------------
        // for oscilloscope function
        //-------------------------------------------------------------------
        var g_OscilloCanvas = null;                 // target Canvas
        var g_Context = null;                       // context
        var g_dChNum = 3;                           // number of channel
        var g_bCollaped = false;                     // collapse channels or not.
        var g_strConntectionStatus = "Connect";
        var g_dChartDisplayDataLength = 300;
        var g_aryLastChartDatas = null;
        var g_aryChColors = ["#3498DB", "#4AA02C", "#C68E17"]; // each channel color
        var g_dTrackingFlag = 0;
        // oscilloscope top side info
        var g_dFrequency = 0;                       // ch1 frequency
        var g_aryVolts = [0.0, 0.0, 0.0];           // volts (3 channels)
        var g_dScale = 1;                           // main scale
        var g_dSubScale = 1;                        // sub scale
        var g_dTotalDataNum = 380 * 2;                  // number of data ( == depth)
        var g_dScrollPos = 0;                      // now scroll pos

        // for UI (mouse event)
        var g_dLastMouseX = 0, g_dLastMouseY = 0;   // Last  mouse click pos.
        var g_dScrollGrapBarSx = 0, g_dScrollGrapBarEx = 0; // scroll grap bar position
        var g_dScrollGrapStartX = 0;
        var g_dPrevBtnIndex = -1;
        var g_dMouseDownStatus = 0;                 // 0 : up status, 1 : down status,  2 : scrollbar down status
        var g_dNormalButtonStartWidth = 60;
        //-------------------------------------------------------------------
        // Master functions
        //-------------------------------------------------------------------
        function initialize() {
            g_OscilloCanvas = document.getElementById('oscilloscopeCanvas');
            g_Context = g_OscilloCanvas.getContext('2d');
            g_OscilloCanvas.addEventListener("mousedown", onMousedown, false);
            g_OscilloCanvas.addEventListener("mousemove", onMousemove, false);
            g_OscilloCanvas.addEventListener("mouseup", onMouseup, false);
            g_OscilloCanvas.addEventListener("touchstart", onMousedown, false);
            g_OscilloCanvas.addEventListener("touchmove", onMousemove, false);
            //   g_OscilloCanvas.addEventListener("touchend", onMouseup, false);
            window.addEventListener('resize', resizeCanvas, false);
            resizeCanvas();
        }
        function resizeCanvas() {
            g_OscilloCanvas.width = window.innerWidth;
            g_OscilloCanvas.height = window.innerHeight;
            redraw();
        }
        function redraw() {
            g_Context.save(); //Freeze redraw
            g_Context.fillStyle = "#303030";
            g_Context.font = "12px Arial";

            g_Context.lineWidth = 1;
            g_Context.fillRect(0, 0, g_OscilloCanvas.width, g_OscilloCanvas.height);
            drawTopside();
            drawMiddleSide();
            drawBottomSide();
            g_Context.restore(); //And now do the redraw
        }

        //-------------------------------------------------------------------
        // for Mouse interface
        //-------------------------------------------------------------------
        function onMousedown(e) {
            g_dLastMouseX = e.pageX;
            g_dLastMouseY = e.pageY;
            if (g_dMouseDownStatus == 1) { return; }
            g_dMouseDownStatus = 1;
            g_dPrevBtnIndex = getButtonIndex();
            if (g_dPrevBtnIndex >= 0) {
                redraw();
            } else if (g_dLastMouseX > g_dScrollGrapBarSx && g_dLastMouseX < g_dScrollGrapBarEx
                && g_dLastMouseY < 30) {
                g_dScrollGrapStartX = g_dLastMouseX;
                g_dMouseDownStatus = 2; // start scrollbar grapping
            }
        }
        function onMousemove(e) {
            g_dLastMouseX = e.pageX;
            g_dLastMouseY = e.pageY;
            if (g_dMouseDownStatus == 1) {
                var dNowBtnIndex = getButtonIndex();
                if (g_dPrevBtnIndex != dNowBtnIndex) {
                    g_dPrevBtnIndex = dNowBtnIndex;
                    redraw();
                }
            } else if (g_dMouseDownStatus == 2) { // process scrolling
                var gap = (g_dLastMouseX - g_dScrollGrapStartX) * ((g_dTotalDataNum - g_dChartDisplayDataLength) / (100 + g_dScrollGrapBarSx - g_dScrollGrapBarEx));
                var dNewScrollPos = parseInt(g_dScrollPos + gap);
                if (dNewScrollPos < 0) dNewScrollPos = 0;
                if (Math.abs(dNewScrollPos - g_dScrollPos) > 2) {
                    g_dScrollGrapStartX = g_dLastMouseX;
                    var tmp = [1, 10, parseInt(dNewScrollPos / 256), parseInt(dNewScrollPos % 256)];
                    var sendBuffer = new Uint8Array(tmp);
                    g_WebSocket.send(sendBuffer.buffer);
                }
            }
        }
        function onMouseup(e) {
            g_dMouseDownStatus = 0;
            g_dLastMouseX = e.pageX;
            g_dLastMouseY = e.pageY;
            g_dPrevBtnIndex = -1;

            if (g_dLastMouseY < 30 && g_dLastMouseX > (window.innerWidth - 80)) {
                if (g_WebSocket == null) { // 'connect', 'disconnect' button
                    startSocket();
                } else {
                    g_bForceCloseWebsocket = true;
                    g_WebSocket.close();
                    g_WebSocket = null;
                }
            } else {
                var btnIndex = getButtonIndex();  // function button
                if (btnIndex >= 0 && g_WebSocket != null && g_WebSocket
                    && g_WebSocket.readyState == g_WebSocket.OPEN) {
                    var tmp = [1, btnIndex];
                    var sendBuffer = new Uint8Array(tmp);
                    g_WebSocket.send(sendBuffer.buffer);
                    if (btnIndex == 0) {
                        g_dTrackingFlag = 1;
                        redraw();
                    }
                }
            }
            redraw();
        }
        // 클릭 포인트를 바탕으로 어떤 버튼이 눌려졌는지 조사하는 함수
        function getButtonIndex() {
            var btnIndex = -1;

            if (g_dLastMouseY >= (window.innerHeight - 45)) {
                for (let i = 0; i < 7; i++) {
                    if (g_dLastMouseX >= 5 + i * g_dNormalButtonStartWidth && g_dLastMouseX <= 5 + i * g_dNormalButtonStartWidth + 34) {
                        btnIndex = i;
                        break;
                    }
                }
            }
            return btnIndex;
        }

        //-------------------------------------------------------------------
        // Sub Functions
        //-------------------------------------------------------------------
        function drawTopside() {
            // header info
            g_Context.font = "18px Arial";
            g_Context.textAlign = "left";
            g_Context.fillStyle = "#dfdfdf";
            g_Context.fillText("Scale : " + g_dSubScale + " / " + g_dScale, 10, 20);
            g_Context.fillStyle = g_aryChColors[0];
            g_Context.fillText(g_dFrequency + " Hz", 140, 20);

            // draw scroll bar...
            var scrollsx = window.innerWidth / 2 - 50;
            var scrollWidth = 100;
            var barWidth = (scrollWidth - 2) * g_dChartDisplayDataLength / g_dTotalDataNum;
            g_dScrollGrapBarSx = (scrollWidth - 2) * g_dScrollPos / g_dTotalDataNum + scrollsx + 1;
            g_dScrollGrapBarEx = g_dScrollGrapBarSx + barWidth;
            g_Context.strokeStyle = "#f3f3f3";
            g_Context.beginPath();
            drawLine(scrollsx, 6, scrollsx, 23);
            drawLine(scrollsx + scrollWidth, 6, scrollsx + scrollWidth, 23);
            drawLine(scrollsx, 15, scrollsx + scrollWidth, 15);
            g_Context.stroke();
            if (g_dMouseDownStatus == 2) {
                g_Context.fillStyle = "#5DADE2";
            } else { g_Context.fillStyle = "#d6d6d6" };
            g_Context.roundRect(g_dScrollGrapBarSx, 11, barWidth, 9, { upperLeft: 2, upperRight: 2, lowerLeft: 2, lowerRight: 2 }, true, false);

            // draw connecting status..
            g_Context.font = "12px Arial";
            if (g_strConntectionStatus == "Connected") {
                g_Context.fillStyle = "#1D8348";
            } else {
                g_Context.fillStyle = "#EC7063";
            }
            g_Context.roundRect(window.innerWidth - 80, 4, 72, 22, { upperLeft: 3, upperRight: 3, lowerLeft: 3, lowerRight: 3 }, true, false);
            g_Context.fillStyle = "#fafafa";
            g_Context.textAlign = "center";
            g_Context.fillText(g_strConntectionStatus, window.innerWidth - 44, 19);

            // bottom line
            g_Context.strokeStyle = "#dfdfdf";
            g_Context.beginPath();
            drawLine(0, 30, window.innerWidth, 30);
            g_Context.stroke();
        }
        function drawMiddleSide() {
            // separating line
            g_Context.strokeStyle = "#dfdfdf";
            g_Context.beginPath();
            drawLine(49, 30, 49, window.innerHeight - 50)
            g_Context.stroke();
            drawChart();

            if (g_dTrackingFlag == 1) {
                g_Context.fillStyle = "#303030";
                g_Context.roundRect(window.innerWidth / 2 - 160, window.innerHeight / 2 - 25, 320, 50, { upperLeft: 3, upperRight: 3, lowerLeft: 3, lowerRight: 3 }, true, false);

                g_Context.font = "18px Arial";
                g_Context.fillStyle = "#fafafa";
                g_Context.textAlign = "center";
                g_Context.fillText("Waiting for phase changing...", window.innerWidth / 2, window.innerHeight / 2 + 9);
            }
        }
        function drawBottomSide() {
            // bottom line
            g_Context.strokeStyle = "#dfdfdf";
            g_Context.beginPath();
            drawLine(0, window.innerHeight - 50, window.innerWidth, window.innerHeight - 50)
            g_Context.stroke();
            //buttons
            drawButtons();
        }
        function drawChart() {
            var OneChannelCnt = g_dChartDisplayDataLength; // one channel data count
            if (g_aryLastChartDatas != null) {
                g_dChNum = g_aryLastChartDatas[1];
            }

            var allHeight = window.innerHeight - 30 - 50 - 2; // height - topside - bottomside
            var allWidth = window.innerWidth - 50; // width - leftside
            var hopWidth = allWidth / OneChannelCnt;

            var yPos = [30 + allHeight, 30 + allHeight, 30 + allHeight];
            var eachMax = allHeight - 4;

            g_Context.strokeStyle = "#dfdfdf";
            g_Context.fillStyle = g_aryChColors[0];
            g_Context.fillText(g_aryVolts[0] + " V", 22, 66);

            g_Context.fillRect(0, 31, 49, 20);
            g_Context.fillStyle = "#dfdfdf";
            g_Context.fillText("CH 1", 22, 46);
            if (g_bCollaped == false) {
                switch (g_dChNum) {
                    case 1:
                        yPos[0] = 30 + allHeight - 2;
                        break;
                    case 2: // display 2nd channel
                        yPos[0] = 30 + allHeight / 2 - 2;
                        yPos[1] = 30 + allHeight - 2;
                        eachMax = allHeight / 2 - 4;
                        g_Context.beginPath();
                        drawLine(0, 30 + allHeight / 2 + 2, window.innerWidth, 30 + allHeight / 2 + 2);
                        g_Context.stroke();
                        g_Context.fillStyle = g_aryChColors[1];
                        g_Context.fillText(g_aryVolts[1] + " V", 22, yPos[0] + 40);
                        g_Context.fillRect(0, yPos[0] + 5, 49, 20);
                        g_Context.fillStyle = "#dfdfdf";
                        g_Context.fillText("CH 2", 22, yPos[0] + 20);
                        break;
                    case 3: // display 3th channel
                        yPos[0] = 30 + allHeight / 3 - 2;
                        yPos[1] = 30 + allHeight * 2 / 3 - 2;
                        yPos[2] = 30 + allHeight - 2;
                        eachMax = allHeight / 3 - 4;
                        g_Context.beginPath();
                        drawLine(0, 30 + allHeight / 3 + 2, window.innerWidth, 30 + allHeight / 3 + 2);
                        drawLine(0, 30 + allHeight * 2 / 3 + 2, window.innerWidth, 30 + allHeight * 2 / 3 + 2);
                        g_Context.stroke();
                        g_Context.fillStyle = g_aryChColors[1];
                        g_Context.fillText(g_aryVolts[1] + " V", 22, yPos[0] + 40);
                        g_Context.fillRect(0, yPos[0] + 5, 49, 20);
                        g_Context.fillStyle = "#dfdfdf";
                        g_Context.fillText("CH 2", 22, yPos[0] + 20);
                        g_Context.fillStyle = g_aryChColors[2];
                        g_Context.fillText(g_aryVolts[2] + " V", 22, yPos[1] + 40);
                        g_Context.fillRect(0, yPos[1] + 5, 49, 20);
                        g_Context.fillStyle = "#dfdfdf";
                        g_Context.fillText("CH 3", 22, yPos[1] + 19);
                    default:
                        break;
                }
            } else {
                for (let i = 1; i < g_dChNum; i++) {
                    g_Context.fillStyle = g_aryChColors[i];
                    g_Context.fillText(g_aryVolts[i] + " V", 22, 66 + i * 61);
                    g_Context.fillRect(0, 31 + i * 61, 49, 20);
                    g_Context.fillStyle = "#dfdfdf";
                    g_Context.fillText("CH " + (i + 1), 22, 46 + i * 61);
                }
            }

            if (g_aryLastChartDatas == null) {
                return;
            }
            ratio = eachMax / 256;
            for (let j = 2; j >= 0; j--) {
                if (j >= g_dChNum) {
                    continue;
                }
                g_Context.strokeStyle = g_aryChColors[j];
                g_Context.beginPath();
                g_Context.moveTo(50, yPos[j] - g_aryLastChartDatas[2 + j * OneChannelCnt] * ratio);
                for (let index = 0; index < OneChannelCnt; index++) {
                    g_Context.lineTo(50 + index * hopWidth, yPos[j] - g_aryLastChartDatas[2 + index + j * OneChannelCnt] * ratio);
                }
                g_Context.stroke();
            }
        }

        function drawButtons() {
            var shapeColor = "#dfdfdf";
            var backColor = "#505050";
            var sy = window.innerHeight - 40;
            g_Context.lineWidth = 3;
            // 모든 버튼은 , , 점등으로 한땀 한땀 그렷다.
            for (let i = 0; i < 7; i++) {
                //var sx = 5 + i * 47;
                var sx = 5 + i * g_dNormalButtonStartWidth;
                if (i == g_dPrevBtnIndex) {
                    g_Context.fillStyle = "#5DADE2";
                    g_Context.strokeStyle = "#5DADE2";
                } else {
                    g_Context.fillStyle = "#d3d3d3";
                    g_Context.strokeStyle = "#d3d3d3";
                }
                g_Context.roundRect(sx, sy, 34, 30, { upperLeft: 3, upperRight: 3, lowerLeft: 3, lowerRight: 3 }, false, true);
                switch (i) {
                    case 0: // 추적 모드인경우에는 T 모양을 다른 색으로 표시한다.
                        // T
                        g_Context.fillStyle = "#dfdfdf";
                        if (g_dTrackingFlag == 1) {
                            g_Context.fillStyle = "#F5B041";
                        } else if (2 == g_dTrackingFlag) {
                            g_Context.fillStyle = "#3498DB";
                        }
                        g_Context.fillRect(sx + 6, sy + 6, 21, 4);
                        g_Context.fillRect(sx + 15, sy + 10, 4, 14);
                        break;
                    case 1: // zoom in
                        g_Context.beginPath();
                        g_Context.arc(sx + 16, sy + 14, 10, 0, 2 * Math.PI, false);
                        g_Context.moveTo(sx + 24, sy + 21); g_Context.lineTo(sx + 28, sy + 25);
                        g_Context.moveTo(sx + 16, sy + 9); g_Context.lineTo(sx + 16, sy + 19);
                        g_Context.moveTo(sx + 11, sy + 14); g_Context.lineTo(sx + 21, sy + 14);
                        g_Context.stroke();
                        break;
                    case 2: // zoom out
                        g_Context.beginPath();
                        g_Context.arc(sx + 16, sy + 14, 10, 0, 2 * Math.PI, false);
                        g_Context.moveTo(sx + 24, sy + 21); g_Context.lineTo(sx + 28, sy + 25);
                        g_Context.moveTo(sx + 11, sy + 14); g_Context.lineTo(sx + 21, sy + 14);
                        g_Context.stroke();
                        break;
                    case 3: // <<<>
                        g_Context.beginPath();
                        g_Context.moveTo(sx + 22, sy + 7); g_Context.lineTo(sx + 8, sy + 14); g_Context.lineTo(sx + 22, sy + 21);
                        g_Context.stroke();
                        break;
                    case 4: // >
                        g_Context.beginPath();
                        g_Context.moveTo(sx + 11, sy + 7); g_Context.lineTo(sx + 25, sy + 14); g_Context.lineTo(sx + 11, sy + 21);
                        g_Context.stroke();
                        break;
                    case 5: // collapse channel on or off
                        g_Context.lineWidth = 2;
                        g_Context.beginPath();
                        if (g_bCollaped) {
                            drawLine(sx + 6, sy + 14, sx + 27, sy + 14);
                            drawLine(sx + 8, sy + 12, sx + 12, sy + 8);
                            drawLine(sx + 12, sy + 8, sx + 16, sy + 12);
                            drawLine(sx + 16, sy + 12, sx + 21, sy + 7);
                            drawLine(sx + 21, sy + 7, sx + 25, sy + 11);
                            drawLine(sx + 8, sy + 19, sx + 11, sy + 16);
                            drawLine(sx + 11, sy + 16, sx + 16, sy + 21);
                            drawLine(sx + 16, sy + 21, sx + 21, sy + 16);
                            drawLine(sx + 21, sy + 16, sx + 26, sy + 20);
                        } else {
                            drawLine(sx + 8, sy + 15, sx + 12, sy + 11);
                            drawLine(sx + 12, sy + 11, sx + 19, sy + 18);
                            drawLine(sx + 19, sy + 18, sx + 25, sy + 9);
                        }
                        g_Context.stroke();
                        g_Context.lineWidth = 3;
                        break;
                    case 6: // change channel number
                        g_Context.textAlign = "center";
                        g_Context.fillText("CH+", sx + 17, sy + 19);
                        break;
                }
            }
        }

        function drawLine(sx, sy, ex, ey) {
            g_Context.moveTo(sx, sy); g_Context.lineTo(ex, ey);
        }


    </script>
</head>

<body onload="initialize()">
    <canvas id='oscilloscopeCanvas' style='position:absolute; left:0px; top:0px;'></canvas>
</body>

</html>


댓글 없음:

댓글 쓰기

활용도가 높은 파워뱅크를 만들어 보자 - 제1편 (설계 및 재료 조달편)

미안함 때문에...  파워뱅크 1차 버전을 만들어서 선물하였다 (총 2개 제작) . 파워뱅크를 먼저 선물받은 사람은 캠핑을 좋아하는 사람이기에 엄청 좋아했다. 컨셉 자체가 전등을 켤 수 있게 만들었고, 필요시 핸드폰등을 충전할 수 있으며, 쌀쌀한 날씨...