2024年12月11日 星期三

C# 用Yolo做目標檢測

Purpose:
因為最近在做Data Labeling的工作, 想說利用C#導入YOLO Model 辨識物件偵測的演算法, 來做圖片的影像辨識!
開發環境:
1. Microsoft Visual Studio Community 2022 (64 位元) - Current版本 17.12.3
2. NuGet - YoloSharp Package
3. NuGet -Microsoft.ML.OnnxRuntime Package

圖一: NuGet Package
圖二: 程式效果
圖三: 程式效果
圖四: 程式效果
Yolo (You only look once)
Yolo 是屬於物件偵測 (object detection)類神經網路演算法。物件偵測是屬於電腦視覺中比較簡單的任務,可以在一張圖片中找到某些特定的物體,同時物件偵測不僅要求我們識別這些物體的種類,同時要求我們標出這些物體的位置。

VS2022 程式介面與架構
介面安排如下:
圖五: 程式物件Layout
圖六:程式coding
Program is put into Github respository !
https://docs.ultralytics.com/zh/models/yolo11/#overview

2024年12月2日 星期一

Data Labeling 學習

機器學習依賴大量高質量的資料來實現革命性的應用。要更好地理解資料標註的重要性,必須了解不同類型的機器學習:監督式學習、非監督式學習和強化學習。
監督式學習是最常見也是最強大的機器學習應用之一,從垃圾郵件檢測到自駕車檢測人、車和其他障礙物都有使用。監督式學習使用大量有標籤的數據來訓練模型,以準確地分類數據或預測結果。
為了創建高品質的監督式學習模型,您需要大量具有高質量標籤的數據。那麼,如何標記數據呢?首先,您需要確定誰將標記您的數據。建立標記團隊有幾種不同的方法,每種方法都有其優點、缺點和考慮因素。讓我們首先考慮是最好讓人類參與標記過程,完全依賴自動化數據標記,還是結合兩種方法。

CVAT(Computer Vision Annotation Tool)
CVAT(Computer Vision Annotation Tool)是一個開源的計算機視覺標註工具,它提供了一個方便易用的界面,用於創建和維護標註數據集。CVAT具有強大的功能,可以標註圖像和視頻數據,並支持不同的標註類型,如邊界框、遮罩和關鍵點等。它是一個非常有用的工具,尤其是對於計算機視覺項目和研究人員來說。
訓練電腦視覺模型需要許多標注過的圖片,而這些過程會消耗大量的時間。為了節省時間,有人致力於開發好用的影像標註軟體,其中之一是 Computation Vision Annotation Tool(CVAT) 。原本由Intel開發,然後在2022年轉為由OpenCV維護的開源專案。
CVAT的用途非常廣泛,它可以應用於各種領域,包括目標檢測、物體追蹤、行為識別等。對於機器學習和深度學習模型的訓練來說,標註數據集是非常重要的一步,CVAT提供了一個方便且高效的方式來標註數據,以幫助模型更好地理解和學習圖像和視頻數據。



2024年9月1日 星期日

Tektronix MSO64B C# GUI Application

Purpose:
為了長時間去偵測TekTronix的MSO64B量到的數據並記錄下來, 因此才有這個專案產生. 利用C#寫的GUI來做這個動作!
Fundamental:
TEKTRONIX太克
6B 系列MSO 混合訊號示波器
GUI:
包含連接儀器控制, 儀器channel顯示控制, 個別channel 顯示 和即時儲存量測資料

Demo:



2024年8月13日 星期二

C# 利用ffmpeg將WebCAM影像Streaming to RTSP server

Purpose:
利用C#架構一個RTSP 影像streaming的環境.包含WebCAM控制, FFMpeg使用 and Emgu.CV的應用 
Fundamental:
RTSP
即時串流協定(Real Time Streaming Protocol,RTSP)是一種網路應用協定,專為娛樂和通訊系統的使用,以控制串流媒體伺服器。該協定用於建立和控制終端之間的媒體對談。媒體伺服器的客戶端發布VCR命令,例如播放,錄製和暫停,以便於即時控制從伺服器到客戶端(影片點播)或從客戶端到伺服器(語音錄音)的媒體流。
FFmpeg
FFmpeg 是一個開放原始碼的自由軟體,它包含了音訊和視訊多種格式的錄影、轉檔、串流功能,同時也是一個音訊與視訊格式轉換函式庫(Library),許多開源的工具都是基於 FFmpeg 打造的。

