1
0
mirror of https://github.com/esphome/esphome.git synced 2025-04-09 12:20:30 +01:00

add zephyr

This commit is contained in:
Tomasz Duda 2024-01-21 20:45:36 +01:00
parent c5b70bad84
commit 52cf8a1cf5
20 changed files with 688 additions and 32 deletions

View File

@ -0,0 +1,187 @@
from bleak import BleakClient, BleakScanner
from bleak.backends.characteristic import BleakGATTCharacteristic
from ble_serial.bluetooth.constants import ble_chars
import logging
import asyncio
from typing import Optional
class BLE_interface:
def __init__(self, adapter: str, service: str):
self._send_queue = asyncio.Queue()
self.scan_args = dict(adapter=adapter)
if service:
self.scan_args["service_uuids"] = [service]
async def connect(self, addr_str: str, addr_type: str, timeout: float):
if addr_str:
device = await BleakScanner.find_device_by_address(
addr_str, timeout=timeout, **self.scan_args
)
else:
logging.warning(
"Picking first device with matching service, "
"consider passing a specific device address, especially if there could be multiple devices"
)
device = await BleakScanner.find_device_by_filter(
lambda dev, ad: True, timeout=timeout, **self.scan_args
)
assert device, "No matching device found!"
# address_type used only in Windows .NET currently
self.dev = BleakClient(
device,
address_type=addr_type,
timeout=timeout,
disconnected_callback=self.handle_disconnect,
)
logging.info(f"Trying to connect with {device}")
await self.dev.connect()
logging.info(f"Device {self.dev.address} connected")
async def setup_chars(self, write_uuid: str, read_uuid: str, mode: str):
self.read_enabled = "r" in mode
self.write_enabled = "w" in mode
if self.write_enabled:
self.write_char = self.find_char(
write_uuid, ["write", "write-without-response"]
)
else:
logging.info("Writing disabled, skipping write UUID detection")
if self.read_enabled:
self.read_char = self.find_char(read_uuid, ["notify", "indicate"])
await self.dev.start_notify(self.read_char, self.handle_notify)
else:
logging.info("Reading disabled, skipping read UUID detection")
def find_char(
self, uuid: Optional[str], req_props: [str]
) -> BleakGATTCharacteristic:
name = req_props[0]
# Use user supplied UUID first, otherwise try included list
if uuid:
uuid_candidates = [uuid]
else:
uuid_candidates = ble_chars
logging.debug(f"No {name} uuid specified, trying builtin list")
results = []
for srv in self.dev.services:
for c in srv.characteristics:
if c.uuid in uuid_candidates:
results.append(c)
if uuid:
assert (
len(results) > 0
), f"No characteristic with specified {name} UUID {uuid} found!"
else:
assert (
len(results) > 0
), f"""No characteristic in builtin {name} list {uuid_candidates} found!
Please specify one with {'-w/--write-uuid' if name == 'write' else '-r/--read-uuid'}, see also --help"""
res_str = "\n".join(f"\t{c} {c.properties}" for c in results)
logging.debug(f"Characteristic candidates for {name}: \n{res_str}")
# Check if there is a intersection of permission flags
results[:] = [c for c in results if set(c.properties) & set(req_props)]
assert len(results) > 0, f"No characteristic with {req_props} property found!"
assert (
len(results) == 1
), f"Multiple matching {name} characteristics found, please specify one"
# must be valid here
found = results[0]
logging.info(f"Found {name} characteristic {found.uuid} (H. {found.handle})")
return found
def set_receiver(self, callback):
self._cb = callback
logging.info("Receiver set up")
async def send_loop(self):
assert hasattr(self, "_cb"), "Callback must be set before receive loop!"
while True:
data = await self._send_queue.get()
if data is None:
break # Let future end on shutdown
if not self.write_enabled:
logging.warning(f"Ignoring unexpected write data: {data}")
continue
logging.debug(f"Sending {data}")
await self.dev.write_gatt_char(self.write_char, data)
def stop_loop(self):
logging.info("Stopping Bluetooth event loop")
self._send_queue.put_nowait(None)
async def disconnect(self):
if hasattr(self, "dev") and self.dev.is_connected:
if hasattr(self, "read_char"):
await self.dev.stop_notify(self.read_char)
await self.dev.disconnect()
logging.info("Bluetooth disconnected")
def queue_send(self, data: bytes):
self._send_queue.put_nowait(data)
def handle_notify(self, handle: int, data: bytes):
logging.debug(f"Received notify from {handle}: {data}")
if not self.read_enabled:
logging.warning(f"Read unexpected data, dropping: {data}")
return
self._cb(data)
def handle_disconnect(self, client: BleakClient):
logging.warning(f"Device {client.address} disconnected")
self.stop_loop()
def receive_callback(value: bytes):
print("Received:", value)
async def hello_sender(ble: BLE_interface):
while True:
await asyncio.sleep(1 / 100)
# print("Sending...")
ble.queue_send(
b"123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890\n"
)
# ble.queue_send(b"123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890\n")
# ble.queue_send(b"123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890\n")
async def main():
# None uses default/autodetection, insert values if needed
ADAPTER = "hci0"
SERVICE_UUID = "6ba1b218-15a8-461f-9fa8-5dcae273eafd"
WRITE_UUID = "f75c76d2-129e-4dad-a1dd-7866124401e7"
# READ_UUID = '2c55e69e-4993-11ed-b878-0242ac120002'
READ_UUID = "ed9da18c-a800-4f66-a670-aa7547e34453"
DEVICE = "EF:45:95:65:46:FD"
ble = BLE_interface(ADAPTER, SERVICE_UUID)
ble.set_receiver(receive_callback)
try:
await ble.connect(DEVICE, "public", 10.0)
await ble.setup_chars(WRITE_UUID, READ_UUID, "rw")
await asyncio.gather(ble.send_loop(), hello_sender(ble))
finally:
await ble.disconnect()
if __name__ == "__main__":
logging.basicConfig(level=logging.INFO)
asyncio.run(main())

