JS
DETEKSI DAN PELACAKAN WARNA
KOMPUTASI PERVASIVE
DISUSUN OLEH:
BM-6B
2022
Tujuan Praktikum
Pada praktikum ini akan dikenalkan alat/tool OpenCV.js dan OpenCV untuk lingkungan
Server Web Kamera ESP32. Sebagai contoh, akan dibuat Server Web Kamera ESP32
sederhana yang menyertakan deteksi warna dan pelacakan objek bergerak. Diharapkan
pengenalan ini akan menginspirasi kerja OpenCV tambahan dengan kamera ESP32.
Teori Singkat
1. ESP32 Cam
ESP32-CAM merupakan salah satu mikrokontroler yang memiliki fasilitas tambahan berupa
bluetooth, wifi, kamera, bahkan sampai ke slot mikroSD. ESP32-CAM ini biasanya digunakan
untuk project IoT (Internet of Things) yang membutuhkan fitur kamera. Modul ESP32CAM
memiliki lebih sedikit pin I/O dibandingkan modul ESP32 produk sebelumnya, yaitu ESP32
Wroom. Hal ini dikarenakan sudah banyak pin yang digunakan secara internal untuk fungsi
kamera dan fungsi slot kartu microSD. Selain itu, modul ESP32CAM juga tidak memiliki port
USB khusus (mengirim program dari port USB komputer). Jadi untuk memprogram modul ini
Anda harus menggunakan USB TTL atau kita dapat menambahkan modul tambahan berupa
downloader khusus untuk ESP32-CAM.
Sumber : https://indobot.co.id/blog/mengenal-esp32-cam-dan-bagaimana-cara-
menggunakannya/
2. OpenCV.js
OpenCV.js adalah pengikatan JavaScript untuk subset terpilih dari fungsi OpenCV
untuk platform web. Hal ini memungkinkan aplikasi web yang muncul dengan
pemrosesan multimedia untuk mendapatkan keuntungan dari berbagai macam fungsi
visi yang tersedia di OpenCV. OpenCV.js memanfaatkan Emscripten untuk
mengkompilasi fungsi OpenCV ke dalam target asm.js atau WebAssembly, dan
menyediakan API JavaScript untuk aplikasi web untuk mengaksesnya. Versi
perpustakaan mendatang akan memanfaatkan API akselerasi yang tersedia di Web
seperti SIMD dan eksekusi multi-threaded
Sumber : https://docs.opencv.org/3.4/df/d0a/tutorial_js_intro.html.
Diagram Rangkaian
Langkah Percobaan
I. Menambahkan aplikasi ke dalan proyek firebase
1. Buka Konsol proyek Firebase Anda dan tambahkan aplikasi ke proyek yang Anda buat di
tutorial sebelumnya dengan mengeklik tombol + Tambahkan aplikasi.
2. Pilih ikon aplikasi web.
3. Beri nama pada aplikasi . Kemudian, centang kotak di sebelah Juga siapkan Firebase
Hosting untuk Aplikasi ini. Klik Daftar aplikasi.
4. Kemudian, salin objek firebaseConfig dan simpan karena Anda akan membutuhkannya
nanti.
5. Klik Next pada langkah selanjutnya, dan terakhir pada Continue to console.
OCV_ColorTrack
/***
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
***/
#include <WiFi.h>
#include <WiFiClientSecure.h>
#include "esp_camera.h"
#include "soc/soc.h"
#include "soc/rtc_cntl_reg.h"
#include "index_OCV_ColorTrack.h"
String
Command="",cmd="",P1="",P2="",P3="",P4="",P5="",P6="",P7="",P8="",P9="";
byte
ReceiveState=0,cmdState=1,strState=1,questionstate=0,equalstate=0,semicolon
state=0;
//ANN:0
// AI-Thinker
#define PWDN_GPIO_NUM 32
#define RESET_GPIO_NUM -1
#define XCLK_GPIO_NUM 0
#define SIOD_GPIO_NUM 26
#define SIOC_GPIO_NUM 27
#define Y9_GPIO_NUM 35
#define Y8_GPIO_NUM 34
#define Y7_GPIO_NUM 39
#define Y6_GPIO_NUM 36
#define Y5_GPIO_NUM 21
#define Y4_GPIO_NUM 19
#define Y3_GPIO_NUM 18
#define Y2_GPIO_NUM 5
#define VSYNC_GPIO_NUM 25
#define HREF_GPIO_NUM 23
#define PCLK_GPIO_NUM 22
WiFiServer server(80);
//ANN:2
void ExecuteCommand() {
if (cmd!="colorDetect") { //Omit printout
//Serial.println("");
if (cmd=="resetwifi") {
WiFi.begin(P1.c_str(), P2.c_str());
Serial.print("Connecting to ");
Serial.println(P1);
delay(500);
Serial.println("");
Serial.println("STAIP: "+WiFi.localIP().toString());
Feedback="STAIP: "+WiFi.localIP().toString();
else if (cmd=="restart") {
ESP.restart();
else if (cmd=="cm"){
else if (cmd=="quality") {
sensor_t * s = esp_camera_sensor_get();
s->set_quality(s, val);
else if (cmd=="contrast") {
sensor_t * s = esp_camera_sensor_get();
s->set_contrast(s, val);
else if (cmd=="brightness") {
sensor_t * s = esp_camera_sensor_get();
s->set_brightness(s, val);
else {
if (Feedback=="") {
Feedback=Command;
void setup() {
WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0);
Serial.begin(115200);
Serial.setDebugOutput(true);
Serial.println();
camera_config_t config;
config.ledc_channel = LEDC_CHANNEL_0;
config.ledc_timer = LEDC_TIMER_0;
config.pin_d0 = Y2_GPIO_NUM;
config.pin_d1 = Y3_GPIO_NUM;
config.pin_d2 = Y4_GPIO_NUM;
config.pin_d3 = Y5_GPIO_NUM;
config.pin_d4 = Y6_GPIO_NUM;
config.pin_d5 = Y7_GPIO_NUM;
config.pin_d6 = Y8_GPIO_NUM;
config.pin_d7 = Y9_GPIO_NUM;
config.pin_xclk = XCLK_GPIO_NUM;
config.pin_pclk = PCLK_GPIO_NUM;
config.pin_vsync = VSYNC_GPIO_NUM;
config.pin_href = HREF_GPIO_NUM;
config.pin_sscb_sda = SIOD_GPIO_NUM;
config.pin_sscb_scl = SIOC_GPIO_NUM;
config.pin_pwdn = PWDN_GPIO_NUM;
config.pin_reset = RESET_GPIO_NUM;
config.xclk_freq_hz = 20000000;
config.pixel_format = PIXFORMAT_JPEG;
if(psramFound()){
config.frame_size = FRAMESIZE_UXGA;
} else {
config.frame_size = FRAMESIZE_SVGA;
config.fb_count = 1;
// camera init
if (err != ESP_OK) {
delay(1000);
ESP.restart();
sensor_t * s = esp_camera_sensor_get();
s->set_framesize(s, FRAMESIZE_CIF);
//UXGA|SXGA|XGA|SVGA|VGA|CIF|QVGA|HQVGA|QQVGA 設定初始化影像解析度
WiFi.mode(WIFI_AP_STA);
WiFi.begin(ssid, password);
delay(1000);
delay(500);
if ((StartTime+10000) < millis())
break;
if (WiFi.status() == WL_CONNECTED) {
Serial.println(WiFi.localIP());
server.begin();
void loop() {
Feedback="";Command="";cmd="";P1="";P2="";P3="";P4="";P5="";P6="";P7="";P8=
"";P9="";
ReceiveState=0,cmdState=1,strState=1,questionstate=0,equalstate=0,semicolon
state=0;
if (client) {
while (client.connected()) {
if (client.available()) {
char c = client.read();
getCommand(c);
if (c == '\n') {
if (currentLine.length() == 0) {
if (cmd=="colorDetect") {
camera_fb_t * fb = NULL;
fb = esp_camera_fb_get();
if(!fb) {
delay(1000);
ESP.restart();
//ANN:1
client.println("Access-Control-Allow-Origin: *");
client.println("Access-Control-Allow-Headers: Origin, X-
Requested-With, Content-Type, Accept");
client.println("Access-Control-Allow-Methods:
GET,POST,PUT,DELETE,OPTIONS");
client.println("Content-Type: image/jpeg");
client.println("Content-Disposition: form-data;
name=\"imageFile\"; filename=\"picture.jpg\"");
client.println("Connection: close");
client.println();
if (n+1024<fbLen) {
client.write(fbBuf, 1024);
fbBuf += 1024;
else if (fbLen%1024>0) {
client.write(fbBuf, remainder);
esp_camera_fb_return(fb);
else {
//ANN:1
client.println("Access-Control-Allow-Headers: Origin, X-
Requested-With, Content-Type, Accept");
client.println("Access-Control-Allow-Methods:
GET,POST,PUT,DELETE,OPTIONS");
client.println("Access-Control-Allow-Origin: *");
client.println("Connection: close");
client.println();
String Data="";
if (cmd!="")
Data = Feedback;
else {
int Index;
client.print(Data.substring(Index, Index+1000));
client.println();
Feedback="";
break;
} else {
currentLine = "";
else if (c != '\r') {
currentLine += c;
if ((currentLine.indexOf("/?")!=-1)&&(currentLine.indexOf("
HTTP")!=-1)) {
if (Command.indexOf("stop")!=-1) {
client.println();
client.println();
client.stop();
currentLine="";
Feedback="";
ExecuteCommand();
}
}
delay(1);
client.stop();
if (c=='?') ReceiveState=1;
if (ReceiveState==1) {
Command=Command+String(c);
if (c=='=') cmdState=0;
if (c==';') strState++;
if ((cmdState==1)&&((c!='?')||(questionstate==1))) cmd=cmd+String(c);
if ((cmdState==0)&&(strState==1)&&((c!='=')||(equalstate==1)))
P1=P1+String(c);
if ((cmdState==0)&&(strState==2)&&(c!=';')) P2=P2+String(c);
if ((cmdState==0)&&(strState==3)&&(c!=';')) P3=P3+String(c);
if ((cmdState==0)&&(strState==4)&&(c!=';')) P4=P4+String(c);
if ((cmdState==0)&&(strState==5)&&(c!=';')) P5=P5+String(c);
if ((cmdState==0)&&(strState==6)&&(c!=';')) P6=P6+String(c);
if ((cmdState==0)&&(strState==7)&&(c!=';')) P7=P7+String(c);
if ((cmdState==0)&&(strState==8)&&(c!=';')) P8=P8+String(c);
if ((cmdState==0)&&(strState>=9)&&((c!=';')||(semicolonstate==1)))
P9=P9+String(c);
if (c=='?') questionstate=1;
if (c=='=') equalstate=1;
if ((strState>=9)&&(c==';')) semicolonstate=1;
index_OCV_ColorTrack
/**********
***********/
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<!----ANN:3--->
</head>
<style>
html {
body {
background-color: #F7F7F2;
margin: 0px;
h1 {
font-size: 1.6rem;
color:white;
text-align: center;
.topnav {
overflow: hidden;
background-color: #0A1128;
.main-controls{
padding-top: 5px;
h2 {
color: #0A1128;
font-size: 1rem;
.section {
margin: 2px;
padding: 10px;
.column{
float: left;
width: 50%
}
table {
margin: 0;
width: 90%;
border-collapse: collapse;
th{
text-align: center;
.row{
margin-right:50px;
margin-left:50px;
#colorDetect{
border: none;
color: #FEFCFB;
background-color: #0A1128;
padding: 15px;
text-align: center;
display: inline-block;
font-size: 16px;
border-radius: 4px;
#restart{
border: none;
color: #FEFCFB;
background-color: #7B0828;
padding: 15px;
text-align: center;
display: inline-block;
font-size: 16px;
border-radius: 4px;
button{
border: none;
color: #FEFCFB;
background-color: #0A1128;
padding: 10px;
text-align: center;
display: inline-block;
border-radius: 4px;
</style>
<body>
<div class="topnav">
</div>
<div class="main-controls">
<table>
<tr>
</tr>
</table>
</div>
<div class="container">
<div class="section">
<h2>Video Streaming</h2>
<center><canvas id="canvas"
style="display:none"></canvas></center>
</div>
</div>
<div class="section">
<table>
<tr>
<td>Quality</td>
</tr>
<tr>
<td>Brightness</td>
</tr>
<tr>
<td>Contrast</td>
</table>
</div>
<!-----ANN:5---->
<div class="section">
<table>
<tr>
<td>R min:   <span
id="RMINdemo"></span></td>
<td>R max:   <span
id="RMAXdemo"></span></td>
</tr>
<tr>
<td>G min:   <span
id="GMINdemo"></span></td>
<td>G max:   <span
id="GMAXdemo"></span></td>
</tr>
<tr>
<td>B min:   <span id
="BMINdemo"></span></td>
<td><input type="range" id="bmin" min="0" max="255"
value="0" class = "slider"> </td>
<td>B max:   <span
id="BMAXdemo"></span></td>
</tr>
</table>
</div>
<div class="section">
<table>
<tr>
<td>Minimum Threshold:   <span
id="THRESH_MINdemo"></span></td>
</tr>
</table>
</div>
<!----ANN:9--->
<div class="section">
<h2>Color Probe</h2>
<table>
<tr>
<td>X probe:   <span
id="X_PROBEdemo"></span></td>
</tr>
</table>
</div>
</div> <!------endfirstcolumn---------------->
<div class="section">
<h2>Image Mask</h2>
<canvas id="imageMask"></canvas>
</div>
<div class="section">
<h2>Image Canvas</h2>
<canvas id="imageCanvas"></canvas>
</div>
<div class="section">
<table>
<tr>
</tr>
<tr>
<td>Invert: <span id="INVERTdemo"></span></td>
</td>
</tr>
</table>
</div>
<div class="section">
<table>
<tr>
<td><strong>XCM:</strong> <span
id="XCMdemo"></span></td>
<td><strong>YCM:</strong> <span
id="YCMdemo"></span></td>
</tr>
</table>
</div>
<div class="section">
<div id="message"></div>
</div>
</div> <!------end2ndcolumn------------------------>
</div> <!-----endrow---------------------->
</div> <!------endcontainer-------------->
<!--------------- </body>----------------->
<!----------------</html>----------------->
<div class="modal"></div>
<script>
var myTimer;
var restartCount=0;
let currentStream;
let faceDetection;
let x_cm = 0;
let y_cm = 0;
var RMIN=0;
var GMAX=50;
var GMIN=0;
var BMAX=50;
var BMIN=0;
var THRESH_MIN=120;
var X_PROBE=200;
var Y_PROBE=196;
var R=0;
var G=0;
var B=0;
var A=0;
clearInterval(myTimer);
myTimer = setInterval(function(){error_handle();},5000);
ShowImage.src=location.origin+'/?colorDetect='+Math.random();
//ANN:READY
var Module = {
onRuntimeInitialized(){onOpenCvReady();}
function onOpenCvReady(){
//alert("onOpenCvReady");
console.log("OpenCV IS READY!!!");
drawReadyText();
document.body.classList.remove("loading");
function error_handle() {
restartCount++;
clearInterval(myTimer);
if (restartCount<=2) {
myTimer = setInterval(function(){colorDetect.click();},10000);
ifr.src = document.location.origin+'?restart';
else
message.innerHTML = "Get still error. <br>Please close the page and check
ESP32-CAM.";
colorDetect.style.display = "block";
//alert("SHOW IMAGE");
console.log("SHOW iMAGE");
clearInterval(myTimer);
restartCount=0;
canvas.setAttribute("width", ShowImage.width);
canvas.setAttribute("height", ShowImage.height);
canvas.style.display = "block";
imageCanvas.setAttribute("width", ShowImage.width);
imageCanvas.setAttribute("height", ShowImage.height);
imageCanvas.style.display = "block";
imageMask.setAttribute("width", ShowImage.width);
imageMask.setAttribute("height", ShowImage.height);
imageMask.style.display = "block";
context.drawImage(ShowImage,0,0,ShowImage.width,ShowImage.height);
DetectImage();
fetch(location.origin+'/?restart=stop');
fetch(document.location.origin+'/?quality='+this.value+';stop');
fetch(document.location.origin+'/?brightness='+this.value+';stop');
fetch(document.location.origin+'/?contrast='+this.value+';stop');
//alert("DETECT IMAGE");
console.log("DETECT IMAGE");
/******opencv***********/
//ANN:4
arows = src.rows;
acols = src.cols;
aarea = arows*acols;
adepth = src.depth();
atype = src.type();
achannels = src.channels();
/*******COLOR DETECT***********/
//ANN:6
RMAXoutput.innerHTML = RMAXslider.value;
RMAXslider.oninput = function(){
RMAXoutput.innerHTML = this.value;
RMAX = parseInt(RMAXoutput.innerHTML,10);
console.log("RMAX=" + RMAX);
}
console.log("RMAX=" + RMAX);
RMINoutput.innerHTML = RMINslider.value;
RMINslider.oninput = function(){
RMINoutput.innerHTML = this.value;
RMIN = parseInt(RMINoutput.innerHTML,10);
console.log("RMIN=" + RMIN);
console.log("RMIN=" + RMIN);
GMAXoutput.innerHTML = GMAXslider.value;
GMAXslider.oninput = function(){
GMAXoutput.innerHTML = this.value;
GMAX = parseInt(GMAXoutput.innerHTML,10);
console.log("GMAX=" + GMAX);
GMINoutput.innerHTML = GMINslider.value;
GMINslider.oninput = function(){
GMINoutput.innerHTML = this.value;
GMIN = parseInt(GMINoutput.innerHTML,10);
}
console.log("GMIN=" + GMIN);
BMAXoutput.innerHTML = BMAXslider.value;
BMAXslider.oninput = function(){
BMAXoutput.innerHTML = this.value;
BMAX = parseInt(BMAXoutput.innerHTML,10);
console.log("BMAX=" + BMAX);
BMINoutput.innerHTML = BMINslider.value;
BMINslider.oninput = function(){
BMINoutput.innerHTML = this.value;
BMIN = parseInt(BMINoutput.innerHTML,10);
console.log("BMIN=" + BMIN);
THRESH_MINoutput.innerHTML = THRESH_MINslider.value;
THRESH_MINslider.oninput = function(){
THRESH_MINoutput.innerHTML = this.value;
THRESH_MIN = parseInt(THRESH_MINoutput.innerHTML,10);
}
//ANN:9A
X_PROBEoutput.innerHTML = X_PROBEslider.value;
X_PROBEslider.oninput = function(){
X_PROBEoutput.innerHTML = this.value;
X_PROBE = parseInt(X_PROBEoutput.innerHTML,10);
console.log("X_PROBE=" + X_PROBE);
Y_PROBEoutput.innerHTML = Y_PROBEslider.value;
Y_PROBEslider.oninput = function(){
Y_PROBEoutput.innerHTML = this.value;
Y_PROBE = parseInt(Y_PROBEoutput.innerHTML,10);
console.log("Y_PROBE=" + Y_PROBE);
document.getElementById('trackButton').onclick = function(){
TRACKoutput.innerHTML = b_tracker;
//var XCMoutput = document.getElementById("XCMdemo");
//XCMoutput.innerHTML = x_cm;
document.getElementById('invertButton').onclick = function(){
INVERToutput.innerHTML = b_invert;
/**/
document.getElementById('contourButton').onclick = function(){
CONTOURoutput.innerHTML = b_contour;
/**/
let tracker = 0;
TRACKoutput.innerHTML = b_tracker;
XCMoutput.innerHTML = 0;
YCMoutput.innerHTML = 0;
INVERToutput.innerHTML = b_invert;
CONTOURoutput.innerHTML = b_contour;
//ANN:8
clear_canvas();
orig = cv.imread(ShowImage);
cv.split(orig,rgbaPlanes); //SPLIT
let GP = rgbaPlanes.get(1);
let RP = rgbaPlanes.get(0);
cv.merge(rgbaPlanes,orig);
drawColRowText(acols,arows);
//ANN:9C
drawRGB_PROBE_Text();
//ANN:9b
cv.circle(src,point4,5,[255,255,255,255],2,cv.LINE_AA,0);
//ANN:7
cv.inRange(src,low,high,mask1);
cv.threshold(mask1,mask,THRESH_MIN,255,cv.THRESH_BINARY);
//ANN:9
if(b_invert==true){
cv.bitwise_not(mask,mask2);
/*******start contours***************/
//ANN:10
if(b_tracker == true){
try{
if(b_invert==false){
//ANN:11
cv.findContours(mask,contours,hierarchy,cv.RETR_CCOMP,cv.CHAIN_APPROX_SIMPL
E);
//findContours(source image, array of contours found, hierarchy of
contours
//algorithm method)
else{
cv.findContours(mask2,contours,hierarchy,cv.RETR_CCOMP,cv.CHAIN_APPROX_SIMP
LE);
//draw contours
if(b_contour==true){
cv.drawContours(src,contours,i,[0,0,0,255],2,cv.LINE_8,hierarchy,100)
//ANN:12
let cnt;
let Moments;
let M00;
let M10;
//let x_cm;
//let y_cm;
//ANN:13
for(let k = 0; k < contours.size(); k++){
cnt = contours.get(k);
Moments = cv.moments(cnt,false);
M00Array[k] = Moments.m00;
// cnt.delete();
//ANN13A
console.log("MAXAREAARG = "+max_area_arg);
//console.log("MAXTESTAREAARG = "+max_test_area_arg);
//cnt = contours.get(54);
Moments = cv.moments(cnt,false);
M00 = Moments.m00;
M10 = Moments.m10;
M01 = Moments.m01;
XCMoutput.innerHTML = Math.round(x_cm);
YCMoutput.innerHTML = Math.round(y_cm);
console.log("M00 = "+M00);
console.log("XCM = "+Math.round(x_cm));
console.log("YCM = "+Math.round(y_cm));
//fetch(document.location.origin+'/?xcm='+Math.round(x_cm)+';stop');
fetch(document.location.origin+'/?cm='+Math.round(x_cm)+';'+Math.round(y_cm
)+';stop');
//ANN:14
//let rotatedRect=cv.minAreaRect(cnt);
//for(let j=0;j<4;j++){
// cv.line(src,vertices[j],
// vertices[(j+1)%4],[0,0,255,255],2,cv.LINE_AA,0);
//}
cv.rectangle(src,point1,point2,[0,0,255,255],2,cv.LINE_AA,0);
cv.circle(src,point3,2,[0,0,255,255],2,cv.LINE_AA,0);
else{
if(ArgMaxArea==-1){
else{ //ArgMaxArea=-2
cnt.delete();
/*******end contours note cnt line one up**************/
drawXCM_YCM_Text();
}//end try
catch{
clear_canvas();
drawErrorTracking_Text();
else{
XCMoutput.innerHTML = 0;
YCMoutput.innerHTML = 0;
if(b_invert==false){
cv.imshow('imageMask', mask);
else{
cv.imshow('imageMask', mask2);
//cv.imshow('imageMask', R);
cv.imshow('imageCanvas', src);
//ANN:8A
src.delete();
high.delete();
low.delete();
orig.delete();
mask1.delete();
mask2.delete();
mask.delete();
contours.delete();
hierarchy.delete();
//cnt.delete();
RP.delete();
/******end opencv***********/
setTimeout(function(){colorDetect.click();},500);
}//end detectimage
function MaxAreaArg(arr){
if (arr.length == 0) {
return -1;
}
var max = arr[0];
var maxIndex = 0;
max = 0;
maxIndex = i;
max = arr[i];
dupIndexCount = 0;
dupIndexCount++;
if(dupIndexCount==0){
return maxIndex;
else{
return -2;
}//end MaxAreaArg
function clear_canvas(){
ctx.clearRect(0,0,txtcanvas.width,txtcanvas.height);
ctx.rect(0,0,txtcanvas.width,txtcanvas.height);
ctx.fillStyle="red";
ctx.fill();
function drawReadyText(){
ctx.fillStyle = 'black';
ctx.fillText('OpenCV.JS READY',txtcanvas.width/4,txtcanvas.height/10);
function drawColRowText(x,y){
ctx.fillStyle = 'black';
ctx.fillText('ImageCols='+x,0,txtcanvas.height/10);
ctx.fillText('ImageRows='+y,txtcanvas.width/2,txtcanvas.height/10);
function drawRGB_PROBE_Text(){
ctx.fillStyle = 'black';
ctx.fillText('Rp='+R,0,2*txtcanvas.height/10);
ctx.fillText('Gp='+G,txtcanvas.width/4,2*txtcanvas.height/10);
ctx.fillText('Bp='+B,txtcanvas.width/2,2*txtcanvas.height/10);
ctx.fillText('Ap='+A,3*txtcanvas.width/4,2*txtcanvas.height/10);
function drawXCM_YCM_Text(){
ctx.fillStyle = 'black';
ctx.fillText('XCM='+Math.round(x_cm),0,3*txtcanvas.height/10);
ctx.fillText('YCM='+Math.round(y_cm),txtcanvas.width/4,3*txtcanvas.height/1
0);
function drawErrorTracking_Text(){
ctx.fillStyle = 'black';
</script>
</body>
</html>
)rawliteral";
Penjelasan Bagaimana Kode Program Bekerja
Di <head> file HTML, kita harus menambahkan semua metadata yang diperlukan.
Judul halaman web adalah Aplikasi Firebase ESP, tetapi Anda dapat mengubahnya di baris
berikut.
<link rel="stylesheet"
href="https://use.fontawesome.com/releases/v5.7.2/css/all.cs
s" integrity="sha384-
fnmOCqbTlWIlj8LyTjo7mOUStjsKC4pOpQbqyi7RrhN7udi9RwhKkMHpvLbH
G9Sr" crossorigin="anonymous">
Referensikan file style.css eksternal untuk memformat halaman HTML.
<link rel="stylesheet" type="text/css" href="style.css">
Anda harus menambahkan baris berikut untuk dapat menggunakan Firebase dengan aplikasi
Anda.
<script
src="https://www.gstatic.com/firebasejs/8.10.0/firebase-
app.js"></script>
Anda juga harus menambahkan produk Firebase yang ingin digunakan. Dalam contoh ini, kami
menggunakan Penyimpanan dan Otentikasi.
<script
src="https://www.gstatic.com/firebasejs/8.8.1/firebase-
auth.js"></script>
<script
src="https://www.gstatic.com/firebasejs/8.10.0/firebase-
storage.js"></script>
Kemudian, ganti objek firebaseConfig dengan yang Anda dapatkan dari langkah ini.
const firebaseConfig = {
apiKey: "AIzaSyA2sJiXEcOWVZqdrh348eUO3aHothDz7sE",
authDomain: "esp-firebase-demo.firebaseapp.com",
databaseURL: "https://esp-firebase-demo-default-
rtdb.europe-west1.firebasedatabase.app",
projectId: "esp-firebase-demo",
storageBucket: "esp-firebase-demo.appspot.com",
messagingSenderId: "1086300631316",
appId: "1:1086300631316:web:316410846aaeb0bb8c9201"
};
Terakhir, Firebase diinisialisasi, dan kami membuat dua variabel global storage, dan
storageRef, yang merujuk ke Firebase Storage dan referensi ke layanan penyimpanan.
// Initialize Firebase
firebase.initializeApp(firebaseConfig);
<button onclick="window.location.reload();">Refresh</button>
Setelah membuat perubahan yang diperlukan (memasukkan objek firebaseConfig Anda),
Anda dapat menyimpan file HTML.