วัดอุณหภูมิและความชื้นด้วย C# ตอน 1

ไปหน้าแรก | สารบัญ | Laploy.comระเบียนบทความ | บทความจากลาภลอย

 

วัดอุณหภูมิและความชื้นด้วย C# ตอน 1

เขียนโปรแกรมภาษา C# ใน .NET Framework นิยามคลาสวัดอุณหภูมิและความชื้นที่นำไปใช้ได้ทั้งใน WinForm และ WebForm

 

งานที่ผู้เขียนกำลังทำอยู่ตอนนี้คือ ระบบเก็บข้อมูลเพื่อวิเคราะห์สาเหตุความขัดข้องของอุปกรณ์อิเลคทรอนิคส์ ข้อมูลที่เก็บมีหลายอย่างหลายรายการ ข้อมูลสำคัญอย่างหนึ่งที่อาจเป็นสาเหตุของความขัดข้องก็คือความร้อนและความชื้น ปัญหาคืออุปกรณ์อิเลคทรอนิคส์ดังกล่าวถูกติดตั้งไว้กลางแจ้ง ในถิ่นทุรกันดาร เช่นตามทุ่งนาป่าเขา ยากแก่การเข้าไปตรวจสอบ

ผู้เขียนแก้ปัญหาโดยจัดทำระบบตรวจวัดอุณหภูมิและความชื้น แล้วส่งข้อมูลเหล่านี้เข้าสู่เครือข่ายอินเตอร์เน็ต ทำให้เฝ้าติดตามสถานะจากทางไกลได้ โปรแกรมส่วน client เป็น thin client คือใช้ web browser เป็นตัวเปิด web application มีโปรแกรมทั้งส่วน client และส่วน server

โปรแกรมส่วน server ใช้เทคโนโลยี ASP.NET และภาษา C# โปรแกรมส่วน client ใช้ภาษา JavaScript ทำงานประสานกับโปรแกรมภาษา C# ในฝั่ง server โดยใช้กลไก Ajax ทำให้แสดงข้อมูลได้อย่างมีพลวัต ตัวเลขและแผนภูมิถูก update อัตโนมัติทุกนาทีโดยไม่ต้องโหลดหน้าเว็บ

รูป 001 โปรแกรมส่วน client ใช้ภาษา JavaScript ทำงานประสานกับโปรแกรมภาษา C# ในฝั่ง server โดยใช้กลไก Ajax ทำให้แสดงข้อมูลได้อย่างมีพลวัต

เรื่องการทำ remote และ Ajax ผู้เขียนจะเล่าให้ฟังวันหลัง สำหรับเรื่องที่จะเล่าสูกันฟังวันนี้คือส่วนฮาร์ดแวร์และซอฟท์แวร์ที่ใช้เพื่อการตรวจวัดอุณหภูมิและความชื้น เป็นเรื่องของการเปลี่ยนข้อมูลทางกายภาพ ให้เป็นข้อมูลดิจิตอล แล้วเก็บบันทึกไว้เพื่อใช้ในการวิเคราะห์ หากท่านต้องทำโครงงานที่มีปัญหาลักษณะเดียวกัน อาจนำเทคนิคเหล่านี้ไปประยุกต์ใช้ หรืออาจเพียงแค่อ่านเล่นสนุกๆ ก็ได้

บทความนี้ท่านจะได้เรียนรู้เทคนิคการเขียนโปรแกรมในแง่มุมต่างๆ ดังนี้
• การเขียนโปรแกรมภาษา C# เพื่อรับ-ส่งข้อมูลผ่านพอร์ทอนุกรม
• การใช้งานไลบรารีส่วนพอร์ทอนุกรมของ .NET Framework
• วิธีนิยามคลาสหุ้มห่อบอร์ดอ่านอุณหภูมิและความชื้น
• การเชื่อมต่อคอมพิวเตอร์กับบอร์ดอ่านอุณหภูมิและความชื้นผ่าน RS-485
• ตัวอย่างการประยุกต์ใช้งาน Delegate และ event เพื่อการทำ call back ระหว่าง object
• แสดงตัวอย่างการใช้งาน anonymous method
• ตัวอย่างการประยุกต์ใช้งาน Delegate และ Invoke เพื่อการทำ call back ระหว่าง thread