Reference:
感謝分享:包含 RTSP windows server 和一些相關軟體介紹與下載!!
public void StartRTSP(string path)
    {
       
        if (_RTSPPara._process != null && !_RTSPPara._process.HasExited)
            return;

        _RTSPPara._process = new Process();
        _RTSPPara._process.StartInfo.UseShellExecute = false;
        _RTSPPara._process.StartInfo.FileName = @path;
        _RTSPPara._process.StartInfo.Arguments = "";
        _RTSPPara._process.StartInfo.RedirectStandardInput = true;
        _RTSPPara._process.StartInfo.RedirectStandardOutput = true;
        _RTSPPara._process.StartInfo.RedirectStandardError = true;
        _RTSPPara._process.EnableRaisingEvents = true;
        _RTSPPara._process.StartInfo.CreateNoWindow = true;
        try
        {
            var started = _RTSPPara._process.Start();
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message + Environment.NewLine + ex.StackTrace);
        }

        _RTSPPara._process.BeginErrorReadLine();
        _RTSPPara._process.BeginOutputReadLine();
    }


public void StartFFmpeg(string path, string webname)
    {
        string rtspServer = "rtsp://localhost:8554/test";
        string ffmpegCommand =
                $"-f dshow -i video=\"{webname}\" -vcodec libx264 -preset:v ultrafast -tune:v zerolatency -rtsp_transport tcp -f rtsp {rtspServer}";
        if (_FFmpegPara._process != null && !_FFmpegPara._process.HasExited)
            return;

        _FFmpegPara._process = new Process();
        _FFmpegPara._process.StartInfo.UseShellExecute = false;
        _FFmpegPara._process.StartInfo.FileName = @path;
        _FFmpegPara._process.StartInfo.Arguments = ffmpegCommand;
        _FFmpegPara._process.StartInfo.RedirectStandardInput = true;
        _FFmpegPara._process.StartInfo.RedirectStandardOutput = true;
        _FFmpegPara._process.StartInfo.RedirectStandardError = true;
        _FFmpegPara._process.EnableRaisingEvents = true;
        _FFmpegPara._process.StartInfo.CreateNoWindow = true;
        _FFmpegPara._process.StartInfo.Verb = "RunAs";
        try
        {
            var started = _FFmpegPara._process.Start();
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message + Environment.NewLine + ex.StackTrace);
        }

        _FFmpegPara._process.BeginErrorReadLine();
        _FFmpegPara._process.BeginOutputReadLine();
    }

        private void button7_Click(object sender, EventArgs e)
        {

            if (FFmpeg._FFmpegPara.RunisFinish == true)
            {
                //button5.BackColor = Color.AliceBlue;
                button7.BackColor = Color.Green;
                WebCAM[0].show_screen = true;
                myVideoCapture = new VideoCapture("rtsp://127.0.0.1:8554/test");
                myVideoCapture.ImageGrabbed += imageGrabbedEvent;
                myVideoCapture.Start();
            }
            else if (WebCAM[0].run == true)
            {
                //button5.BackColor = Color.Green;
                button7.BackColor = Color.AliceBlue;
                if (myVideoCapture != null)
                {
                    myVideoCapture.ImageGrabbed -= imageGrabbedEvent;
                    Thread.Sleep(500);
                    myVideoCapture.Dispose();
                }

            }
        }

        public void imageGrabbedEvent(object sender, EventArgs arg)
        {
            try
            {
                TEmgu._EmguPara.ImageSource = myVideoCapture.QueryFrame().ToImage<Bgr, byte>();
            }
            catch (Exception Ex)
            {

            }
            if (TEmgu._EmguPara.ImageSource != null)
            {

                imageBox1.Image = TEmgu._EmguPara.ImageSource.Resize(imageBox1.Width, imageBox1.Height, Emgu.CV.CvEnum.Inter.Linear);

            } //----if (TEmgu._EmguPara.ImageSource != null)
           

        }

