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>


댓글 없음:

댓글 쓰기

3단 6핀 스위치로 DC 모터의 회전 방향을 바꾸어 보자

1. 필요는 연구의 어머니 항상 느끼는 부분이다. 필요하지 않으면 연구하지 않으며, 필요하면 연구한다. DC 모터를 조건에 따라서 정방향 또는 역방향으로 회전시켜야 하는 필요가 생겼다. 처음에는 MCU 및 Relay Switch를 이용하는 방법을 생각...