ผู้เขียนส่งบทความนี้ให้ท่านบก. คือคุณสุวัจชัยพิจารณา บก. แจ้งว่า เนื้อหายาวเกินกว่าจะลงพิมพ์จนจบในฉบับเดียวได้ ให้ไปแยกมาเป็นสองตอน ที่ท่านกำลังอ่านอยู่นี้คือตอนแรก ผู้เขียนจะพูดถึง อุปกรณ์ คือส่วนฮาร์ดแวร์ที่เราจำเป็นต้องใช้ในโครงงานนี้ เป็นฮาร์ดแวร์ที่หาซื้อสำเร็จรูปได้ ไม่จำเป็นต้องสร้างขึ้นเอง วิธีนำอุปกรณ์มาต่อเข้าด้วยกันเป็นระบบ วิธีเขียนคำสั่งควบคุมอุปกรณ์ และวิธีที่อุปกรณ์กับคอมพิวเตอร์สื่อสารกัน จากนั้นจะพูดถึงวิธีนิยามคลาสในภาษา C# เพื่อห่อหุ้มอุปกรณ์และพอร์ทอนุกรมไว้เป็นก้อนเดียวกัน

อุปกรณ์ที่ต้องใช้

ระบบนี้มีอุปกรณ์หลักๆ สี่ชิ้น คือเครื่องคอมพิวเตอร์ บอร์ดอ่านอุณหภูมิและความชื้น (ต่อไปจะเรียกย่อว่า AP170) เครื่องแปลงสัญญาณแบบ RS-232 ไปเป็น RS-485 และหัววัดอุณหภูมิและความชื้น ดังแสดงในแผนภูมินี้

รูป 002 แผนภูมิแสดงอุปกรณ์และการเชื่อมต่อสำหรับระบบอ่านอุณหภูมิและความชื้นกับคอมพิวเตอร์


รูป 003 ตัวแปลง RS-232/RS485

 
รูป 004 บอร์ดอ่านอุณหภูมิและความชื้น

 
รูป 005 หัววัดอุณหภูมิและความชื้น

รูป 006 เมื่อนำทั้งหมดมาต่อเชื่อมเข้าด้วยกัน

อุปกรณ์ทั้งหมดผลิตขึ้นในประเทศไทย ผู้เขียนซื้อมาจากพระโขนง หัววัดเป็นแบบวัดอุณหภูมิและความชื้นได้ในตัวเดียวกัน วัดอุณหภูมิความละเอียดขั้นละ .1 องศา ให้ค่าออกมาเป็นตัวเลของศาเซลเซียส ส่วนการวัดความชื้น วัดได้ที่ความละเอียดขั้นละ 1% ให้ตัวเลขออกมาสัดส่วนร้อยละของความชื้นสัมพัทธ์

บอร์ด AP170 อันที่จริงแล้วก็คือคอมพิวเตอร์ตัวหนึ่ง เราสามารถนำ AP170 หลายๆ บอร์ดมาต่อเชื่อมโยงเป็นเครือข่ายเหมือนระบบ LAN ได้ วิธีเชื่อมโยงใช้มาตรฐานแบบ RS-485 เมื่อเราจะนำมาต่อกับพอร์ทอนุกรมซึ่งเป็นมาตรฐาน RS-232 จึงต้องแปลงสัญญาณเสียก่อน โดยใช้ตัวแปลงสัญญาณอย่างที่เห็นในรูป

เมื่อนำอุปกรณ์ทั้งหมดมาต่อเชื่อมเข้าด้วยกัน และเสียบเข้ากับพอร์ทอนุกรมของคอมพิวเตอร์ ก็ถือว่าการติดตั้งส่วนฮาร์ดแวร์สำเร็จเรียบร้อย ง่ายๆ เพียงเท่านี้ งานขั้นต่อไปคือการเขียนโปรแกรมเพื่อติดต่อสื่อสารกับ AP170

กล่องดำ

