軟硬結合——酷我音樂盒的逆天玩法
1、靈感來源:
本文引用地址:http://www.104case.com/article/201701/343047.htmLZ是純宅男,一天從早上8:00起一直要呆在電腦旁到晚上12:00左右吧~平時也沒人來閑聊幾句,刷空間暑假也沒啥動態,聽音樂吧...~有些確實不好聽,于是就不得不打斷手頭的工作去點擊下一曲或是找個好聽的歌來聽...但是,[移動手鎖定鼠標-->移動鼠標關閉當前頁面選擇音樂軟件頁面-->選擇合適的音樂-->恢復原來的界面] 這一過程也會煩人不少,如果說軟件的設計要在用戶體驗上做足功夫,感覺這一點是軟件設計人員很難管住的方面,畢竟操作系統也就這樣安排的嘛(當然,有些機智的開發人員加了幾個熱鍵,確實方便了不少!)。于是我想能不能設計一個軟件能盡量少打斷我們正常的工作簡單操作去觸發下一曲~
2、需求分析:
下圖左一是傳統的操作模式,在這里要人的眼、手并用而且還必須等待記憶,可能我們平時感覺不到,但是這個過程卻是比較浪費時間且分散注意力!
下圖右一是想改為的操作模式,在這里我們只需要外部觸發(如:搖一下頭或者微笑一下,甚至只要想一下就可以啦),讓切歌任務在后臺進行,這樣就能不打斷前臺工作(這里的前臺和后臺只是當前工作窗口和非當前窗口,和專業的有差別!)

3、解決方案
根據上面分析我們需要這些條件:
外部硬件設備,可以接收特殊信號并傳給PC
PC上的軟件能夠讀取硬件傳來的信號并分析信息,做出切歌任務
結合我現有設備,做出如下方案:
硬件采用STC89C52單片機最小系統占用P1.0和P1.1兩個端口和超聲波測距模塊HC-SR04,通過根據遮擋物在超聲波測距范圍內停留的時間來發出觸發“下一曲”,“暫停”,“上一曲”事件的信號。

軟件采用C#從串口讀取單片機發送的觸發事件信號消息,然后調用WinAPI對音樂盒窗口進行識別計算以及發送點擊消息,來控制切換歌曲。

PS:這里根據手在超聲波范圍內停留的時間來分出3種信號:
短暫停留在區域內-->下一曲信號
稍長停留在區域內-->上一曲信號
超長停留在區域內-->暫停信號
4、作品提前展示及相關介紹:
哈哈,秒懂啦吧!圖中那個像望遠鏡的東西就是超聲波測距模塊,它的前面輻射狀的空間(我設置為40cm)就是有效范圍,那個黑色的像蜈蚣的東西就是單片機(就相當于電腦里的CPU),插在USB里面的不用介紹就是USB轉TTL啦!主要就是負責采集傳感器信號然后將距離信息通過USB發送給電腦。最終達到達到的效果是:你的手只要在區域內揮一下,就能切歌啦!手停長一點時間就能暫停啦!這個玩法沒試過吧,哈哈!

下面這個圖就是基于C#的電腦端軟件,其主要功能就是連接串口進行數據接收、數據處理、以及查找音樂盒的窗口、計算該點擊的按鈕位置、發出點擊消息、在不同窗口中切換(因為要實現少打擾當前活動的目的)。這里為了測試方便所以加了3個功能按鈕:上一曲、暫停、下一曲,通過點擊這些按鈕能實現控制酷我音樂盒歌曲的切換,然后右邊加了個下拉框用來枚舉當前可用串口,LINK按鈕就是連接該串口的觸發按鈕。下面一個文本顯示區是用來顯示串口傳過來的距離的數據的(便于調試哈~)