View File

@ -0,0 +1,53 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.const import (
CONF_ID,
)
from esphome.components.nrf52 import add_zephyr_prj_conf_option
ipsp_ns = cg.esphome_ns.namespace("ipsp")
Network = ipsp_ns.class_("Network", cg.Component)
CONFIG_SCHEMA = cv.All(
cv.Schema(
{
cv.GenerateID(): cv.declare_id(Network),
}
).extend(cv.COMPONENT_SCHEMA),
cv.only_with_zephyr,
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
add_zephyr_prj_conf_option("CONFIG_BT", True)
add_zephyr_prj_conf_option("CONFIG_BT_SMP", True)
add_zephyr_prj_conf_option("CONFIG_BT_PERIPHERAL", True)
add_zephyr_prj_conf_option("CONFIG_BT_CENTRAL", True)
add_zephyr_prj_conf_option("CONFIG_BT_L2CAP_DYNAMIC_CHANNEL", True)
add_zephyr_prj_conf_option("CONFIG_BT_DEVICE_NAME", "Test IPSP node") # TODO
add_zephyr_prj_conf_option("CONFIG_NETWORKING", True)
add_zephyr_prj_conf_option("CONFIG_NET_IPV6", True)
add_zephyr_prj_conf_option("CONFIG_NET_IPV4", False)
add_zephyr_prj_conf_option("CONFIG_NET_UDP", False)
add_zephyr_prj_conf_option("CONFIG_NET_TCP", True)
add_zephyr_prj_conf_option("CONFIG_TEST_RANDOM_GENERATOR", True)
add_zephyr_prj_conf_option("CONFIG_NET_L2_BT", True)
add_zephyr_prj_conf_option("CONFIG_INIT_STACKS", True)
add_zephyr_prj_conf_option("CONFIG_NET_PKT_RX_COUNT", 10)
add_zephyr_prj_conf_option("CONFIG_NET_PKT_TX_COUNT", 10)
add_zephyr_prj_conf_option("CONFIG_NET_BUF_RX_COUNT", 20)
add_zephyr_prj_conf_option("CONFIG_NET_BUF_TX_COUNT", 20)
add_zephyr_prj_conf_option("CONFIG_NET_IF_UNICAST_IPV6_ADDR_COUNT", 3)
add_zephyr_prj_conf_option("CONFIG_NET_IF_MCAST_IPV6_ADDR_COUNT", 4)
add_zephyr_prj_conf_option("CONFIG_NET_MAX_CONTEXTS", 6)
add_zephyr_prj_conf_option("CONFIG_NET_CONFIG_AUTO_INIT", True)
add_zephyr_prj_conf_option("CONFIG_NET_CONFIG_SETTINGS", True)
add_zephyr_prj_conf_option("CONFIG_NET_CONFIG_MY_IPV6_ADDR", "2001:db8::1")
add_zephyr_prj_conf_option("CONFIG_NET_CONFIG_PEER_IPV6_ADDR", "2001:db8::2")
add_zephyr_prj_conf_option("CONFIG_NET_CONFIG_BT_NODE", True)
if True:
add_zephyr_prj_conf_option("CONFIG_LOG", True)
add_zephyr_prj_conf_option("CONFIG_NET_LOG", True)

View File

@ -0,0 +1,31 @@
#include "network.h"
#include <zephyr/net/net_if.h>
/* Define my IP address where to expect messages */
#define MY_IP6ADDR { { { 0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, \
0, 0, 0, 0, 0, 0, 0, 0x1 } } }
static struct in6_addr in6addr_my = MY_IP6ADDR;
namespace esphome {
namespace ipsp {
void Network::setup() {
if (net_addr_pton(AF_INET6,
CONFIG_NET_CONFIG_MY_IPV6_ADDR,
&in6addr_my) < 0) {
// LOG_ERR("Invalid IPv6 address %s",
// CONFIG_NET_CONFIG_MY_IPV6_ADDR);
}
do {
struct net_if_addr *ifaddr;
ifaddr = net_if_ipv6_addr_add(net_if_get_default(),
&in6addr_my, NET_ADDR_MANUAL, 0);
} while (0);
}
} // namespace shell
} // namespace esphome

