ควบคุมอุปกรณ์ภายนอก

ใน Android 11 ขึ้นไป ฟีเจอร์ระบบควบคุมอุปกรณ์เพื่อการเข้าถึงด่วนช่วยให้ผู้ใช้ดูและควบคุมอุปกรณ์ภายนอก เช่น ไฟ ตัวควบคุมอุณหภูมิ และกล้องได้อย่างรวดเร็วจากการเข้าถึงของผู้ใช้ภายใน 3 การโต้ตอบจาก Launcher เริ่มต้น OEM ของอุปกรณ์จะเป็นผู้เลือก Launcher ที่ใช้ ตัวรวบรวมอุปกรณ์ เช่น Google Home และแอปของผู้ให้บริการบุคคลที่สามสามารถแสดงอุปกรณ์ในพื้นที่นี้ได้ หน้านี้จะแสดงวิธีแสดงการควบคุมอุปกรณ์ในพื้นที่นี้และลิงก์การควบคุมเหล่านั้นกับแอปควบคุม

รูปที่ 1 พื้นที่ควบคุมอุปกรณ์ใน UI ของ Android

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

อินเทอร์เฟซผู้ใช้

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

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

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

รูปภาพแสดงวิดเจ็ตแผงอุณหภูมิ (เปิด)
รูปที่ 3 วิดเจ็ตแผงอุณหภูมิเปิด

สร้างบริการ

ส่วนนี้จะแสดงวิธีสร้าง ControlsProviderService บริการนี้จะบอก UI ของระบบ Android ว่าแอปของคุณมีการควบคุมอุปกรณ์ที่ต้องแสดงในส่วนการควบคุมอุปกรณ์ ของ UI ของ Android

API ของ ControlsProviderService สันนิษฐานว่าผู้ใช้มีความคุ้นเคยกับสตรีมแบบโต้ตอบตามที่ กำหนดไว้ในโปรเจ็กต์ Reactive Streams GitHub และนำไปใช้ในอินเทอร์เฟซ Java 9 Flow API สร้างขึ้นโดยอิงตามแนวคิดต่อไปนี้

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

ประกาศบริการ

แอปของคุณต้องประกาศบริการ เช่น MyCustomControlService ในไฟล์ Manifest ของแอป

บริการต้องมีตัวกรอง Intent สำหรับ ControlsProviderService ตัวกรองนี้ช่วยให้แอปพลิเคชันมีส่วนร่วมในการควบคุม UI ของระบบได้

นอกจากนี้ คุณยังต้องมี label ที่แสดงในการควบคุมใน UI ของระบบด้วย

ตัวอย่างต่อไปนี้แสดงวิธีประกาศบริการ

<service
    android:name="MyCustomControlService"
    android:label="My Custom Controls"
    android:permission="android.permission.BIND_CONTROLS"
    android:exported="true"
    >
    <intent-filter>
      <action android:name="android.service.controls.ControlsProviderService" />
    </intent-filter>
</service>

จากนั้นสร้างไฟล์ Kotlin ใหม่ชื่อ MyCustomControlService.kt และทำให้ไฟล์นี้ขยาย ControlsProviderService()

Kotlin

    class MyCustomControlService : ControlsProviderService() {
        ...
    }
    

Java

    public class MyCustomJavaControlService extends ControlsProviderService {
        ...
    }
    

เลือกประเภทการควบคุมที่ถูกต้อง

API มีเมธอด Builder สำหรับสร้างการควบคุม หากต้องการป้อนข้อมูลลงใน Builder ให้กำหนดอุปกรณ์ที่ต้องการควบคุมและวิธีที่ผู้ใช้โต้ตอบกับอุปกรณ์ ทำตามขั้นตอนต่อไปนี้

  1. เลือกประเภทอุปกรณ์ที่การควบคุมแสดง คลาส DeviceTypes เป็นการ แจกแจงอุปกรณ์ที่รองรับทั้งหมด ระบบจะใช้ประเภทนี้เพื่อกำหนดไอคอนและสีของอุปกรณ์ใน UI
  2. กำหนดชื่อที่ผู้ใช้มองเห็น ตำแหน่งอุปกรณ์ เช่น ห้องครัว และองค์ประกอบข้อความอื่นๆ ของ UI ที่เชื่อมโยงกับการควบคุม
  3. เลือกเทมเพลตที่ดีที่สุดเพื่อรองรับการโต้ตอบของผู้ใช้ ระบบจะกำหนดการควบคุมจากแอปพลิเคชันControlTemplate เทมเพลตนี้จะแสดงสถานะการควบคุมต่อผู้ใช้โดยตรง รวมถึงวิธีการป้อนข้อมูลที่ใช้ได้ ซึ่งก็คือ ControlAction ตารางต่อไปนี้แสดงเทมเพลตบางส่วนที่ใช้ได้และการดำเนินการที่เทมเพลตเหล่านั้นรองรับ