2024年7月10日 星期三

c# 如何檢測目錄檔案有變動

Purpose:
在一個測試系統中, 有時候會在特定的目錄中存放測試中的測試資料. 所以便可以在測試時去偵測特定目錄中是否有資料檔案產再去做資料的分析!
Method:

        DirectoryInfo dirInfo;
        FileSystemWatcher watcher = new FileSystemWatcher();
        private void button24_Click(object sender, EventArgs e)
        {
            button24.BackColor = Color.Green;
            button24.Enabled = false;

            FolderBrowserDialog folderDialog = new FolderBrowserDialog();
            folderDialog.SelectedPath = FileCtrl.CANFilePath;
            folderDialog.Description = "Select an listen Folder";

            if (folderDialog.ShowDialog() == DialogResult.OK)
            {
                string selectedPath = folderDialog.SelectedPath;
                dirInfo = new DirectoryInfo(selectedPath);
                string[] files = Directory.GetFiles(selectedPath);
                //設定所要監控的資料夾
                watcher.Path = selectedPath;
                CommonData.WriteMessage(DateTime.Now.ToString("yyyyMMdd_hhmmss_ffff") + " -- ", "選擇監聽目錄檔案於:" + watcher.Path, Color.Blue, Color.Green);
            }

            //FileSystemWatcher watcher = new FileSystemWatcher(@"C:\path\to\folder");

            watcher.NotifyFilter = NotifyFilters.Attributes
                                 | NotifyFilters.CreationTime
                                 | NotifyFilters.DirectoryName
                                 | NotifyFilters.FileName
                                 | NotifyFilters.LastAccess
                                 | NotifyFilters.LastWrite
                                 | NotifyFilters.Security
                                 | NotifyFilters.Size;

            //watcher.Changed += OnChanged;
            watcher.Created += OnCreated;
            //watcher.Deleted += OnDeleted;
            //watcher.Renamed += OnRenamed;
            //watcher.Error += OnError;

            if (comboBox_carmodel.Text == "PeakCAN_csv")
            {
                CommonData.Auto_Detect_Mode = 0;
                watcher.Filter = "*.csv";
            }
            else if (comboBox_carmodel.Text == "CANoe_asc")
            {
                CommonData.Auto_Detect_Mode = 1;
                watcher.Filter = "*.asc";
            }
           
            watcher.IncludeSubdirectories = true;
            watcher.EnableRaisingEvents = true;
            button24.BackColor = Color.PeachPuff;
            button24.Enabled = true;
        }

private void OnCreated(object sender, FileSystemEventArgs e)
        {
            dirInfo = new DirectoryInfo(e.FullPath.ToString());
            CommonData.FilterFlag = true;
            Thread.Sleep(100);
            CommonData.WriteMessage(DateTime.Now.ToString("yyyyMMdd_hhmmss_ffff") + " -- ", "新建檔案於:" + dirInfo.FullName.Replace(dirInfo.Name, ""), Color.Blue, Color.Green);
            CommonData.WriteMessage(DateTime.Now.ToString("yyyyMMdd_hhmmss_ffff") + " -- ", "新建檔案名稱:" + dirInfo.Name, Color.Blue, Color.Green);
            CommonData.WriteMessage(DateTime.Now.ToString("yyyyMMdd_hhmmss_ffff") + " -- ", "建立時間:" + dirInfo.CreationTime.ToString(), Color.Blue, Color.Green);
            CommonData.WriteMessage(DateTime.Now.ToString("yyyyMMdd_hhmmss_ffff") + " -- ", "目錄下共有:" + dirInfo.Parent.GetFiles().Count() + " 檔案", Color.Blue, Color.Green);
            CommonData.WriteMessage(DateTime.Now.ToString("yyyyMMdd_hhmmss_ffff") + " -- ", "目錄下共有:" + dirInfo.Parent.GetDirectories().Count() + " 資料夾", Color.Blue, Color.Green);

        }

