diff --git a/LICENSE b/LICENSE
index 2acc94fa820a741742e4b3966dd9b294edf7ac4a..0b37b2bc7ea68f12210e292d92ba947747f16c6b 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
 MIT License
 
-Copyright (c) 2021 Sergey Lyubka
+Copyright (c) 2021 Cesanta
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
diff --git a/examples/http/Makefile b/examples/http/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..5b0a0692e4f15332429d1faadc8abab13349b62b
--- /dev/null
+++ b/examples/http/Makefile
@@ -0,0 +1,3 @@
+SOURCES = main.c
+
+include ../../make/project.mk
diff --git a/examples/http/README.md b/examples/http/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..4325a1eb730bc29c97d3da3c6d5c7d9b1e71f539
--- /dev/null
+++ b/examples/http/README.md
@@ -0,0 +1,16 @@
+# BME280 sensor example
+
+Pinout is according to the C source: MOSI - 23, MISO - 19, CLK - 18, CS = 5:
+
+```c
+  struct spi bme280 = {.mosi = 23, .miso = 19, .clk = 18, .cs = {5, -1, -1}};
+```
+
+Build, flash and monitor:
+
+```
+Chip ID: 96, expecting 96
+Temp: 23.69
+Chip ID: 96, expecting 96
+Temp: 25.5
+```
diff --git a/examples/http/main.c b/examples/http/main.c
new file mode 100644
index 0000000000000000000000000000000000000000..25e11cba0f3ddddeb4ed1c4d54be354cd7c9f919
--- /dev/null
+++ b/examples/http/main.c
@@ -0,0 +1,63 @@
+#include <sdk.h>
+
+static unsigned readn(struct spi *spi, uint8_t reg, int n) {
+  unsigned rx = 0;
+  spi_begin(spi, 0);
+  spi_txn(spi, reg | 0x80);
+  for (int i = 0; i < n; i++) rx <<= 8, rx |= spi_txn(spi, 0);
+  spi_end(spi, 0);
+  return rx;
+}
+
+static void write8(struct spi *spi, uint8_t reg, uint8_t val) {
+  spi_begin(spi, 0);
+  spi_txn(spi, (uint8_t)(reg & ~0x80));
+  spi_txn(spi, val);
+  spi_end(spi, 0);
+}
+
+uint16_t swap16(uint16_t val) {
+  uint8_t data[2] = {0, 0};
+  memcpy(&data, &val, sizeof(data));
+  return (uint16_t)((uint16_t) data[1] | (((uint16_t) data[0]) << 8));
+}
+
+// Taken from the BME280 datasheet, 4.2.3
+int32_t read_temp(struct spi *spi) {
+  int32_t t = (int32_t)(readn(spi, 0xfa, 3) >> 4);  // Read temperature reg
+
+  uint16_t c1 = (uint16_t) readn(spi, 0x88, 2);  // dig_T1
+  uint16_t c2 = (uint16_t) readn(spi, 0x8a, 2);  // dig_T2
+  uint16_t c3 = (uint16_t) readn(spi, 0x8c, 2);  // dig_T3
+
+  int32_t t1 = (int32_t) swap16(c1);
+  int32_t t2 = (int32_t)(int16_t) swap16(c2);
+  int32_t t3 = (int32_t)(int16_t) swap16(c3);
+
+  int32_t var1 = ((((t >> 3) - (t1 << 1))) * t2) >> 11;
+  int32_t var2 = (((((t >> 4) - t1) * ((t >> 4) - t1)) >> 12) * t3) >> 14;
+
+  return ((var1 + var2) * 5 + 128) >> 8;
+}
+
+int main(void) {
+  wdt_disable();
+
+  struct spi bme280 = {.mosi = 23, .miso = 19, .clk = 18, .cs = {5, -1, -1}};
+  spi_init(&bme280);
+
+  write8(&bme280, 0xe0, 0xb6);          // Soft reset
+  spin(999999);                         // Wait until reset
+  write8(&bme280, 0xf4, 0);             // REG_CONTROL, MODE_SLEEP
+  write8(&bme280, 0xf5, (3 << 5));      // REG_CONFIG, filter = off, 20ms
+  write8(&bme280, 0xf4, (1 << 5) | 3);  // REG_CONFIG, MODE_NORMAL
+
+  for (;;) {
+    sdk_log("Chip ID: %d, expecting 96\n", readn(&bme280, 0xd0, 1));
+    int temp = read_temp(&bme280);
+    sdk_log("Temp: %d.%d\n", temp / 100, temp % 100);
+    spin(9999999);
+  }
+
+  return 0;
+}
diff --git a/include/cnip.h b/include/cnip.h
new file mode 100644
index 0000000000000000000000000000000000000000..eefeb647b3cfe5455a6f5bc7a7040798d721726b
--- /dev/null
+++ b/include/cnip.h
@@ -0,0 +1,32 @@
+// Copyright (c) 2012-2021 Charles Lohr, MIT license
+// All rights reserved
+
+#pragma once
+
+#include <stddef.h>
+
+// Low level (hardware) API
+void cn_mac_in(void *buf, size_t len);   // Implemented by CNIP
+void cn_mac_out(void *buf, size_t len);  // Must be implemented by user
+void cn_poll(unsigned long now_ms);      // Called periodically or on event
+
+// High level (user) API
+// This is a user-defined callback function that gets called on various events
+struct cn_conn;                                     // A connection - opaque
+typedef void (*cn_fn_t)(struct cn_conn *, int ev);  // User function
+enum {
+  CN_EV_ERROR,    // Error! Use cn_get_error() to get the reason
+  CN_EV_CONNECT,  // Client connection established
+  CN_EV_ACCEPT,   // New server connection accepted
+  CN_EV_READ,     // More data arrived, use cn_rx() to access data
+  CN_EV_WRITE,    // Data has been sent to the network
+  CN_EV_CLOSE,    // Connection is about to vanish
+};
+// API for creating new connections
+struct cn_conn *cn_tcp_listen(const char *url, cn_fn_t handler_fn);
+struct cn_conn *cn_tcp_connect(const char *url, cn_fn_t handler_fn);
+// API for use from inside a user callback function
+void cn_rx(struct cn_conn *, void **buf, size_t *len);   // Get recv data
+void cn_tx(struct cn_conn *, void **buf, size_t *len);   // Get sent data
+void cn_send(struct cn_conn *, void *buf, size_t len);   // Send more data
+void cn_free(struct cn_conn *, size_t off, size_t len);  // Discard recv data
diff --git a/src/cnip.c b/src/cnip.c
new file mode 100644
index 0000000000000000000000000000000000000000..3c13cde0ce2757c6fd4da84cbcd9dc632e3bedd8
--- /dev/null
+++ b/src/cnip.c
@@ -0,0 +1,13 @@
+// Copyright (c) 2012-2021 Charles Lohr, MIT license
+// All rights reserved
+
+#include <cnip.h>
+
+void cn_mac_in(void *buf, size_t len) {
+  (void) buf;
+  (void) len;
+}
+
+void cn_poll(unsigned long now_ms) {
+  (void) now_ms;
+}