From a15ac0677112d268728433254f1f1675fc8ce80e Mon Sep 17 00:00:00 2001 From: Daniel Mahaney <66575559+Papa-DMan@users.noreply.github.com> Date: Sun, 21 May 2023 18:31:27 -0400 Subject: [PATCH] Rp2040 pio ledstrip (#4818) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- CODEOWNERS | 1 + esphome/components/rp2040/__init__.py | 66 ++++- esphome/components/rp2040/const.py | 1 + .../rp2040_pio_led_strip/__init__.py | 1 + .../rp2040_pio_led_strip/led_strip.cpp | 139 +++++++++ .../rp2040_pio_led_strip/led_strip.h | 108 +++++++ .../components/rp2040_pio_led_strip/light.py | 267 ++++++++++++++++++ esphome/writer.py | 10 + tests/test6.yaml | 20 ++ 9 files changed, 610 insertions(+), 3 deletions(-) create mode 100644 esphome/components/rp2040_pio_led_strip/__init__.py create mode 100644 esphome/components/rp2040_pio_led_strip/led_strip.cpp create mode 100644 esphome/components/rp2040_pio_led_strip/led_strip.h create mode 100644 esphome/components/rp2040_pio_led_strip/light.py diff --git a/CODEOWNERS b/CODEOWNERS index de6488c3d3..2b6f10e4cf 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -220,6 +220,7 @@ esphome/components/restart/* @esphome/core esphome/components/rf_bridge/* @jesserockz esphome/components/rgbct/* @jesserockz esphome/components/rp2040/* @jesserockz +esphome/components/rp2040_pio_led_strip/* @Papa-DMan esphome/components/rp2040_pwm/* @jesserockz esphome/components/rtttl/* @glmnet esphome/components/safe_mode/* @jsuanet @paulmonigatti diff --git a/esphome/components/rp2040/__init__.py b/esphome/components/rp2040/__init__.py index 3d0d6ec060..1eba0bf192 100644 --- a/esphome/components/rp2040/__init__.py +++ b/esphome/components/rp2040/__init__.py @@ -1,4 +1,7 @@ import logging +import os + +from string import ascii_letters, digits import esphome.codegen as cg import esphome.config_validation as cv @@ -12,9 +15,11 @@ from esphome.const import ( KEY_TARGET_FRAMEWORK, KEY_TARGET_PLATFORM, ) -from esphome.core import CORE, coroutine_with_priority +from esphome.core import CORE, coroutine_with_priority, EsphomeError +from esphome.helpers import mkdir_p, write_file +import esphome.platformio_api as api -from .const import KEY_BOARD, KEY_RP2040, rp2040_ns +from .const import KEY_BOARD, KEY_PIO_FILES, KEY_RP2040, rp2040_ns # force import gpio to register pin schema from .gpio import rp2040_pin_to_code # noqa @@ -33,6 +38,8 @@ def set_core_data(config): ) CORE.data[KEY_RP2040][KEY_BOARD] = config[CONF_BOARD] + CORE.data[KEY_RP2040][KEY_PIO_FILES] = {} + return config @@ -148,7 +155,10 @@ async def to_code(config): cg.add_platformio_option("platform", conf[CONF_PLATFORM_VERSION]) cg.add_platformio_option( "platform_packages", - [f"earlephilhower/framework-arduinopico@{conf[CONF_SOURCE]}"], + [ + f"earlephilhower/framework-arduinopico@{conf[CONF_SOURCE]}", + "earlephilhower/tool-pioasm-rp2040-earlephilhower", + ], ) cg.add_platformio_option("board_build.core", "earlephilhower") @@ -159,3 +169,53 @@ async def to_code(config): "USE_ARDUINO_VERSION_CODE", cg.RawExpression(f"VERSION_CODE({ver.major}, {ver.minor}, {ver.patch})"), ) + + +def add_pio_file(component: str, key: str, data: str): + try: + cv.validate_id_name(key) + except cv.Invalid as e: + raise EsphomeError( + f"[{component}] Invalid PIO key: {key}. Allowed characters: [{ascii_letters}{digits}_]\nPlease report an issue https://github.com/esphome/issues" + ) from e + CORE.data[KEY_RP2040][KEY_PIO_FILES][key] = data + + +def generate_pio_files() -> bool: + import shutil + + shutil.rmtree(CORE.relative_build_path("src/pio"), ignore_errors=True) + + includes: list[str] = [] + files = CORE.data[KEY_RP2040][KEY_PIO_FILES] + if not files: + return False + for key, data in files.items(): + pio_path = CORE.relative_build_path(f"src/pio/{key}.pio") + mkdir_p(os.path.dirname(pio_path)) + write_file(pio_path, data) + _LOGGER.info("Assembling PIO assembly code") + retval = api.run_platformio_cli( + "pkg", + "exec", + "--package", + "earlephilhower/tool-pioasm-rp2040-earlephilhower", + "--", + "pioasm", + pio_path, + pio_path + ".h", + ) + includes.append(f"pio/{key}.pio.h") + if retval != 0: + raise EsphomeError("PIO assembly failed") + + write_file( + CORE.relative_build_path("src/pio_includes.h"), + "#pragma once\n" + "\n".join([f'#include "{include}"' for include in includes]), + ) + return True + + +# Called by writer.py +def copy_files() -> bool: + return generate_pio_files() diff --git a/esphome/components/rp2040/const.py b/esphome/components/rp2040/const.py index e09016ca31..ab5f42d757 100644 --- a/esphome/components/rp2040/const.py +++ b/esphome/components/rp2040/const.py @@ -2,5 +2,6 @@ import esphome.codegen as cg KEY_BOARD = "board" KEY_RP2040 = "rp2040" +KEY_PIO_FILES = "pio_files" rp2040_ns = cg.esphome_ns.namespace("rp2040") diff --git a/esphome/components/rp2040_pio_led_strip/__init__.py b/esphome/components/rp2040_pio_led_strip/__init__.py new file mode 100644 index 0000000000..4c9aa2d155 --- /dev/null +++ b/esphome/components/rp2040_pio_led_strip/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@Papa-DMan"] diff --git a/esphome/components/rp2040_pio_led_strip/led_strip.cpp b/esphome/components/rp2040_pio_led_strip/led_strip.cpp new file mode 100644 index 0000000000..ce1836306f --- /dev/null +++ b/esphome/components/rp2040_pio_led_strip/led_strip.cpp @@ -0,0 +1,139 @@ +#include "led_strip.h" + +#ifdef USE_RP2040 + +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" + +#include +#include +#include + +namespace esphome { +namespace rp2040_pio_led_strip { + +static const char *TAG = "rp2040_pio_led_strip"; + +void RP2040PIOLEDStripLightOutput::setup() { + ESP_LOGCONFIG(TAG, "Setting up RP2040 LED Strip..."); + + size_t buffer_size = this->get_buffer_size_(); + + ExternalRAMAllocator allocator(ExternalRAMAllocator::ALLOW_FAILURE); + this->buf_ = allocator.allocate(buffer_size); + if (this->buf_ == nullptr) { + ESP_LOGE(TAG, "Failed to allocate buffer of size %u", buffer_size); + this->mark_failed(); + return; + } + + this->effect_data_ = allocator.allocate(this->num_leds_); + if (this->effect_data_ == nullptr) { + ESP_LOGE(TAG, "Failed to allocate effect data of size %u", this->num_leds_); + this->mark_failed(); + return; + } + + // Select PIO instance to use (0 or 1) + this->pio_ = pio0; + if (this->pio_ == nullptr) { + ESP_LOGE(TAG, "Failed to claim PIO instance"); + this->mark_failed(); + return; + } + + // Load the assembled program into the PIO and get its location in the PIO's instruction memory + uint offset = pio_add_program(this->pio_, this->program_); + + // Configure the state machine's PIO, and start it + this->sm_ = pio_claim_unused_sm(this->pio_, true); + if (this->sm_ < 0) { + ESP_LOGE(TAG, "Failed to claim PIO state machine"); + this->mark_failed(); + return; + } + this->init_(this->pio_, this->sm_, offset, this->pin_, this->max_refresh_rate_); +} + +void RP2040PIOLEDStripLightOutput::write_state(light::LightState *state) { + ESP_LOGVV(TAG, "Writing state..."); + + if (this->is_failed()) { + ESP_LOGW(TAG, "Light is in failed state, not writing state."); + return; + } + + if (this->buf_ == nullptr) { + ESP_LOGW(TAG, "Buffer is null, not writing state."); + return; + } + + // assemble bits in buffer to 32 bit words with ex for GBR: 0bGGGGGGGGRRRRRRRRBBBBBBBB00000000 + for (int i = 0; i < this->num_leds_; i++) { + uint8_t c1 = this->buf_[(i * 3) + 0]; + uint8_t c2 = this->buf_[(i * 3) + 1]; + uint8_t c3 = this->buf_[(i * 3) + 2]; + uint8_t w = this->is_rgbw_ ? this->buf_[(i * 4) + 3] : 0; + uint32_t color = encode_uint32(c1, c2, c3, w); + pio_sm_put_blocking(this->pio_, this->sm_, color); + } +} + +light::ESPColorView RP2040PIOLEDStripLightOutput::get_view_internal(int32_t index) const { + int32_t r = 0, g = 0, b = 0, w = 0; + switch (this->rgb_order_) { + case ORDER_RGB: + r = 0; + g = 1; + b = 2; + break; + case ORDER_RBG: + r = 0; + g = 2; + b = 1; + break; + case ORDER_GRB: + r = 1; + g = 0; + b = 2; + break; + case ORDER_GBR: + r = 2; + g = 0; + b = 1; + break; + case ORDER_BGR: + r = 2; + g = 1; + b = 0; + break; + case ORDER_BRG: + r = 1; + g = 2; + b = 0; + break; + } + uint8_t multiplier = this->is_rgbw_ ? 4 : 3; + return {this->buf_ + (index * multiplier) + r, + this->buf_ + (index * multiplier) + g, + this->buf_ + (index * multiplier) + b, + this->is_rgbw_ ? this->buf_ + (index * multiplier) + 3 : nullptr, + &this->effect_data_[index], + &this->correction_}; +} + +void RP2040PIOLEDStripLightOutput::dump_config() { + ESP_LOGCONFIG(TAG, "RP2040 PIO LED Strip Light Output:"); + ESP_LOGCONFIG(TAG, " Pin: GPIO%d", this->pin_); + ESP_LOGCONFIG(TAG, " Number of LEDs: %d", this->num_leds_); + ESP_LOGCONFIG(TAG, " RGBW: %s", YESNO(this->is_rgbw_)); + ESP_LOGCONFIG(TAG, " RGB Order: %s", rgb_order_to_string(this->rgb_order_)); + ESP_LOGCONFIG(TAG, " Max Refresh Rate: %f Hz", this->max_refresh_rate_); +} + +float RP2040PIOLEDStripLightOutput::get_setup_priority() const { return setup_priority::HARDWARE; } + +} // namespace rp2040_pio_led_strip +} // namespace esphome + +#endif diff --git a/esphome/components/rp2040_pio_led_strip/led_strip.h b/esphome/components/rp2040_pio_led_strip/led_strip.h new file mode 100644 index 0000000000..b3a6b87d7d --- /dev/null +++ b/esphome/components/rp2040_pio_led_strip/led_strip.h @@ -0,0 +1,108 @@ +#pragma once + +#ifdef USE_RP2040 + +#include "esphome/core/color.h" +#include "esphome/core/component.h" +#include "esphome/core/helpers.h" + +#include "esphome/components/light/addressable_light.h" +#include "esphome/components/light/light_output.h" + +#include +#include +#include + +namespace esphome { +namespace rp2040_pio_led_strip { + +enum RGBOrder : uint8_t { + ORDER_RGB, + ORDER_RBG, + ORDER_GRB, + ORDER_GBR, + ORDER_BGR, + ORDER_BRG, +}; + +inline const char *rgb_order_to_string(RGBOrder order) { + switch (order) { + case ORDER_RGB: + return "RGB"; + case ORDER_RBG: + return "RBG"; + case ORDER_GRB: + return "GRB"; + case ORDER_GBR: + return "GBR"; + case ORDER_BGR: + return "BGR"; + case ORDER_BRG: + return "BRG"; + default: + return "UNKNOWN"; + } +} + +using init_fn = void (*)(PIO pio, uint sm, uint offset, uint pin, float freq); + +class RP2040PIOLEDStripLightOutput : public light::AddressableLight { + public: + void setup() override; + void write_state(light::LightState *state) override; + float get_setup_priority() const override; + + int32_t size() const override { return this->num_leds_; } + light::LightTraits get_traits() override { + auto traits = light::LightTraits(); + this->is_rgbw_ ? traits.set_supported_color_modes({light::ColorMode::RGB, light::ColorMode::RGB_WHITE}) + : traits.set_supported_color_modes({light::ColorMode::RGB}); + return traits; + } + void set_pin(uint8_t pin) { this->pin_ = pin; } + void set_num_leds(uint32_t num_leds) { this->num_leds_ = num_leds; } + void set_is_rgbw(bool is_rgbw) { this->is_rgbw_ = is_rgbw; } + + void set_max_refresh_rate(float interval_us) { this->max_refresh_rate_ = interval_us; } + + void set_pio(int pio_num) { pio_num ? this->pio_ = pio1 : this->pio_ = pio0; } + void set_program(const pio_program_t *program) { this->program_ = program; } + void set_init_function(init_fn init) { this->init_ = init; } + + void set_rgb_order(RGBOrder rgb_order) { this->rgb_order_ = rgb_order; } + void clear_effect_data() override { + for (int i = 0; i < this->size(); i++) { + this->effect_data_[i] = 0; + } + } + + void dump_config() override; + + protected: + light::ESPColorView get_view_internal(int32_t index) const override; + + size_t get_buffer_size_() const { return this->num_leds_ * (3 + this->is_rgbw_); } + + uint8_t *buf_{nullptr}; + uint8_t *effect_data_{nullptr}; + + uint8_t pin_; + uint32_t num_leds_; + bool is_rgbw_; + + pio_hw_t *pio_; + uint sm_; + + RGBOrder rgb_order_{ORDER_RGB}; + + uint32_t last_refresh_{0}; + float max_refresh_rate_; + + const pio_program_t *program_; + init_fn init_; +}; + +} // namespace rp2040_pio_led_strip +} // namespace esphome + +#endif // USE_RP2040 diff --git a/esphome/components/rp2040_pio_led_strip/light.py b/esphome/components/rp2040_pio_led_strip/light.py new file mode 100644 index 0000000000..432ff6935a --- /dev/null +++ b/esphome/components/rp2040_pio_led_strip/light.py @@ -0,0 +1,267 @@ +from dataclasses import dataclass + +from esphome import pins +from esphome.components import light, rp2040 +from esphome.const import ( + CONF_CHIPSET, + CONF_ID, + CONF_NUM_LEDS, + CONF_OUTPUT_ID, + CONF_PIN, + CONF_RGB_ORDER, +) + +import esphome.codegen as cg +import esphome.config_validation as cv + +from esphome.util import _LOGGER + + +def get_nops(timing): + """ + Calculate the number of NOP instructions required to wait for a given amount of time. + """ + time_remaining = timing + nops = [] + if time_remaining < 32: + nops.append(time_remaining - 1) + return nops + nops.append(31) + time_remaining -= 32 + while time_remaining > 0: + if time_remaining >= 32: + nops.append("nop [31]") + time_remaining -= 32 + else: + nops.append("nop [" + str(time_remaining) + " - 1 ]") + time_remaining = 0 + return nops + + +def generate_assembly_code(id, rgbw, t0h, t0l, t1h, t1l): + """ + Generate assembly code with the given timing values. + """ + nops_t0h = get_nops(t0h) + nops_t0l = get_nops(t0l) + nops_t1h = get_nops(t1h) + nops_t1l = get_nops(t1l) + + t0h = nops_t0h.pop(0) + t0l = nops_t0l.pop(0) + t1h = nops_t1h.pop(0) + t1l = nops_t1l.pop(0) + + nops_t0h = "\n".join(" " * 4 + nop for nop in nops_t0h) + nops_t0l = "\n".join(" " * 4 + nop for nop in nops_t0l) + nops_t1h = "\n".join(" " * 4 + nop for nop in nops_t1h) + nops_t1l = "\n".join(" " * 4 + nop for nop in nops_t1l) + + const_csdk_code = f""" +% c-sdk {{ +#include "hardware/clocks.h" + +static inline void rp2040_pio_led_strip_driver_{id}_init(PIO pio, uint sm, uint offset, uint pin, float freq) {{ + pio_gpio_init(pio, pin); + pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true); + + pio_sm_config c = rp2040_pio_led_strip_{id}_program_get_default_config(offset); + sm_config_set_set_pins(&c, pin, 1); + sm_config_set_out_shift(&c, false, true, {32 if rgbw else 24}); + sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX); + + int cycles_per_bit = 69; + float div = 2.409; + sm_config_set_clkdiv(&c, div); + + + pio_sm_init(pio, sm, offset, &c); + pio_sm_set_enabled(pio, sm, true); +}} +%}}""" + + assembly_template = f""".program rp2040_pio_led_strip_{id} + +.wrap_target +awaiting_data: + ; Wait for data in FIFO queue + pull block ; this will block until there is data in the FIFO queue and then it will pull it into the shift register + set y, {31 if rgbw else 23} ; set y to the number of bits to write counting 0, (23 if RGB, 31 if RGBW) + +mainloop: + ; go through each bit in the shift register and jump to the appropriate label + ; depending on the value of the bit + + out x, 1 + jmp !x, writezero + jmp writeone + +writezero: + ; Write T0H and T0L bits to the output pin + set pins, 1 [{t0h}] +{nops_t0h} + set pins, 0 [{t0l}] +{nops_t0l} + jmp y--, mainloop + jmp awaiting_data + +writeone: + ; Write T1H and T1L bits to the output pin + set pins, 1 [{t1h}] +{nops_t1h} + set pins, 0 [{t1l}] +{nops_t1l} + jmp y--, mainloop + jmp awaiting_data + +.wrap""" + + return assembly_template + const_csdk_code + + +def time_to_cycles(time_us): + cycles_per_us = 57.5 + cycles = round(float(time_us) * cycles_per_us) + return cycles + + +CONF_PIO = "pio" + +CODEOWNERS = ["@Papa-DMan"] +DEPENDENCIES = ["rp2040"] + +rp2040_pio_led_strip_ns = cg.esphome_ns.namespace("rp2040_pio_led_strip") +RP2040PIOLEDStripLightOutput = rp2040_pio_led_strip_ns.class_( + "RP2040PIOLEDStripLightOutput", light.AddressableLight +) + +RGBOrder = rp2040_pio_led_strip_ns.enum("RGBOrder") + +Chipsets = rp2040_pio_led_strip_ns.enum("Chipset") + + +@dataclass +class LEDStripTimings: + T0H: int + T0L: int + T1H: int + T1L: int + + +RGB_ORDERS = { + "RGB": RGBOrder.ORDER_RGB, + "RBG": RGBOrder.ORDER_RBG, + "GRB": RGBOrder.ORDER_GRB, + "GBR": RGBOrder.ORDER_GBR, + "BGR": RGBOrder.ORDER_BGR, + "BRG": RGBOrder.ORDER_BRG, +} + +CHIPSETS = { + "WS2812": LEDStripTimings(20, 43, 41, 31), + "WS2812B": LEDStripTimings(23, 46, 46, 23), + "SK6812": LEDStripTimings(17, 52, 31, 31), + "SM16703": LEDStripTimings(17, 52, 52, 17), +} + +CONF_IS_RGBW = "is_rgbw" +CONF_BIT0_HIGH = "bit0_high" +CONF_BIT0_LOW = "bit0_low" +CONF_BIT1_HIGH = "bit1_high" +CONF_BIT1_LOW = "bit1_low" + + +def _validate_timing(value): + # if doesn't end with us, raise error + if not value.endswith("us"): + raise cv.Invalid("Timing must be in microseconds (us)") + value = float(value[:-2]) + nops = get_nops(value) + nops.pop(0) + if len(nops) > 3: + raise cv.Invalid("Timing is too long, please try again.") + return value + + +CONFIG_SCHEMA = cv.All( + light.ADDRESSABLE_LIGHT_SCHEMA.extend( + { + cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(RP2040PIOLEDStripLightOutput), + cv.Required(CONF_PIN): pins.internal_gpio_output_pin_number, + cv.Required(CONF_NUM_LEDS): cv.positive_not_null_int, + cv.Required(CONF_RGB_ORDER): cv.enum(RGB_ORDERS, upper=True), + cv.Required(CONF_PIO): cv.one_of(0, 1, int=True), + cv.Optional(CONF_CHIPSET): cv.one_of(*CHIPSETS, upper=True), + cv.Optional(CONF_IS_RGBW, default=False): cv.boolean, + cv.Inclusive( + CONF_BIT0_HIGH, + "custom", + ): _validate_timing, + cv.Inclusive( + CONF_BIT0_LOW, + "custom", + ): _validate_timing, + cv.Inclusive( + CONF_BIT1_HIGH, + "custom", + ): _validate_timing, + cv.Inclusive( + CONF_BIT1_LOW, + "custom", + ): _validate_timing, + } + ), + cv.has_exactly_one_key(CONF_CHIPSET, CONF_BIT0_HIGH), +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_OUTPUT_ID]) + id = config[CONF_ID].id + await light.register_light(var, config) + await cg.register_component(var, config) + + cg.add(var.set_num_leds(config[CONF_NUM_LEDS])) + cg.add(var.set_pin(config[CONF_PIN])) + + cg.add(var.set_rgb_order(config[CONF_RGB_ORDER])) + cg.add(var.set_is_rgbw(config[CONF_IS_RGBW])) + + cg.add(var.set_pio(config[CONF_PIO])) + cg.add(var.set_program(cg.RawExpression(f"&rp2040_pio_led_strip_{id}_program"))) + cg.add( + var.set_init_function( + cg.RawExpression(f"rp2040_pio_led_strip_driver_{id}_init") + ) + ) + + key = f"led_strip_{id}" + + if CONF_CHIPSET in config: + _LOGGER.info("Generating PIO assembly code") + rp2040.add_pio_file( + __name__, + key, + generate_assembly_code( + id, + config[CONF_IS_RGBW], + CHIPSETS[config[CONF_CHIPSET]].T0H, + CHIPSETS[config[CONF_CHIPSET]].T0L, + CHIPSETS[config[CONF_CHIPSET]].T1H, + CHIPSETS[config[CONF_CHIPSET]].T1L, + ), + ) + else: + _LOGGER.info("Generating custom PIO assembly code") + rp2040.add_pio_file( + __name__, + key, + generate_assembly_code( + id, + config[CONF_IS_RGBW], + time_to_cycles(config[CONF_BIT0_HIGH]), + time_to_cycles(config[CONF_BIT0_LOW]), + time_to_cycles(config[CONF_BIT1_HIGH]), + time_to_cycles(config[CONF_BIT1_LOW]), + ), + ) diff --git a/esphome/writer.py b/esphome/writer.py index 2bf665c2b2..ad506b6ae6 100644 --- a/esphome/writer.py +++ b/esphome/writer.py @@ -299,6 +299,16 @@ def copy_src_tree(): copy_files() + elif CORE.is_rp2040: + from esphome.components.rp2040 import copy_files + + (pio) = copy_files() + if pio: + write_file_if_changed( + CORE.relative_src_path("esphome.h"), + ESPHOME_H_FORMAT.format(include_s + '\n#include "pio_includes.h"'), + ) + def generate_defines_h(): define_content_l = [x.as_macro for x in CORE.defines] diff --git a/tests/test6.yaml b/tests/test6.yaml index 7d4bd7bb19..6d956aa9c8 100644 --- a/tests/test6.yaml +++ b/tests/test6.yaml @@ -37,6 +37,26 @@ switch: output: pin_4 id: pin_4_switch +light: + - platform: rp2040_pio_led_strip + id: led_strip + pin: GPIO13 + num_leds: 60 + pio: 0 + rgb_order: GRB + chipset: WS2812 + - platform: rp2040_pio_led_strip + id: led_strip_custom_timings + pin: GPIO13 + num_leds: 60 + pio: 1 + rgb_order: GRB + bit0_high: .1us + bit0_low: 1.2us + bit1_high: .69us + bit1_low: .4us + + sensor: - platform: internal_temperature name: Internal Temperature