ปรกติคนจะนำ AP170 ไปใช้กับไมโครคอนโทรลเลอร์ แล้วเขียนโปรแกรมควบคุมด้วยภาษา แอสแซมบลี หรือภาษา ซี แต่งานนี้เราจะนำมันมาใช้กับพีซี โดยพัฒนาเป็นโปรแกรมประยุกต์ใช้งานใน Windows ด้วยภาษา C# และ .NET Framework โปรแกรมนี้จึงทำงานได้ใน Windows 98/ME/XP และ Vista

ผู้เขียนจะใช้หลักการ encapsulation และ information hiding เพื่อนิยามคลาสห่อหุ้มพอร์ทสื่อสารและบอร์ด AP170 เข้าเป็นก้อนเดียวกัน เพื่อให้การนำไปใช้งานทำได้ง่าย ท่านสามารถนำคลาสนี้ไปสร้างเป็น object ที่ทำงานภายในโครงงานต่างๆ ได้ โดยไม่ต้องพะวงกับรายละเอียดปลีกย่อยทางฮาร์ดแวร์ เหมือนเราได้กล่องดำที่ไม่จำเป็นต้องรู้รายละเอียดของการทำงานภายในก็สามารถนำไปใช้งานได้

การรับ-ส่งข้อมูลกับบอร์ดวัดอุณหภูมิ

AP170 มีฟังก์ชันไม่ซับซ้อน โปรโตคอลในการรับ-ส่งข้อมูลจึงเรียบง่าย การส่งคำสั่งจากพีซีไปยังบอร์ดใช้วิธีส่งเป็นตัวอักษร (เป็นสายอักขระหรือ string) เหมือนคำสั่งควบคุมโมเด็ม ยกตัวอย่างเช่น คำสั่งอ่านอุณหภูมิและความชื้นคือ “:1<cr>” เราเพียงแค่ส่งตัวอักษร : และ 1 และรหัสปุ่ม Enter ซึ่งในภาษา C# จะใช้ “\r”

เมื่อส่งคำสั่งไปแล้ว โปรแกรมของเราจะต้องรอรับคำตอบ เพราะ AP170 จะส่งข้อมูลกลับมาภายในครึ่งวินาที โดยเป็นข้อมูลตัวอักษรที่มีรูปแบบแน่นอนดังนี้ Txxx.x Hxxx.x<cr> ยกตัวอย่างเช่นหากอุณหภูมิเป็นสามสิบหาจุดห้าความชื้นร้อยละห้าสิบจุดสอง ข้อมูลที่ AP170 ส่งมาจะเป็น T036.5 H50.2<cr>

คลาสหุ้มห่อบอร์ดวัดอุณหภูมิและความชื้น

คลาสที่จะนิยามนี้ชื่อ TempReader ชื่อคลาสนี้มากจากคำสองคำ คือ temperature และ Reader ถ้าท่านไม่ชอบจะเปลี่ยนชื่อเป็นอย่างอื่นก็ได้ คลาสนี้เป็นคลาสที่เบ็ดเสร็จในตัว คือไม่ได้อ้างถึงตัวแปรหรือตัวคงค่า หรือ method ของคลาสอื่นๆ ใน project เดียวกันนี้

คลาส TempReader เป็นคลาสที่ถูกออกแบบมาให้นำไปใช้สร้าง object ไม่ใช่คลาสสำหรับนำไปทำเป็น base class แต่ถ้าท่านต้องการจะนำไปใช้เป็น base class เพื่อพัฒนาต่อยอด ก็สามารถทำได้เช่นกัน

รูป 007คลาสไดอะแกรมของ TempReader จะเห็นว่าคลาสนี้มีสมาชิกหลายแบบ คือมีฟิลด์สมาชิกสามตัว property สมาชิกหนึ่งตัว method สมาชิกสี่ตัว และสมาชิกแบบ event หนึ่งตัว โปรดสังเกตว่า delegate มีภาวะเป็น nested type