2024年7月9日 星期二

C# 讀取超大檔案方式

Purpose:
在專案中, 有一需求是去搜尋一些500MB個別檔案重複資料, 嘗試了很多讀取檔案的方式. 最後試出來最快的方法.
Method:
using System.IO;

string strLine;
const int MAX_BUFFER = 33554432; //32MB 
using (FileStream fs = File.Open(csvFile, FileMode.Open, FileAccess.Read))
using (BufferedStream bs = new BufferedStream(fs, MAX_BUFFER)
using (StreamReader sr = new StreamReader(bs))
{  
    while (!sr.EndOfStream)
   {
        while ((strLine = sr.ReadLine()) != null)
       {
               //---處理資料
        }

    }
    sr.Close();
    sr.Dispose();
}

希望對你們有幫助!!

2024年7月1日 星期一

CAN Bus Sniffer by PeakCAN windows software GUI

Purpose:
經由這篇文章PeakCAN windows software for CAN Bus message後, 再次利用PeakCAN的 PCANBasic API for C#的sample修改為自己的CAN Bus Sniffer擷取在CAN BUS介面上的資料!
特點
1. 增加monitor功能 
所有的CAN Bus上的資料 Tx and Rx 都可以即時監控.
2. 增加可同時多個Tx 傳送的CAN Bus資料依據個別定義的時序傳送
After reading this article PeakCAN windows software for CAN Bus message, I once again used the sample of PeakCAN's PCANBasic API for C# to modify it into my own CAN Bus Sniffer to capture the data on the CAN BUS interface!
Features
1. Add monitor function
All data Tx and Rx on the CAN Bus can be monitored in real time.
2. Added CAN Bus data that can be transmitted by multiple Tx at the same time according to individually defined timings.
利用自己製作的ESP32 CAN module搭配CAN Hacker software去做驗證!

YouTubeDemo:


2024年6月30日 星期日

ESP32CAM + 麥克納姆倫 + Car

Purpose:
這個專題結合了ESP32CAM影像Web傳輸及Web http Control Mecanum wheel Car to move.



Fundamental&BOM:
Mecanum wheel and TT motor
Reference https://en.wikipedia.org/wiki/Mecanum_wheel
方向控制:


L298N

Mega2560


Circuit:









Reference
https://randomnerdtutorials.com/esp32-cam-car-robot-web-server/

All source code put on:

ESP32CAM Code:
#include <WiFi.h>
#include "soc/soc.h"             //用於電源不穩不重開機
#include "soc/rtc_cntl_reg.h"    //用於電源不穩不重開機
#include "esp_camera.h"    

// Select camera model
//#define CAMERA_MODEL_WROVER_KIT
//#define CAMERA_MODEL_M5STACK_PSRAM
#define CAMERA_MODEL_AI_THINKER

const char* ssid = "ras_2.4";   //Enter SSID WIFI Name
const char* password = "181415181415";   //Enter WIFI Password

#if defined(CAMERA_MODEL_WROVER_KIT)
#define PWDN_GPIO_NUM    -1
#define RESET_GPIO_NUM   -1
#define XCLK_GPIO_NUM    21
#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      19
#define Y4_GPIO_NUM      18
#define Y3_GPIO_NUM       5
#define Y2_GPIO_NUM       4
#define VSYNC_GPIO_NUM   25
#define HREF_GPIO_NUM    23
#define PCLK_GPIO_NUM    22


#elif defined(CAMERA_MODEL_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

#else
#error "Camera model not selected"
#endif

// GPIO Setting
extern int gpLb =  2; // Left 1
extern int gpLf = 14; // Left 2
extern int gpRb = 15; // Right 1
extern int gpRf = 13; // Right 2
extern int gpLed =  4; // Light
extern String WiFiAddr ="";

void startCameraServer();

void setup() {
  WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0);  //關閉電源不穩就重開機的設定

  Serial.begin(115200);
  Serial.setDebugOutput(true);
  Serial.println();

  setupCam();

  pinMode(gpLb, OUTPUT); //Left Backward
  pinMode(gpLf, OUTPUT); //Left Forward
  pinMode(gpRb, OUTPUT); //Right Forward
  pinMode(gpRf, OUTPUT); //Right Backward
  pinMode(gpLed, OUTPUT); //Light

  //initialize
  digitalWrite(gpLb, LOW);
  digitalWrite(gpLf, LOW);
  digitalWrite(gpRb, LOW);
  digitalWrite(gpRf, LOW);
  digitalWrite(gpLed, LOW);

  WifiConnect();

  startCameraServer();

  Serial.print("Camera Ready! Use 'http://");
  Serial.print(WiFi.localIP());
  Serial.println("' to connect");

}