View File

@ -0,0 +1,10 @@
#pragma once
#include "esphome/core/component.h"
namespace esphome {
namespace ipsp {
class Network : public Component {
void setup() override;
};
}
}

View File

@ -27,8 +27,10 @@
#include "esphome/core/log.h"
#ifdef USE_NRF52
#ifdef USE_ARDUINO
#include <Adafruit_TinyUSB.h> // for Serial
#endif
#endif
namespace esphome {
namespace logger {
@ -64,12 +66,16 @@ void Logger::write_header_(int level, const char *tag, int line) {
const char *color = LOG_LEVEL_COLORS[level];
const char *letter = LOG_LEVEL_LETTERS[level];
#ifdef USE_ARDUINO
void *current_task = xTaskGetCurrentTaskHandle();
if (current_task == main_task) {
#endif
this->printf_to_buffer_("%s[%s][%s:%03u]: ", color, letter, tag, line);
#ifdef USE_ARDUINO
} else {
this->printf_to_buffer_("%s[%s][%s:%03u]%s[%s]%s: ", color, letter, tag, line, ESPHOME_LOG_BOLD(ESPHOME_LOG_COLOR_RED), pcTaskGetName(current_task), color);
}
#endif
}
void HOT Logger::log_vprintf_(int level, const char *tag, int line, const char *format, va_list args) { // NOLINT
@ -230,7 +236,9 @@ void HOT Logger::log_message_(int level, const char *tag, int offset) {
Logger::Logger(uint32_t baud_rate, size_t tx_buffer_size) : baud_rate_(baud_rate), tx_buffer_size_(tx_buffer_size) {
// add 1 to buffer size for null terminator
this->tx_buffer_ = new char[this->tx_buffer_size_ + 1]; // NOLINT
#ifdef USE_ARDUINO
this->main_task = xTaskGetCurrentTaskHandle();
#endif
}
#ifndef USE_LIBRETINY

View File

@ -3,34 +3,47 @@ import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.const import (
CONF_BOARD,
CONF_FRAMEWORK,
KEY_CORE,
KEY_TARGET_FRAMEWORK,
KEY_TARGET_PLATFORM,
PLATFORM_NRF52,
CONF_TYPE,
CONF_FRAMEWORK,
)
from esphome.core import CORE, coroutine_with_priority
from esphome.helpers import (
write_file_if_changed,
)
from typing import Union
# force import gpio to register pin schema
from .gpio import nrf52_pin_to_code # noqa
AUTO_LOAD = ["nrf52_nrfx"]
# def AUTO_LOAD():
# # if CORE.using_arduino:
# return ["nrf52_nrfx"]
# # return []
KEY_NRF52 = "nrf52"
def set_core_data(config):
CORE.data[KEY_NRF52] = {}
CORE.data[KEY_NRF52][KEY_PRJ_CONF_OPTIONS] = {}
CORE.data[KEY_CORE][KEY_TARGET_PLATFORM] = PLATFORM_NRF52
CORE.data[KEY_CORE][KEY_TARGET_FRAMEWORK] = "arduino"
CORE.data[KEY_CORE][KEY_TARGET_FRAMEWORK] = config[CONF_FRAMEWORK][CONF_TYPE]
return config
# https://github.com/platformio/platform-nordicnrf52/releases
ARDUINO_PLATFORM_VERSION = cv.Version(10, 3, 0)
NORDICNRF52_PLATFORM_VERSION = cv.Version(10, 3, 0)
def _arduino_check_versions(value):
def _platform_check_versions(value):
value = value.copy()
value[CONF_PLATFORM_VERSION] = value.get(
CONF_PLATFORM_VERSION, _parse_platform_version(str(ARDUINO_PLATFORM_VERSION))
CONF_PLATFORM_VERSION,
_parse_platform_version(str(NORDICNRF52_PLATFORM_VERSION)),
)
return value
@ -46,20 +59,32 @@ def _parse_platform_version(value):
CONF_PLATFORM_VERSION = "platform_version"
ARDUINO_FRAMEWORK_SCHEMA = cv.All(
PLATFORM_FRAMEWORK_SCHEMA = cv.All(
cv.Schema(
{
cv.Optional(CONF_PLATFORM_VERSION): _parse_platform_version,
}
),
_arduino_check_versions,
_platform_check_versions,
)
FRAMEWORK_ZEPHYR = "zephyr"
FRAMEWORK_ARDUINO = "arduino"
FRAMEWORK_SCHEMA = cv.typed_schema(
{
FRAMEWORK_ZEPHYR: PLATFORM_FRAMEWORK_SCHEMA,
FRAMEWORK_ARDUINO: PLATFORM_FRAMEWORK_SCHEMA,
},
lower=True,
space="-",
default_type=FRAMEWORK_ARDUINO,
)
CONFIG_SCHEMA = cv.All(
cv.Schema(
{
cv.Required(CONF_BOARD): cv.string_strict,
cv.Optional(CONF_FRAMEWORK, default={}): ARDUINO_FRAMEWORK_SCHEMA,
cv.Optional(CONF_FRAMEWORK, default={}): FRAMEWORK_SCHEMA,
}
),
set_core_data,
@ -68,31 +93,94 @@ CONFIG_SCHEMA = cv.All(
nrf52_ns = cg.esphome_ns.namespace("nrf52")
PrjConfValueType = Union[bool, str, int]
KEY_PRJ_CONF_OPTIONS = "prj_conf_options"
def add_zephyr_prj_conf_option(name: str, value: PrjConfValueType):
"""Set an zephyr prj conf value."""
if not CORE.using_zephyr:
raise ValueError("Not an zephyr project")
if name in CORE.data[KEY_NRF52][KEY_PRJ_CONF_OPTIONS]:
old_value = CORE.data[KEY_NRF52][KEY_PRJ_CONF_OPTIONS][name]
if old_value != value:
raise ValueError(
f"{name} alread set with value {old_value}, new value {value}"
)
CORE.data[KEY_NRF52][KEY_PRJ_CONF_OPTIONS][name] = value
@coroutine_with_priority(1000)
async def to_code(config):
cg.add(nrf52_ns.setup_preferences())
if config[CONF_BOARD] == "nrf52840":
# it has most generic GPIO mapping
config[CONF_BOARD] = "nrf52840_dk_adafruit"
if CORE.using_zephyr:
# this board works with https://github.com/adafruit/Adafruit_nRF52_Bootloader
config[CONF_BOARD] = "adafruit_itsybitsy_nrf52840"
elif CORE.using_arduino:
# it has most generic GPIO mapping
config[CONF_BOARD] = "nrf52840_dk_adafruit"
cg.add_platformio_option("board", config[CONF_BOARD])
cg.add_build_flag("-DUSE_NRF52")
cg.add_define("ESPHOME_BOARD", config[CONF_BOARD])
cg.add_define("ESPHOME_VARIANT", "nrf52840")
conf = config[CONF_FRAMEWORK]
cg.add_platformio_option("framework", "arduino")
cg.add_build_flag("-DUSE_ARDUINO")
cg.add_platformio_option(CONF_FRAMEWORK, conf[CONF_TYPE])
cg.add_platformio_option("platform", conf[CONF_PLATFORM_VERSION])
# make sure that firmware.zip is created
# for Adafruit_nRF52_Bootloader
cg.add_platformio_option("board_upload.protocol", "nrfutil")
cg.add_platformio_option("board_upload.use_1200bps_touch", "true")
cg.add_platformio_option("board_upload.require_upload_port", "true")
cg.add_platformio_option("board_upload.wait_for_upload_port", "true")
# watchdog
cg.add_build_flag("-DNRFX_WDT_ENABLED=1")
cg.add_build_flag("-DNRFX_WDT0_ENABLED=1")
cg.add_build_flag("-DNRFX_WDT_CONFIG_NO_IRQ=1")
# prevent setting up GPIO PINs
cg.add_platformio_option("board_build.variant", "nrf52840")
cg.add_platformio_option(
"board_build.variants_dir", os.path.dirname(os.path.realpath(__file__))
)
if CORE.using_arduino:
cg.add_build_flag("-DUSE_ARDUINO")
# watchdog
cg.add_build_flag("-DNRFX_WDT_ENABLED=1")
cg.add_build_flag("-DNRFX_WDT0_ENABLED=1")
cg.add_build_flag("-DNRFX_WDT_CONFIG_NO_IRQ=1")
# prevent setting up GPIO PINs
cg.add_platformio_option("board_build.variant", "nrf52840")
cg.add_platformio_option(
"board_build.variants_dir", os.path.dirname(os.path.realpath(__file__))
)
elif CORE.using_zephyr:
cg.add_build_flag("-DUSE_ZEPHYR")
cg.add_platformio_option(
"platform_packages", ["framework-zephyr@~2.30400.230914"]
)
# cpp support
add_zephyr_prj_conf_option("CONFIG_NEWLIB_LIBC", False)
add_zephyr_prj_conf_option("CONFIG_NEWLIB_LIBC_NANO", True)
add_zephyr_prj_conf_option("CONFIG_NEWLIB_LIBC_FLOAT_PRINTF", True)
add_zephyr_prj_conf_option("CONFIG_CPLUSPLUS", True)
add_zephyr_prj_conf_option("CONFIG_LIB_CPLUSPLUS", True)
# watchdog
add_zephyr_prj_conf_option("CONFIG_WATCHDOG", True)
add_zephyr_prj_conf_option("CONFIG_WDT_DISABLE_AT_BOOT", False)
else:
raise NotImplementedError
def _format_prj_conf_val(value: PrjConfValueType) -> str:
if isinstance(value, bool):
return "y" if value else "n"
if isinstance(value, int):
return str(value)
if isinstance(value, str):
return f'"{value}"'
raise ValueError
# Called by writer.py
def copy_files():
if CORE.using_zephyr:
want_opts = CORE.data[KEY_NRF52][KEY_PRJ_CONF_OPTIONS]
contents = (
"\n".join(
f"{name}={_format_prj_conf_val(value)}"
for name, value in sorted(want_opts.items())
)
+ "\n"
)
write_file_if_changed(CORE.relative_build_path("zephyr/prj.conf"), contents)

View File

@ -1,4 +1,5 @@
#pragma once
#include <cstdint>
namespace esphome {

View File

@ -1,11 +1,10 @@
#ifdef USE_NRF52
#ifdef USE_ARDUINO
#include <Arduino.h>
#include "Adafruit_nRFCrypto.h"
#include "nrfx_wdt.h"
namespace esphome {
void yield() { ::yield(); }
uint32_t millis() { return ::millis(); }
void delay(uint32_t ms) { ::delay(ms); }
@ -58,7 +57,7 @@ void nrf52GetMacAddr(uint8_t *mac)
mac[1] = src[4];
mac[0] = src[5] | 0xc0; // MSB high two bits get set elsewhere in the bluetooth stack
}
} // namespace esphome
#endif
#endif // USE_RP2040

View File

@ -0,0 +1,45 @@
#ifdef USE_NRF52
#ifdef USE_ZEPHYR
#include <zephyr/kernel.h>
namespace esphome {
void yield() { ::k_yield(); }
uint32_t millis() { return ::k_ticks_to_ms_floor32(k_uptime_ticks()); }
void delay(uint32_t ms) { ::k_msleep(ms); }
uint32_t micros() { return ::k_ticks_to_us_floor32(k_uptime_ticks()); }
void arch_init() {
// TODO
}
void arch_feed_wdt() {
// TODO
}
void nrf52GetMacAddr(uint8_t *mac)
{
const uint8_t *src = (const uint8_t *)NRF_FICR->DEVICEADDR;
mac[5] = src[0];
mac[4] = src[1];
mac[3] = src[2];
mac[2] = src[3];
mac[1] = src[4];
mac[0] = src[5] | 0xc0; // MSB high two bits get set elsewhere in the bluetooth stack
}
} // namespace esphome
void setup();
void loop();
int main(){
setup();
while(1) {
loop();
esphome::yield();
}
return 0;
}
#endif
#endif // USE_RP2040

View File

@ -1,10 +1,10 @@
#pragma once
#ifdef USE_NRF52
#include <Arduino.h>
#include "esphome/core/hal.h"
#ifdef USE_ZEPHYR
struct device;
#endif
namespace esphome {
namespace nrf52 {
@ -14,7 +14,7 @@ class NRF52GPIOPin : public InternalGPIOPin {
void set_inverted(bool inverted) { inverted_ = inverted; }
void set_flags(gpio::Flags flags) { flags_ = flags; }
void setup() override { pin_mode(flags_); }
void setup() override;
void pin_mode(gpio::Flags flags) override;
bool digital_read() override;
void digital_write(bool value) override;
@ -30,6 +30,9 @@ class NRF52GPIOPin : public InternalGPIOPin {
uint8_t pin_;
bool inverted_;
gpio::Flags flags_;
#ifdef USE_ZEPHYR
const device * gpio_ = nullptr;
#endif
};
} // namespace nrf52

View File

@ -1,9 +1,11 @@
#ifdef USE_NRF52
#ifdef USE_ARDUINO
#include "gpio.h"
#include "esphome/core/log.h"
#include <functional>
#include <vector>
#include "Arduino.h"
namespace esphome {
namespace nrf52 {
@ -77,6 +79,9 @@ void NRF52GPIOPin::attach_interrupt(void (*func)(void *), void *arg, gpio::Inter
irq_arg = arg;
attachInterrupt(pin_, pin_irq, mode);
}
void NRF52GPIOPin::setup() { pin_mode(flags_); }
void NRF52GPIOPin::pin_mode(gpio::Flags flags) {
pinMode(pin_, flags_to_mode(flags, pin_)); // NOLINT
}
@ -109,4 +114,5 @@ bool IRAM_ATTR ISRInternalGPIOPin::digital_read() {
} // namespace esphome
#endif
#endif // USE_NRF52

View File

@ -0,0 +1,143 @@
#ifdef USE_NRF52
#ifdef USE_ZEPHYR
#include "gpio.h"
#include "esphome/core/log.h"
// #include <functional>
// #include <vector>
// #include <device.h>
#include <zephyr/drivers/gpio.h>
namespace esphome {
namespace nrf52 {
static const char *const TAG = "nrf52";
static int flags_to_mode(gpio::Flags flags, uint8_t pin) {
if (flags == gpio::FLAG_INPUT) { // NOLINT(bugprone-branch-clone)
return GPIO_INPUT;
} else if (flags == gpio::FLAG_OUTPUT) {
return GPIO_OUTPUT;
} else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLUP)) {
return GPIO_INPUT | GPIO_PULL_UP;
} else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLDOWN)) {
return GPIO_INPUT | GPIO_PULL_DOWN;
} else if (flags == (gpio::FLAG_OUTPUT | gpio::FLAG_OPEN_DRAIN)) {
return GPIO_OUTPUT | GPIO_OPEN_DRAIN;
} else {
return GPIO_DISCONNECTED;
}
}
struct ISRPinArg {
uint8_t pin;
bool inverted;
};
//TODO implement
//TODO test
// void (*irq_cb)(void *);
// void* irq_arg;
// static void pin_irq(void){
// irq_cb(irq_arg);
// }
ISRInternalGPIOPin NRF52GPIOPin::to_isr() const {
auto *arg = new ISRPinArg{}; // NOLINT(cppcoreguidelines-owning-memory)
arg->pin = pin_;
arg->inverted = inverted_;
return ISRInternalGPIOPin((void *) arg);
}
void NRF52GPIOPin::attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const {
// uint32_t mode = ISR_DEFERRED;
// switch (type) {
// case gpio::INTERRUPT_RISING_EDGE:
// mode |= inverted_ ? FALLING : RISING;
// break;
// case gpio::INTERRUPT_FALLING_EDGE:
// mode |= inverted_ ? RISING : FALLING;
// break;
// case gpio::INTERRUPT_ANY_EDGE:
// mode |= CHANGE;
// break;
// default:
// return;
// }
// irq_cb = func;
// irq_arg = arg;
// attachInterrupt(pin_, pin_irq, mode);
}
void NRF52GPIOPin::setup() {
const struct device * gpio = nullptr;
if(pin_ < 32) {
#define GPIO0 DT_NODELABEL(gpio0)
#if DT_NODE_HAS_STATUS(GPIO0, okay)
gpio = DEVICE_DT_GET(GPIO0);
#else
#error "gpio0 is disabled"
#endif
} else {
#define GPIO1 DT_NODELABEL(gpio1)
#if DT_NODE_HAS_STATUS(GPIO1, okay)
gpio = DEVICE_DT_GET(GPIO1);
#else
#error "gpio1 is disabled"
#endif
}
//TODO why no ready??
// if (!device_is_ready(gpio)) {
gpio_ = gpio;
// } else {
// ESP_LOGE(TAG, "gpio %u is not ready.", pin_);
// }
gpio_ = gpio;
pin_mode(flags_);
}
void NRF52GPIOPin::pin_mode(gpio::Flags flags) {
if(nullptr == gpio_) {
return;
}
gpio_pin_configure(gpio_, pin_, flags_to_mode(flags, pin_));
}
std::string NRF52GPIOPin::dump_summary() const {
char buffer[32];
snprintf(buffer, sizeof(buffer), "GPIO%u", pin_);
return buffer;
}
bool NRF52GPIOPin::digital_read() {
if(nullptr == gpio_) {
//TODO invert ??
return false;
}
return bool(gpio_pin_get(gpio_, pin_)) != inverted_; // NOLINT
}
void NRF52GPIOPin::digital_write(bool value) {
if(nullptr == gpio_) {
return;
}
gpio_pin_set(gpio_, pin_, value != inverted_ ? 1 : 0);
}
void NRF52GPIOPin::detach_interrupt() const {
// detachInterrupt(pin_);
}
} // namespace nrf52
// using namespace nrf52;
// TODO seems to not work???
// bool IRAM_ATTR ISRInternalGPIOPin::digital_read() {
// auto *arg = reinterpret_cast<nrf52::ISRPinArg *>(arg_);
// return bool(digitalRead(arg->pin)) != arg->inverted; // NOLINT
// }
} // namespace esphome
#endif
#endif // USE_NRF52

View File

@ -0,0 +1,31 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.const import (
CONF_ID,
)
from esphome.components.nrf52 import add_zephyr_prj_conf_option
shell_ns = cg.esphome_ns.namespace("shell")
Shell = shell_ns.class_("Shell", cg.Component)
CONFIG_SCHEMA = cv.All(
cv.Schema(
{
cv.GenerateID(): cv.declare_id(Shell),
}
).extend(cv.COMPONENT_SCHEMA),
cv.only_with_zephyr,
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
add_zephyr_prj_conf_option("CONFIG_SHELL", True)
add_zephyr_prj_conf_option("CONFIG_SHELL_BACKENDS", True)
add_zephyr_prj_conf_option("CONFIG_SHELL_BACKEND_SERIAL", True)
add_zephyr_prj_conf_option("CONFIG_NET_SHELL_DYN_CMD_COMPLETION", True)
add_zephyr_prj_conf_option("CONFIG_NET_SHELL", True)
add_zephyr_prj_conf_option("CONFIG_GPIO_SHELL", True)
# add_zephyr_prj_conf_option("CONFIG_ADC_SHELL", True)
add_zephyr_prj_conf_option("CONFIG_NET_L2_BT_SHELL", True)

View File

@ -0,0 +1,21 @@
#include "shell.h"
#include <zephyr/usb/usb_device.h>
struct device;
namespace esphome {
namespace shell {
void Shell::setup() {
#if DT_NODE_HAS_COMPAT(DT_CHOSEN(zephyr_shell_uart), zephyr_cdc_acm_uart)
const device *dev = DEVICE_DT_GET(DT_CHOSEN(zephyr_shell_uart));
if (!device_is_ready(dev) || usb_enable(NULL)) {
return;
}
#else
#error "shell is not set in device tree"
#endif
}
} // namespace shell
} // namespace esphome

View File

@ -0,0 +1,10 @@
#pragma once
#include "esphome/core/component.h"
namespace esphome {
namespace shell {
class Shell : public Component {
void setup() override;
};
}
}

View File

@ -599,6 +599,7 @@ only_on_rp2040 = only_on(PLATFORM_RP2040)
only_on_nrf52 = only_on(PLATFORM_NRF52)
only_with_arduino = only_with_framework("arduino")
only_with_esp_idf = only_with_framework("esp-idf")
only_with_zephyr = only_with_framework("zephyr")
# Adapted from:

View File

@ -676,6 +676,10 @@ class EsphomeCore:
def using_esp_idf(self):
return self.target_framework == "esp-idf"
@property
def using_zephyr(self):
return self.target_framework == "zephyr"
def add_job(self, func, *args, **kwargs):
self.event_loop.add_job(func, *args, **kwargs)

View File

@ -10,6 +10,7 @@
#include <cstdarg>
#include <cstdio>
#include <cstring>
#include <strings.h>
#if defined(USE_ESP8266)
#include <osapi.h>
@ -48,7 +49,9 @@
#endif
#ifdef USE_NRF52
#ifdef USE_ARDUINO
#include "Adafruit_nRFCrypto.h"
#endif
#include "esphome/components/nrf52/core.h"
#endif
@ -246,7 +249,11 @@ bool random_bytes(uint8_t *data, size_t len) {
fclose(fp);
return true;
#elif defined(USE_NRF52)
#ifdef USE_ARDUINO
return nRFCrypto.Random.generate(data, len);
#elif USE_ZEPHYR
//TODO
#endif
#else
#error "No random source available for this configuration."
#endif
@ -518,13 +525,14 @@ void hsv_to_rgb(int hue, float saturation, float value, float &red, float &green
}
// System APIs
#if defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_HOST)
#if defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_HOST) || defined(USE_NRF52)
// ESP8266 doesn't have mutexes, but that shouldn't be an issue as it's single-core and non-preemptive OS.
Mutex::Mutex() {}
void Mutex::lock() {}
bool Mutex::try_lock() { return true; }
void Mutex::unlock() {}
#elif defined(USE_ESP32) || defined(USE_LIBRETINY) || defined(USE_NRF52)
//TODO
#elif defined(USE_ESP32) || defined(USE_LIBRETINY) /*|| defined(USE_NRF52)*/
Mutex::Mutex() { handle_ = xSemaphoreCreateMutex(); }
void Mutex::lock() { xSemaphoreTake(this->handle_, portMAX_DELAY); }
bool Mutex::try_lock() { return xSemaphoreTake(this->handle_, 0) == pdTRUE; }

View File

@ -21,8 +21,10 @@
#include <FreeRTOS.h>
#include <semphr.h>
#elif defined(USE_NRF52)
#ifdef USE_ARDUINO
#include <Arduino.h>
#endif
#endif
#define HOT __attribute__((hot))
#define ESPDEPRECATED(msg, when) __attribute__((deprecated(msg)))
@ -548,7 +550,8 @@ class Mutex {
Mutex &operator=(const Mutex &) = delete;
private:
#if defined(USE_ESP32) || defined(USE_LIBRETINY) || defined(USE_NRF52)
//TODO
#if defined(USE_ESP32) || defined(USE_LIBRETINY) /*|| defined(USE_NRF52)*/
SemaphoreHandle_t handle_;
#endif
};

View File

@ -310,6 +310,10 @@ def copy_src_tree():
CORE.relative_src_path("esphome.h"),
ESPHOME_H_FORMAT.format(include_s + '\n#include "pio_includes.h"'),
)
elif CORE.is_nrf52:
from esphome.components.nrf52 import copy_files
copy_files()
def generate_defines_h():