โค้ดของคลาส TempReader ค่อนข้างสั้น เพราะเจตนาเขียนให้อ่านง่าย ไม่ซับซ้อน ซอร์สโค้ด เป็นดังที่เห็นข้างล่าง หากท่านไม่ต้องการป้อนพิมพ์ก็สามารถดาวน์โหลดมาทดสอบการทำงานได้จากเว็บไซต์ของผู้เขียน ( http://www.laploy.com/download ไฟล์ชื่อ TempReader.cs)

using System;
using System.Collections.Generic;
using System.Text;
using System.IO.Ports;
using System.Threading;

namespace Climsel
{
    class TemppReader
    {
        public delegate void TemppReaderEventHandler(string tempp);
        public event TemppReaderEventHandler TemppReaderFire;
        private SerialPort myTemppComPort = new SerialPort();
        private int temppComPort;
        private string rxString;

        public int TemppComPort
        {
            get { return temppComPort; }
            set { temppComPort = value; }
        }

        public void GetTempp(string code)
        {
            if (!myTemppComPort.IsOpen) myTemppComPort.Open();
            myTemppComPort.Write(code);
        }
        public void CloseCom()
        {
            myTemppComPort.Close();
        }
        public void CreateTemppConnection()
        {
            myTemppComPort.Close();
            myTemppComPort.BaudRate = 9600;
            myTemppComPort.PortName = "COM" + temppComPort.ToString();
            myTemppComPort.Parity = Parity.None;
            myTemppComPort.DataBits = 8;
            myTemppComPort.StopBits = StopBits.One;
            myTemppComPort.RtsEnable = true;

            myTemppComPort.DataReceived += new SerialDataReceivedEventHandler(myTemppComPort_DataReceived);
        }
        private void myTemppComPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
        {
            rxString += myTemppComPort.ReadExisting();
            if (rxString[rxString.Length-1] == '\r' )
            {
                TemppReaderFire(rxString);
                rxString = "";
            }
        }
    }
}

โค้ดส่วนบนสุดคือกลุ่มคำสั่ง using ทำหน้าที่จับรวม namespace ห้า namespace เข้ามาคอมไพล์ร่วมด้วย คำสั่ง using เป็นคำสั่งในภาษา C# ทำหน้าที่คล้ายๆ คำสั่ง include ในภาษาซี หรือภาษา PHP แตกต่างกันตรงที่คำสั่ง include ทำหน้าที่ผนวกฟังค์ชันจากไฟล์ที่เป็นซอร์สโค้ด ขณะที่คำสั่ง using ทำหน้าที่ผนวก type ภายใน namespace ที่อ้างถึง โดย type เหล่านั้นอยู่ในสภาพไฟล์แอสเซมบลี (คือไฟล์นามสกุล .dll ของ .NET Framework) ที่คอมไพล์ไว้แล้ว

สำหรับผู้ที่ใช้ภาษาจาวาและ VB.NET โปรดเข้าใจว่าคำสั่ง using ก็คือคำสั่ง import นั่นเอง คำสั่ง using ทั้งหมดถูกแทรกเข้ามาโดยอัตโนมัติโดยโปรแกรม Visual Studio .NET ยกเว้น using System.IO.Ports;
ที่ผู้เขียนใส่เข้าไปเอง เพราะ namespace นี้เกี่ยวข้องโดยตรงกับการใช้งานพอร์ทอนุกรม

ถัดมาคำสั่ง namespace Laploy.TempHumReader ทำหน้าที่ประกาศ namespace ที่ผู้เขียนกำหนดขึ้นสำหรับโปรแกรมนี้ .NET Framework สนับสนุนให้เราตั้ง namespace ขึ้นเองเพื่อป้องกัน error ที่เกิดจากการตั้งชื่อ type ซ้ำกัน ซึ่งมีโอกาสเกิดขึ้นได้หากทำงานเป็นทีม หรือมีการนำ type ที่เคยนิยามไว้กลับมาใช้ใหม่

ต่อมาคำสั่ง class TemppReader ทำหน้าที่บอกจุดเริ่มต้นของนิยามคลาส โปรดสังเกตว่าผู้เขียนใช้วิธีตั้งชื่อคลาสแบบ ปาสคาล (pascal case) ซึ่งเป็นธรรมเนียมปฏิบัติของการตั้งชื่อคลาสในภาษา C#

การประกาศ Delegate และ Event

สองบรรทัดถัดมาคือการประกาศ delegate และ event โดยกำหนดให้ทั้งสองตัวมี access modifier เป็นแบบ public ทั้งคู่ เพราะเราต้องการให้คลาสที่จะใช้งาน object นี้ (คือโปรแกรมที่เรียกใช้งานคลาสนี้ ต่อไปจะเรียกว่า client class) สามารถเข้าถึงมันได้

ในคลาสนี้ผู้เขียนจะสาทิตการใช้ delegate เพื่อทำงานแบบ call back เพื่อให้ client class รับรู้การทำงานเมื่อพอร์ทอนุกรมได้รับข้อมูลจาก AP170 สำหรับนักเขียนภาษาซี และ C++ ขอให้เข้าใจว่านี่เป็นการทำงานเลียนแบบ function pointer แตกต่างจาก function pointer ตรงที่เป็นกลไกซึ่งมีคุณสมบัติ type safety โดยสมบูรณ์ (ไม่ต้องใช้ pointer)

คำสั่ง public delegate void TemppReaderEventHandler(string tempp); ทำหน้าที่ประกาศ delegate ชื่อ TemppReaderEventHandler โปรดสังเกตว่ารูปแบบการประกาศ delegate คล้ายการนิยามส่วนหัวของ method เพราะมีทั้ง return type, ชื่อ method และพารามิเตอร์ เราเรียกการประกาศเช่นนี้ว่า anonymous method เพราะเป็น method ที่เราไม่ได้นิยามไว้จริง รูปแบบเช่นนี้ช่วยอำนวยความสะดวกในการใช้งาน delegate (anonymous method เริ่มมีใช้ใน .NET 2.0)

บรรทัดถัดมา public event TemppReaderEventHandler TemppReaderFire; ทำหน้าที่ประกาศ event ที่เราต้องการให้ทำงานเมื่อพอร์ทอนุกรมได้รับข้อมูลจาก AP170 เมื่อเกิด event ขึ้น delegate จะทำงานโดยเรียก anonymous method ให้ทำงาน ถ้าอ่านถึงตรงนี้แล้วยังไม่เข้าใจก็ไม่เป็นไร เพระอีกสักครู่ เมื่อท่านเห็นส่วนใช้งาน anonymous method แล้วจะเข้าใจดีขึ้น

โปรดสังเกตว่าการประกาศ delegate เป็นการนิยาม type ใหม่ ในกรณีนี้เป็น type ที่ซ้อนอยู่ภายในคลาส TemppReader การประกาศเช่นนี้เรียกว่า nested type (ดูในคลาสไดอะแกรม)

การประกาศฟิลด์สมาชิก

ฟิลด์สมาชิกทำหน้าที่เก็บข้อมูลใน object ทำให้ object มีความเบ็ดเสร็จในตัวตามหลักการ encapsulation ในกรณีที่ไม่ใช่ static class (ตามในตัวอย่างนี้) object แต่ละตัวจะมีฟิลด์สมาชิกเป็นของตัวเอง ยกตัวอย่างเช่น หากเราสร้าง object สาม object จะเกิดฟิลด์สมาชิกสามชุด สำหรับแต่ละ object ไม่ปะปนกัน

คลาส TempReader มีการประกาศสมาชิกแบบฟิลด์สามตัว มีภาวะเป็น private ทั้งสามตัว สมาชิกแบบฟิลด์ที่เป็น private จะมีขอบเขตอยู่ภายใน object ที่จะถูกสร้างจากคลาสนี้เท่านั้น ไม่สามารถถูกอ่าน-เขียนจาก client class ได้ จึงมักถูกเรียกว่า instance filed หรือ object filed

ฟิลด์ชื่อ myTemppComPort เป็นตัวแปรแบบ reference type ทำหน้าที่ใช้อ้างอิง object ซึ่งมี type เป็น SerialPort อันเป็น type ที่เกิดจากคลาสชื่อเดียวกันใน namespace ชื่อ System.IO.Ports ที่เราจับรวมไว้ด้วยคำสั่ง using ดังที่อธิบายไปตอนต้น

เมื่อสร้าง object ซึ่งทำหน้าที่ควบคุมพอร์ทอนุกรมแล้ว การใช้งานพอร์ทอนุกรมต่อไปจะทำได้ง่ายมาก เพราะเราเพียงกำหนด property ต่างๆ ให้แก่ฟิลด์ myTemppComPort และเรียก method ต่างๆ ของมัน เพียงเท่านี้เราก็จะสามารถรับ-ส่งข้อมูลกับพอร์ทอนุกรมได้แล้ว ท่านจะได้เห็นตัวอย่างวิธีเรียกใช้ object นี้ในโค้ดส่วนทดสอบการใช้งานตอนท้ายของบทความ (คลาส test)

ถัดมาคือฟิลด์ชื่อ temppComPort ทำหน้าที่เก็บค่าหมายเลขพอร์ท ในเครื่องพีซีโดยทั่วไปจะมีพอร์ทอนุกรมสองพอร์ท ชื่อ COM1 และ COM2 เดิมที่เรามักใช้พอร์ท COM1 เพื่อต่อกับเมาส์ แต่ปัจจุบันเมาส์ส่วนใหญ่เป็นอินเตอร์เฟสแบบ USB หมดแล้ว พอร์ท COM1 และ COM2 จึงไม่ได้ใช้และถูกปล่อยทิ้งว่างไว้

ฟิลด์ตัวสุดท้ายชื่อ rxString ทำหน้าที่รับข้อมูลอุณหภูมิและความชื้นจาก AP170

โปรดติดตามตอนจบ

นิยามคลาส TempReader ยังไม่จบ แต่หน้ากระดาษหมดเสียก่อน ที่ท่านอ่านไปแล้วในตอนนี้เป็นเรื่องอุปกรณ์ที่เราต้องใช้เพื่อรับข้อมูลอุณหภูมิและความชื้น วิธีนำอุปกรณ์เหล่านี้มาต่อเชื่อมกันเป็นระบบ วิธีเขียนคำสั่งควบคุมอุปกรณ์ วิธีที่อุปกรณ์และคอมพิวเตอร์สื่อสารกัน และวิธีนิยามคลาส TempReader เพื่อห่อหุ้มอุปกรณ์และพอร์ทอนุกรมไว้เป็นก้อนเดียวกัน

ในตอนหน้าซึ่งเป็นตอนจบ เราจะมาดูนิยามส่วนที่เหลือของ คลาส TempReader ได้แก่ส่วนประกาศสมาชิกแบบ property ส่วนนิยาม method สมาชิก และดูตัวอย่างวิธีสร้าง object จากคลาส TempReader ซึ่งจะช่วยให้ท่านจะสามารถนำ object นี้ไปใช้งานในโครงงานต่างๆ ของท่านได้ทันที

Post a comment or leave a trackback: Trackback URL.

ความเห็น

  • Narongsak  On สิงหาคม 29, 2007 at 1:59 pm

     thank you very much

  • • Up2ku  On กันยายน 11, 2007 at 8:04 pm

    อยากทราบว่า ตอนที่ 13 หายไปไหนครับ ขอบคุณสำหรับบทความดีๆ แบบนี้นะครับ 

  • ธนัติชัย  On กันยายน 14, 2007 at 1:34 pm

    สุดยอดเลยครับ หาอ่านแบบละเอียดอย่างนี้ไม่ค่อยได้ครับ อยากให้นำเสนองานอย่างนี้มากๆนะครับ 
    ถ้าไม่ลำบากเกินไป  ถึงผมจะไม่ได้ใช้ตัวนี้ แต่ก็จะนำเสนอให้ผู้สนใจต่อไปเพราะมีผู้อยากศึกษาจาวาอยู่มาก
    ที่ยังขาดผู้แนะนำดีๆอย่างนี้ครับ   ขอบพระคุณมากครับ
     

  • ausawin  On มิถุนายน 20, 2008 at 1:20 pm

    ขอบคุณครับ  

  • ชนะ  On พฤศจิกายน 29, 2008 at 9:14 pm

    เยี่ยมมากครับ…ขอเป็นกำลังใจให้อีกคน
     

  • ann  On ธันวาคม 30, 2008 at 10:06 am

    เก่งจังเลย แล้วจะติดตามต่อนะคะ

  • IPOMZ  On มีนาคม 8, 2010 at 1:10 pm

    จะพยายามอ่านจนครบครับ ขอบคุณมากๆ

ส่งความเห็นที่ IPOMZ ยกเลิกการตอบ