void loop()
{
 
  while(1)
  {
    if (WiFi.status() != WL_CONNECTED)
    {
      WifiConnect();
    }
   
  }

}
//-----------------------------------------
//開始WiFi連線
void WifiConnect()
{
  //開始WiFi連線
  //WiFi.begin("Wokwi-GUEST", "", 6);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED)
  {
    delay(500);
    Serial.print(".");
  }
 
  WiFiAddr = WiFi.localIP().toString();
  Serial.print("IP Address:");
  Serial.println(WiFi.localIP());
  Serial.println("WiFi連線成功");
}
//-----------------------------------------
//鏡頭設定
void setupCam() {  
  // #define CAMERA_MODEL_AI_THINKER
  //視訊組態設定  https://github.com/espressif/esp32-camera/blob/master/driver/include/esp_camera.h
  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;
  //
  // WARNING!!! PSRAM IC required for UXGA resolution and high JPEG quality
  //            Ensure ESP32 Wrover Module or other board with PSRAM is selected
  //            Partial images will be transmitted if image exceeds buffer size
  //  
  // if PSRAM IC present, init with UXGA resolution and higher JPEG quality
  //                      for larger pre-allocated frame buffer.
  if(psramFound()){  //是否有PSRAM(Psuedo SRAM)記憶體IC
    config.frame_size = FRAMESIZE_UXGA;
    config.jpeg_quality = 10;
    config.fb_count = 2;
  } else {
    config.frame_size = FRAMESIZE_SVGA;
    config.jpeg_quality = 12;
    config.fb_count = 1;
  }

  //視訊初始化
  esp_err_t err = esp_camera_init(&config);
  if (err != ESP_OK) {
    Serial.printf("Camera init failed with error 0x%x", err);
    ESP.restart();
  }

  //可自訂視訊框架預設大小(解析度大小)
  sensor_t * s = esp_camera_sensor_get();
  // initial sensors are flipped vertically and colors are a bit saturated
  if (s->id.PID == OV3660_PID) {
    s->set_vflip(s, 1); // flip it back
    s->set_brightness(s, 1); // up the brightness just a bit
    s->set_saturation(s, -2); // lower the saturation
  }
  // drop down frame size for higher initial frame rate
  s->set_framesize(s, FRAMESIZE_CIF);    //解析度 UXGA(1600x1200), SXGA(1280x1024), XGA(1024x768), SVGA(800x600), VGA(640x480), CIF(400x296), QVGA(320x240), HQVGA(240x176), QQVGA(160x120), QXGA(2048x1564 for OV3660)

  //s->set_vflip(s, 1);  //垂直翻轉
  //s->set_hmirror(s, 1);  //水平鏡像
  //s->set_pRotation(s, 1);  //0=不旋轉 1-轉90度 2- 轉180度 3-轉270
}