เทมเพลต การดำเนินการ คำอธิบาย
ControlTemplate.getNoTemplateObject() None แอปพลิเคชันอาจใช้เทมเพลตนี้เพื่อสื่อสารข้อมูลเกี่ยวกับการควบคุม แต่ผู้ใช้จะโต้ตอบกับเทมเพลตไม่ได้
ToggleTemplate BooleanAction แสดงการควบคุมที่สามารถสลับระหว่างสถานะเปิดใช้และปิดใช้ ออบเจ็กต์ BooleanAction มีช่องที่จะเปลี่ยน เพื่อแสดงสถานะใหม่ที่ขอเมื่อผู้ใช้แตะการควบคุม
RangeTemplate FloatAction แสดงวิดเจ็ตแถบเลื่อนที่มีค่าต่ำสุด สูงสุด และค่าขั้นที่ระบุ เมื่อ ผู้ใช้โต้ตอบกับแถบเลื่อน ให้ส่งออบเจ็กต์ FloatAction ใหม่กลับไปยังแอปพลิเคชันพร้อมค่าที่อัปเดต
ToggleRangeTemplate BooleanAction, FloatAction เทมเพลตนี้เป็นการรวมกันของ ToggleTemplate และ RangeTemplate โดยรองรับเหตุการณ์การแตะ รวมถึงแถบเลื่อน, เช่น เพื่อควบคุมไฟที่ปรับความสว่างได้
TemperatureControlTemplate ModeAction, BooleanAction, FloatAction นอกจากจะห่อหุ้มการดำเนินการก่อนหน้าแล้ว เทมเพลตนี้ยังช่วยให้ผู้ใช้ตั้งค่าโหมด เช่น ความร้อน ความเย็น ความร้อน/ความเย็น ประหยัดพลังงาน หรือปิด
StatelessTemplate CommandAction ใช้เพื่อระบุการควบคุมที่มีความสามารถในการแตะ แต่ไม่สามารถกำหนดสถานะ ได้ เช่น รีโมตทีวี IR คุณสามารถใช้เทมเพลตนี้เพื่อกำหนดกิจวัตรหรือมาโคร ซึ่งเป็นการรวมการเปลี่ยนแปลงการควบคุมและการเปลี่ยนแปลงสถานะ

เมื่อมีข้อมูลนี้แล้ว คุณจะสร้างการควบคุมได้ดังนี้

  • ใช้คลาส Builder Control.StatelessBuilder เมื่อไม่ทราบสถานะของการควบคุม
  • ใช้คลาส Builder Control.StatefulBuilder เมื่อทราบสถานะของการควบคุม

ตัวอย่างเช่น หากต้องการควบคุมหลอดไฟอัจฉริยะและตัวควบคุมอุณหภูมิ ให้เพิ่มค่าคงที่ต่อไปนี้ลงใน MyCustomControlService

Kotlin

    private const val LIGHT_ID = 1234
    private const val LIGHT_TITLE = "My fancy light"
    private const val LIGHT_TYPE = DeviceTypes.TYPE_LIGHT
    private const val THERMOSTAT_ID = 5678
    private const val THERMOSTAT_TITLE = "My fancy thermostat"
    private const val THERMOSTAT_TYPE = DeviceTypes.TYPE_THERMOSTAT
 
    class MyCustomControlService : ControlsProviderService() {
      ...
    }
    

Java

    public class MyCustomJavaControlService extends ControlsProviderService {
 
    private final int LIGHT_ID = 1337;
    private final String LIGHT_TITLE = "My fancy light";
    private final int LIGHT_TYPE = DeviceTypes.TYPE_LIGHT;
    private final int THERMOSTAT_ID = 1338;
    private final String THERMOSTAT_TITLE = "My fancy thermostat";
    private final int THERMOSTAT_TYPE = DeviceTypes.TYPE_THERMOSTAT;
 
    ...
    }
    

สร้างผู้เผยแพร่สำหรับการควบคุม