5、C#軟件部分技術詳解
該部分要用到很多Windows API,主要功能就是查找窗口句柄、控制窗口顯示、計算窗口位置、聚焦窗口、窗口切換....算是把窗口有關的常用API都用上啦~此外,還用到了鼠標光標位置設定、鼠標點擊消息發送最終達到模擬鼠標點擊事件。當然,串口通信絕對不能少滴!
5.1、C#串口通信
5.1.1、獲取當前可用串口列表
1 //Get all port list for selection
2 //獲得所有的端口列表,并顯示在列表內
3 PortList.Items.Clear();
4 string[] Ports = SerialPort.GetPortNames();
5
6 for (int i = 0; i < Ports.Length; i++)
7 {
8 string s = Ports[i].ToUpper();
9 Regex reg = new Regex("[^COM\d]", RegexOptions.IgnoreCase " RegexOptions.Multiline);//正則表達式
10 s = reg.Replace(s, "");
11
12 PortList.Items.Add(s);
13 }
14 if (Ports.Length >1) PortList.SelectedIndex = 1;
調用串口要引用 using System.IO.Ports;
第9行的正則表達式要引用 using System.Text.RegularExpressions;
第3行的PortList是那個下拉框;
整體的功能就是通過第4行的函數獲取所有可用串口,然后加入下拉框顯示,如果有可用的就把第一個選中;
5.1.2、串口連接按鈕事件
1 private void btn_link_Click(object sender, EventArgs e)
2 {
3 if (!Connection.IsOpen)
4 {
5 //Start
6 Status = "正在連接...";
7 Connection = new SerialPort();
8 btn_link.Enabled = false;
9 Connection.PortName = PortList.SelectedItem.ToString();
10 Connection.Open();
11 Connection.ReadTimeout = 10000;
12 Connection.DataReceived += new SerialDataReceivedEventHandler(PortDataReceived);
13 Status = "連接成功";
14 }
15 }
PS:整體很好理解就是把下拉框選中的串口號連接上,這里第12行比較重要,它調用SerialDataReceivedEventHandler(Func Name)來定義一個數據接收函數的句柄,這里PortDataReceived你可以隨便寫,但是接下來你要寫對應的實現函數:(這里說句柄比較難理解,你就理解成一個函數,綁定串口的函數,一旦串口有數據發動過來就執行這個函數....)
1 //接收串口數據
2 private int num=0; //障礙物進入范圍的時間
3 private bool enter=false; //是否有障礙物進入
4 private int signal=0; //對每次進入范圍的時間分段形成控制信號
5 private void PortDataReceived(object o, SerialDataReceivedEventArgs e)
6 {
7 int length = 1;
8 byte[] data = new byte[length];
9 Connection.Read(data, 0, length);
10 for (int i = 0; i < length; i++)
11 {
12 ReceivedData = string.Format("{0}",data[i]);
13 }
14
15 //數據濾波轉換為控制信號
16 if (data[0] != 136 && !enter){ //當有障礙物進入時,傳過來數據不是136并且是第一個
17 enter = true;
18 num = 1;
19 }else if (data[0] == 136 && enter){ //當障礙物離開時,傳過來數據變為136且是第一個
20 enter = false;
21 if (num > 1 && num < 6){
22 signal = 1;
23 }else if (num > 5 && num < 10){
24 signal = 2;
25 }else if (num > 9){
26 signal = 3;
27 }
28 num = 0;
29 }else if (data[0] != 136 && data[0] >= 0 && enter){
30 num++;
31 }
32 }
PS:這就是串口數據接收函數實現,先別看其他內容,因為里面涉及濾波算法和控制信號生成的算法,只要看第7~13行的代碼核心部分就是第9行從緩沖區讀取串口數據放到data[]數組中,這樣串口數據就放在data[]中啦!怎么處理是下面的事啦~
5.1.3、重量級功能函數:
1 ///
2 /// 模擬鼠標點擊函數
3 ///
4 /// 0是上一曲,1是暫停,2是下一曲
5 public void func(int n_control_type)
6 {
7 //bool isVisabled; //窗口原來狀態,隱藏還是顯示
8 IntPtr hCurWin = GetForegroundWindow(); //獲取當前激活窗口
9
10 IntPtr hMusic = FindWindow("kwmusicmaindlg", null); //找到窗口句柄
11 if (hMusic == null)
12 {
13 return;
14 }
15 Point pt; //獲取鼠標當前位置
16 GetCursorPos(out pt);
17 ShowWindow(hMusic,SW_SHOWNORMAL); //如果是隱藏的就讓他正常顯示出來
18 SetForegroundWindow(hMusic); //將音樂盒窗口放在最上層
19
20 RECT rect = new RECT(); //獲取窗口矩形
21 GetWindowRect(hMusic, ref rect);
22 int width = rect.Right - rect.Left;
評論