Mega2560 Code:
#include <Robojax_L298N_DC_motor.h>
#include <Servo.h>
//-------------L298(1)---------------------------------------------------
// motor 1 settings
#define CHA_1 0
#define ENA_1 2 // this pin must be PWM enabled pin if Arduino board is used
#define IN1_1 30
#define IN2_1 32
// motor 2 settings
#define IN3_1 34
#define IN4_1 36
#define ENB_1 7// this pin must be PWM enabled pin if Arduino board is used
#define CHB_1 1
//-------------L298(2)---------------------------------------------------
// motor 3 settings
#define CHA_2 0
#define ENA_2 2 // this pin must be PWM enabled pin if Arduino board is used
#define IN1_2 38
#define IN2_2 40
// motor 4 settings
#define IN3_2 42
#define IN4_2 44
#define ENB_2 7// this pin must be PWM enabled pin if Arduino board is used
#define CHB_2 1

const int CCW = 2; // do not change
const int CW  = 1; // do not change

#define motor1 1
#define motor2 2

// for two motors without debug information // Watch video instruciton for this line: https://youtu.be/2JTMqURJTwg
Robojax_L298N_DC_motor motors1(IN1_1, IN2_1, ENA_1, CHA_1,  IN3_1, IN4_1, ENB_1, CHB_1);
Robojax_L298N_DC_motor motors2(IN1_2, IN2_2, ENA_2, CHA_2,  IN3_2, IN4_2, ENB_2, CHB_2);

#define Pin22   22
#define Pin23   23
#define Pin24   24
#define Pin25   25
 
Servo servo1;  //底座  
Servo servo2;  //左臂
Servo servo3;  //右臂
Servo servo4;  //夾子

void setup()
{
  Serial.begin(9600);
  pinMode(Pin22,  INPUT);
  pinMode(Pin23,  INPUT);
  pinMode(Pin24,  INPUT);
  pinMode(Pin25,  INPUT);
  Serial.println(F("init"));
  pinMode(IN1_1, OUTPUT);
  pinMode(IN2_1, OUTPUT);
  pinMode(IN3_1, OUTPUT);
  pinMode(IN4_1, OUTPUT);
  pinMode(ENA_1, OUTPUT);
  pinMode(ENB_1, OUTPUT);
  pinMode(IN1_2, OUTPUT);
  pinMode(IN2_2, OUTPUT);
  pinMode(IN3_2, OUTPUT);
  pinMode(IN4_2, OUTPUT);
  pinMode(ENA_2, OUTPUT);
  pinMode(ENB_2, OUTPUT);
  servo1.attach(8);  // 設置舵機控制腳位
  servo2.attach(9);
  servo3.attach(10);
  servo4.attach(11);

  servo1.write(30);
  servo2.write(50);
  servo3.write(20);
  servo4.write(20);
}