หลังจากสร้างการควบคุมแล้ว การควบคุมนั้นต้องมีผู้เผยแพร่ ผู้เผยแพร่จะแจ้ง UI ของระบบให้ทราบถึงการมีอยู่ของการควบคุม คลาส ControlsProviderService มีเมธอดผู้เผยแพร่ 2 รายการที่คุณต้องลบล้างในโค้ดของแอปพลิเคชัน

  • createPublisherForAllAvailable(): สร้าง Publisher สำหรับการควบคุมทั้งหมดที่ใช้ได้ในแอปของคุณ ใช้ Control.StatelessBuilder() เพื่อสร้าง Control ออบเจ็กต์สำหรับผู้เผยแพร่โฆษณารายนี้
  • createPublisherFor(): สร้าง Publisher สำหรับรายการการควบคุมที่ระบุ ซึ่งระบุโดยตัวระบุสตริง ใช้ Control.StatefulBuilder เพื่อ สร้างออบเจ็กต์ Control เหล่านี้ เนื่องจากผู้เผยแพร่ต้องกำหนดสถานะให้แก่ การควบคุมแต่ละรายการ

สร้างผู้เผยแพร่

เมื่อแอปของคุณเผยแพร่การควบคุมไปยัง UI ของระบบเป็นครั้งแรก แอปจะไม่ทราบสถานะของการควบคุมแต่ละรายการ การรับสถานะอาจเป็นการดำเนินการที่ใช้เวลานานซึ่งเกี่ยวข้องกับการข้ามหลายครั้งในเครือข่ายของผู้ให้บริการอุปกรณ์ ใช้เมธอด createPublisherForAllAvailable() เพื่อโฆษณาการควบคุมที่ใช้ได้ในระบบ เมธอดนี้ใช้คลาส Builder Control.StatelessBuilder เนื่องจากไม่ทราบสถานะของการควบคุมแต่ละรายการ

เมื่อการควบคุมปรากฏใน UI ของ Android แล้ว ผู้ใช้จะเลือกการควบคุมที่ชื่นชอบได้

หากต้องการใช้ Kotlin Coroutines เพื่อสร้าง ControlsProviderService ให้เพิ่มทรัพยากร Dependency ใหม่ลงใน build.gradle

ดึงดูด

dependencies {
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-jdk9:1.6.4"
}

Kotlin

dependencies {
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-jdk9:1.6.4")
}

เมื่อซิงค์ไฟล์ Gradle แล้ว ให้เพิ่มข้อมูลโค้ดต่อไปนี้ลงใน Service เพื่อใช้ createPublisherForAllAvailable()

Kotlin

    class MyCustomControlService : ControlsProviderService() {
 
      override fun createPublisherForAllAvailable(): Flow.Publisher =
          flowPublish {
              send(createStatelessControl(LIGHT_ID, LIGHT_TITLE, LIGHT_TYPE))
              send(createStatelessControl(THERMOSTAT_ID, THERMOSTAT_TITLE, THERMOSTAT_TYPE))
          }
 
      private fun createStatelessControl(id: Int, title: String, type: Int): Control {
          val intent = Intent(this, MainActivity::class.java)
              .putExtra(EXTRA_MESSAGE, title)
              .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
          val action = PendingIntent.getActivity(
              this,
              id,
              intent,
              PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
          )
 
          return Control.StatelessBuilder(id.toString(), action)
              .setTitle(title)
              .setDeviceType(type)
              .build()
      }
 
          override fun createPublisherFor(controlIds: List): Flow.Publisher {
           TODO()
        }
 
        override fun performControlAction(
            controlId: String,
            action: ControlAction,
            consumer: Consumer
        ) {
            TODO()
        }
    }
    

Java

    public class MyCustomJavaControlService extends ControlsProviderService {
 
        private final int LIGHT_ID = 1337;
        private final String LIGHT_TITLE = "My fancy light";
        private final int LIGHT_TYPE = DeviceTypes.TYPE_LIGHT;
        private final int THERMOSTAT_ID = 1338;
        private final String THERMOSTAT_TITLE = "My fancy thermostat";
        private final int THERMOSTAT_TYPE = DeviceTypes.TYPE_THERMOSTAT;
 
        private boolean toggleState = false;
        private float rangeState = 18f;
        private final Map<String, ReplayProcessor> controlFlows = new HashMap<>();
 
        @NonNull
        @Override
        public Flow.Publisher createPublisherForAllAvailable() {
            List controls = new ArrayList<>();
            controls.add(createStatelessControl(LIGHT_ID, LIGHT_TITLE, LIGHT_TYPE));
            controls.add(createStatelessControl(THERMOSTAT_ID, THERMOSTAT_TITLE, THERMOSTAT_TYPE));
            return FlowAdapters.toFlowPublisher(Flowable.fromIterable(controls));
        }
 
        @NonNull
        @Override
        public Flow.Publisher createPublisherFor(@NonNull List controlIds) {
            ReplayProcessor updatePublisher = ReplayProcessor.create();
 
            controlIds.forEach(control -> {
                controlFlows.put(control, updatePublisher);
                updatePublisher.onNext(createLight());
                updatePublisher.onNext(createThermostat());
            });
 
            return FlowAdapters.toFlowPublisher(updatePublisher);
        }
    }
    

ปัดลงเมนูระบบแล้วค้นหาปุ่มการควบคุมอุปกรณ์ ที่แสดงในรูปที่ 4

รูปภาพแสดง UI ของระบบสำหรับการควบคุมอุปกรณ์
รูปที่ 4 การควบคุมอุปกรณ์ในเมนูระบบ

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

รูปภาพแสดงเมนูระบบที่มีการควบคุมไฟและตัวควบคุมอุณหภูมิ
รูปที่ 5 การควบคุมไฟและตัวควบคุมอุณหภูมิที่จะเพิ่ม

ตอนนี้ ให้ใช้เมธอด createPublisherFor() โดยเพิ่มข้อมูลต่อไปนี้ลงใน Service

Kotlin

    private val job = SupervisorJob()
    private val scope = CoroutineScope(Dispatchers.IO + job)
    private val controlFlows = mutableMapOf<String, MutableSharedFlow>()
 
    private var toggleState = false
    private var rangeState = 18f
 
    override fun createPublisherFor(controlIds: List): Flow.Publisher {
        val flow = MutableSharedFlow(replay = 2, extraBufferCapacity = 2)
 
        controlIds.forEach { controlFlows[it] = flow }
 
        scope.launch {
            delay(1000) // Retrieving the toggle state.
            flow.tryEmit(createLight())
 
            delay(1000) // Retrieving the range state.
            flow.tryEmit(createThermostat())
 
        }
        return flow.asPublisher()
    }
 
    private fun createLight() = createStatefulControl(
        LIGHT_ID,
        LIGHT_TITLE,
        LIGHT_TYPE,
        toggleState,
        ToggleTemplate(
            LIGHT_ID.toString(),
            ControlButton(
                toggleState,
                toggleState.toString().uppercase(Locale.getDefault())
            )
        )
    )
 
    private fun createThermostat() = createStatefulControl(
        THERMOSTAT_ID,
        THERMOSTAT_TITLE,
        THERMOSTAT_TYPE,
        rangeState,
        RangeTemplate(
            THERMOSTAT_ID.toString(),
            15f,
            25f,
            rangeState,
            0.1f,
            "%1.1f"
        )
    )
 
    private fun  createStatefulControl(id: Int, title: String, type: Int, state: T, template: ControlTemplate): Control {
        val intent = Intent(this, MainActivity::class.java)
            .putExtra(EXTRA_MESSAGE, "$title $state")
            .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
        val action = PendingIntent.getActivity(
            this,
            id,
            intent,
            PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
        )
 
        return Control.StatefulBuilder(id.toString(), action)
            .setTitle(title)
            .setDeviceType(type)
            .setStatus(Control.STATUS_OK)
            .setControlTemplate(template)
            .build()
    }
 
    override fun onDestroy() {
        super.onDestroy()
        job.cancel()
    }
 
    

