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

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

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

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

ส่วนติดต่อผู้ใช้

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

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

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

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

สร้างบริการ

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

ControlsProviderService API ถือว่าผู้ใช้คุ้นเคยกับสตรีมแบบเรียลไทม์ตามที่ระบุไว้ในโปรเจ็กต์ Reactive Streams ใน GitHub และใช้งานในอินเทอร์เฟซ Flow ของ Java 9 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 และทำให้ไฟล์นั้น extends ControlsProviderService() ดังนี้

Kotlin

    class MyCustomControlService : ControlsProviderService() {
        ...
    }
    

Java

    public class MyCustomJavaControlService extends ControlsProviderService {
        ...
    }
    

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

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

  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 คุณสามารถใช้เทมเพลตนี้เพื่อกำหนดกิจวัตรหรือมาโคร ซึ่งเป็นการรวมการควบคุมและการเปลี่ยนแปลงสถานะ

คุณสร้างการควบคุมได้ด้วยข้อมูลนี้

  • ใช้เมนู Control.StatelessBuilder คลาสเครื่องมือสร้างเมื่อไม่ทราบสถานะของตัวควบคุม
  • ใช้เมนู 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() เพื่อโฆษณาการควบคุมที่ใช้ได้ในระบบ วิธีนี้ใช้Control.StatelessBuilderคลาสตัวสร้าง เนื่องจากสถานะของตัวควบคุมแต่ละรายการไม่เป็นที่รู้จัก

เมื่อตัวควบคุมปรากฏใน UI ของ Android ผู้ใช้จะเลือกรายการโปรด

หากต้องการใช้โครูทีนของ Kotlin เพื่อสร้าง ControlsProviderService ให้เพิ่ม ขึ้นกับ build.gradle ของคุณ:

Groovy

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 และโฟลว์การตอบสนอง Reactive Streams API ที่จำเป็นโดยทำตามขั้นตอนต่อไปนี้

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