void loop()
{
  char key = 0;
  int a=1,b=1,c=1,d=1;
  int fg = 0;
  boolean flag= false;
 
  while(1)
  {  
    if ( Serial.available())
    {
      key = Serial.read();
      flag=true;
    }

    a= digitalRead (Pin22);
    b= digitalRead (Pin23);
    c= digitalRead (Pin24);
    d= digitalRead (Pin25);

    Serial.print(digitalRead(22)) ;  //purple >  14
    Serial.print(digitalRead(23)) ;  //blue 2
    Serial.print(digitalRead(24)) ;  //yellow > 13
    Serial.println(digitalRead(25)); //15
   

    if (flag==false)
    {
       if (a==1 and b==0 and c==1 and d==0) {
          key ='f';
       }
       else if (a==0 and b==1 and c==0 and d==1) {
          key ='b';
       }
       else if (a==1 and b==0 and c==0 and d==1) {
          key ='x';
       }
       else if (a==0 and b==1 and c==1 and d==0) {
         key ='y';
       }
       else if (a==0 and b==0 and c==1 and d==1) {
         key ='a';
       }
       else if (a==1 and b==1 and c==0 and d==0) {
         key ='c';
       }
       else if (a==0 and b==0 and c==0 and d==0) {
         key ='s';
       }
       else if (a==1 and b==1 and c==1 and d==1) {
         key ='s';
       }
       else if (a==0 and b==0 and c==0 and d==1) { //---servo action 1 A1 Stretch
         key ='1';
       }
       else if (a==0 and b==0 and c==1 and d==0) { //---servo action 2 A2 Clip
         key ='2';
       }
       else if (a==0 and b==1 and c==0 and d==0) { //---servo action 3 A3 Retract
         key ='3';
       }
       else if (a==1 and b==0 and c==0 and d==0) { //---servo action 4 A4 Turn
         key ='4';
       }
       else if (a==1 and b==1 and c==1 and d==0) { //---servo action 5 A5 Release<
         key ='5';
       }
       else if (a==1 and b==1 and c==0 and d==1) { //---servo action 6 A6 Origin
         key ='6';
       }
       else if (a==1 and b==0 and c==1 and d==1) { //---servo action 7
         key ='7';
       }
       else if (a==0 and b==1 and c==1 and d==1) { //---servo action 8
         key ='8';
       }
    }

    switch ( key ) {
      case 'f': // 前進
        Serial.println(F("Forward"));
        Forward();
        break;
      case 'b': // 後退
        Serial.println(F("Reverse"));
        Reverse();
        break;
      case 'l': // 左移
        Serial.println(F("Left"));
        Left();
        break;
      case 'r': // 右移
        Serial.println(F("Right"));
        Right();
        break;  
      case 'a': // 左平移
        Serial.println(F("ShiftLeft"));
        S_Left();
        break;
      case 'c': // 右平移
        Serial.println(F("ShiftRight"));
        S_Right();
        break;  
      case 'x': // 逆左旋轉
        Serial.println(F("X"));
        Xtrun();
        break;  
      case 'y': // 順右旋轉
        Serial.println(F("Y"));
        Ytrun();
        break;  
      case '1': // 伸爪
        Serial.println(F("3"));
        servo3.write(40);
        delay(500);
        Serial.println(F("1"));
        servo1.write(50);
        delay(500);
        servo1.write(70);
        delay(500);
        servo1.write(90);
        delay(500);
        servo1.write(130);
        delay(500);
        servo1.write(160);
        delay(500);
        break;  
      case '2': // 夾
        Serial.println(F("2"));
        servo2.write(70);
        delay(500);
        servo2.write(100);
        delay(500);
        servo2.write(140);
        delay(500);
        break;  
      case '3':  //收爪
        Serial.println(F("1"));
        servo1.write(110);
        delay(500);
        servo1.write(90);
        delay(500);
        servo1.write(50);
        delay(500);
        servo1.write(30);
        delay(500);
        break;  
      case '4':  //後轉
        Serial.println(F("4"));
        servo4.write(60);
        delay(500);
        servo4.write(90);
        delay(500);
        servo4.write(120);
        delay(500);
        servo4.write(160);
        delay(500);
        servo4.write(180);
        delay(500);
        break;
      case '5':  //放夾
        Serial.println(F("2"));
        servo2.write(120);
        delay(500);
        servo2.write(90);
        delay(500);
        servo2.write(50);
        delay(500);
        /**
        Serial.println(F("4"));
        servo4.write(160);
        delay(500);;
        servo4.write(120);
        delay(500);
        servo4.write(80);
        delay(500);
        servo4.write(40);
        delay(500);
        servo4.write(20);
        delay(500);**/
        break;  
      case '6': //回原點
        Serial.println(F("4"));
        servo4.write(160);
        delay(500);;
        servo4.write(120);
        delay(500);
        servo4.write(80);
        delay(500);
        servo4.write(40);
        delay(500);
        servo4.write(20);
        delay(500);
        break;
      case 's': // 前進
        //Serial.println(F("Stop"));
        Stop();
        break;
      default:
        Stop();
        flag= false;
        break;

    }
   
  } //---while(1)
}

void Forward() //電機前進
{
  motors1.brake(1);  
  motors1.brake(2);  
  motors2.brake(1);  
  motors2.brake(2);  

  motors1.rotate(motor1, 70, CW);//run motor1 at 60% speed in CW direction (1)
  motors1.rotate(motor2, 70, CCW);//run motor2 at 60% speed in CCW direction (2)
  motors2.rotate(motor1, 70, CCW);//run motor1 at 60% speed in CCW direction (3)
  motors2.rotate(motor2, 70, CW);//run motor2 at 60% speed in CW direction  (4)
}