Java

    @NonNull
    @Override
    public Flow.Publisher createPublisherFor(@NonNull List controlIds) {
        ReplayProcessor updatePublisher = ReplayProcessor.create();
 
        controlIds.forEach(control -> {
            controlFlows.put(control, updatePublisher);
            updatePublisher.onNext(createLight());
            updatePublisher.onNext(createThermostat());
        });
 
        return FlowAdapters.toFlowPublisher(updatePublisher);
    }
 
    private Control createStatelessControl(int id, String title, int type) {
        Intent intent = new Intent(this, MainActivity.class)
                .putExtra(EXTRA_MESSAGE, title)
                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        PendingIntent action = PendingIntent.getActivity(
                this,
                id,
                intent,
                PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE
        );
 
        return new Control.StatelessBuilder(id + "", action)
                .setTitle(title)
                .setDeviceType(type)
                .build();
    }
 
    private Control createLight() {
        return createStatefulControl(
                LIGHT_ID,
                LIGHT_TITLE,
                LIGHT_TYPE,
                toggleState,
                new ToggleTemplate(
                        LIGHT_ID + "",
                        new ControlButton(
                                toggleState,
                                String.valueOf(toggleState).toUpperCase(Locale.getDefault())
                        )
                )
        );
    }
 
    private Control createThermostat() {
        return createStatefulControl(
                THERMOSTAT_ID,
                THERMOSTAT_TITLE,
                THERMOSTAT_TYPE,
                rangeState,
                new RangeTemplate(
                        THERMOSTAT_ID + "",
                        15f,
                        25f,
                        rangeState,
                        0.1f,
                        "%1.1f"
                )
        );
    }
 
    private  Control createStatefulControl(int id, String title, int type, T state, ControlTemplate template) {
        Intent intent = new Intent(this, MainActivity.class)
                .putExtra(EXTRA_MESSAGE, "$title $state")
                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        PendingIntent action = PendingIntent.getActivity(
                this,
                id,
                intent,
                PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE
        );
 
        return new Control.StatefulBuilder(id + "", action)
                .setTitle(title)
                .setDeviceType(type)
                .setStatus(Control.STATUS_OK)
                .setControlTemplate(template)
                .build();
    }
    

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

เมธอด createPublisherFor() ใช้ Kotlin Coroutines และ Flow เพื่อให้เป็นไปตาม API ของ Reactive Streams ที่กำหนดไว้โดยทำดังนี้

  1. สร้าง Flow
  2. รอ 1 วินาที
  3. สร้างและส่งสถานะของหลอดไฟอัจฉริยะ
  4. รออีก 1 วินาที
  5. สร้างและส่งสถานะของตัวควบคุมอุณหภูมิ

จัดการการดำเนินการ

เมธอด performControlAction() จะส่งสัญญาณเมื่อผู้ใช้โต้ตอบกับการควบคุมที่เผยแพร่ ประเภทของ ControlAction ที่ส่งจะเป็นตัวกำหนดการดำเนินการ ดำเนินการที่เหมาะสมสำหรับการควบคุมที่กำหนด แล้วอัปเดตสถานะของอุปกรณ์ใน UI ของ Android

หากต้องการทำให้ตัวอย่างสมบูรณ์ ให้เพิ่มข้อมูลต่อไปนี้ลงใน Service

Kotlin

    override fun performControlAction(
        controlId: String,
        action: ControlAction,
        consumer: Consumer
    ) {
        controlFlows[controlId]?.let { flow ->
            when (controlId) {
                LIGHT_ID.toString() -> {
                    consumer.accept(ControlAction.RESPONSE_OK)
                    if (action is BooleanAction) toggleState = action.newState
                    flow.tryEmit(createLight())
                }
                THERMOSTAT_ID.toString() -> {
                    consumer.accept(ControlAction.RESPONSE_OK)
                    if (action is FloatAction) rangeState = action.newValue
                    flow.tryEmit(createThermostat())
                }
                else -> consumer.accept(ControlAction.RESPONSE_FAIL)
            }
        } ?: consumer.accept(ControlAction.RESPONSE_FAIL)
    }
    

Java

    @Override
    public void performControlAction(@NonNull String controlId, @NonNull ControlAction action, @NonNull Consumer consumer) {
        ReplayProcessor processor = controlFlows.get(controlId);
        if (processor == null) return;
 
        if (controlId.equals(LIGHT_ID + "")) {
            consumer.accept(ControlAction.RESPONSE_OK);
            if (action instanceof BooleanAction) toggleState = ((BooleanAction) action).getNewState();
            processor.onNext(createLight());
        }
        if (controlId.equals(THERMOSTAT_ID + "")) {
            consumer.accept(ControlAction.RESPONSE_OK);
            if (action instanceof FloatAction) rangeState = ((FloatAction) action).getNewValue()
            processor.onNext(createThermostat());
        }
    }
    

เรียกใช้แอป เข้าถึงเมนูการควบคุมอุปกรณ์ แล้วดูการควบคุมไฟและตัวควบคุมอุณหภูมิ

รูปภาพแสดงการควบคุมหลอดไฟและตัวควบคุมอุณหภูมิ
รูปที่ 6 การควบคุมไฟและตัวควบคุมอุณหภูมิ