void Reverse(){
  motors1.brake(1);  
  motors1.brake(2);  
  motors2.brake(1);  
  motors2.brake(2);
  motors1.rotate(motor1, 70, CCW);
  motors1.rotate(motor2, 70, CW);
  motors2.rotate(motor1, 70, CW);
  motors2.rotate(motor2, 70, CCW);
}
void Left()
{
  motors1.brake(1);  
  motors1.brake(2);  
  motors2.brake(1);  
  motors2.brake(2);  
  motors1.rotate(motor1, 70, CW);//run motor1 at 60% speed in CW direction (1)
  //motors1.rotate(motor2, 70, CCW);//run motor2 at 60% speed in CCW direction (2)
  //motors2.rotate(motor1, 70, CCW);//run motor1 at 60% speed in CCW direction (3)
  motors2.rotate(motor2, 70, CW);//run motor2 at 60% speed in CW direction  (4)
}
void S_Left()  //左平移
{
  motors1.brake(1);  
  motors1.brake(2);  
  motors2.brake(1);  
  motors2.brake(2);  
  motors1.rotate(motor1, 70, CW);//run motor1 at 60% speed in CW direction (1)
  motors1.rotate(motor2, 70, CW);//run motor2 at 60% speed in CCW direction (2)
  motors2.rotate(motor1, 70, CW);//run motor1 at 60% speed in CCW direction (3)
  motors2.rotate(motor2, 70, CW);//run motor2 at 60% speed in CW direction  (4)
}
void S_Right()  //右平移
{
  motors1.brake(1);  
  motors1.brake(2);  
  motors2.brake(1);  
  motors2.brake(2);  
  motors1.rotate(motor1, 70, CCW);//run motor1 at 60% speed in CW direction (1)
  motors1.rotate(motor2, 70, CCW);//run motor2 at 60% speed in CCW direction (2)
  motors2.rotate(motor1, 70, CCW);//run motor1 at 60% speed in CCW direction (3)
  motors2.rotate(motor2, 70, CCW);//run motor2 at 60% speed in CW direction  (4)
}
void Right()  //右
{
  motors1.brake(1);  
  motors1.brake(2);  
  motors2.brake(1);  
  motors2.brake(2);  
  //motors1.rotate(motor1, 70, CW);//run motor1 at 60% speed in CW direction (1)
  motors1.rotate(motor2, 70, CCW);//run motor2 at 60% speed in CCW direction (2)
  motors2.rotate(motor1, 70, CCW);//run motor1 at 60% speed in CCW direction (3)
  //motors2.rotate(motor2, 70, CW);//run motor2 at 60% speed in CW direction  (4)
}

void Ytrun() // 逆右旋轉
{
  motors1.brake(1);  
  motors1.brake(2);  
  motors2.brake(1);  
  motors2.brake(2);  

  motors1.rotate(motor1, 70, CCW);//run motor1 at 60% speed in CW direction (1)
  motors1.rotate(motor2, 70, CW);//run motor2 at 60% speed in CCW direction (2)
  motors2.rotate(motor1, 70, CCW);//run motor1 at 60% speed in CCW direction (3)
  motors2.rotate(motor2, 70, CW);//run motor2 at 60% speed in CW direction  (4)
}

void Xtrun() // 逆左旋轉
{
  motors1.brake(1);  
  motors1.brake(2);  
  motors2.brake(1);  
  motors2.brake(2);  

  motors1.rotate(motor1, 70, CW);//run motor1 at 60% speed in CW direction (1)
  motors1.rotate(motor2, 70, CCW);//run motor2 at 60% speed in CCW direction (2)
  motors2.rotate(motor1, 70, CW);//run motor1 at 60% speed in CCW direction (3)
  motors2.rotate(motor2, 70, CCW);//run motor2 at 60% speed in CW direction  (4)
}

void Stop() //電機停止
{
  motors1.brake(1);
  motors1.brake(2);  
  motors2.brake(1);
  motors2.brake(2);  
}


YouTubeDemo: