diff --git a/examples/wifinina/webserver/main.go b/examples/wifinina/webserver/main.go
new file mode 100644
index 0000000..f8bf65e
--- /dev/null
+++ b/examples/wifinina/webserver/main.go
@@ -0,0 +1,209 @@
+package main
+
+import (
+ "fmt"
+ "machine"
+ "strconv"
+ "time"
+
+ "tinygo.org/x/drivers/net/http"
+ "tinygo.org/x/drivers/wifinina"
+)
+
+// You can override the settings with the init() in another source code:
+//
+// func init() {
+// ssid = "your-ssid"
+// pass = "your-password"
+// }
+//
+// Or use -ldflags option on tinygo command to set at compile-time:
+//
+// tinygo flash ... -ldflags '-X "main.ssid=xxx" -X "main.pass=xxx"' ...
+//
+
+var (
+ ssid string
+ pass string
+)
+
+var led = machine.LED
+
+func main() {
+ led.Configure(machine.PinConfig{Mode: machine.PinOutput})
+
+ err := run()
+ for err != nil {
+ fmt.Printf("error: %s\r\n", err.Error())
+ time.Sleep(5 * time.Second)
+ }
+}
+
+func run() error {
+
+ spi := machine.NINA_SPI
+ spi.Configure(machine.SPIConfig{
+ Frequency: 8 * 1e6,
+ SDO: machine.NINA_SDO,
+ SDI: machine.NINA_SDI,
+ SCK: machine.NINA_SCK,
+ })
+
+ adaptor := wifinina.New(spi,
+ machine.NINA_CS,
+ machine.NINA_ACK,
+ machine.NINA_GPIO0,
+ machine.NINA_RESETN)
+ adaptor.Configure()
+
+ time.Sleep(2 * time.Second)
+ println("Connecting to " + ssid)
+ err := adaptor.ConnectToAccessPoint(ssid, pass, 10*time.Second)
+ if err != nil {
+ return err
+ }
+
+ println("Connected.")
+
+ time.Sleep(2 * time.Second)
+ ip, subnet, gateway, err := adaptor.GetIP()
+ if err != nil {
+ return err
+ }
+
+ fmt.Printf("IP Address : %s\r\n", ip)
+ fmt.Printf("Mask : %s\r\n", subnet)
+ fmt.Printf("Gateway : %s\r\n", gateway)
+
+ http.UseDriver(adaptor)
+
+ http.HandleFunc("/", root)
+ http.HandleFunc("/hello", hello)
+ http.HandleFunc("/cnt", cnt)
+ http.HandleFunc("/6", sixlines)
+ http.HandleFunc("/off", LED_OFF)
+ http.HandleFunc("/on", LED_ON)
+
+ return http.ListenAndServe(":80", nil)
+}
+
+func root(w http.ResponseWriter, r *http.Request) {
+ access := 1
+
+ cookie, err := r.Cookie("access")
+ if err != nil {
+ if err == http.ErrNoCookie {
+ cookie = &http.Cookie{
+ Name: "access",
+ Value: "1",
+ }
+ } else {
+ http.Error(w, fmt.Sprintf("%s", err.Error()), http.StatusBadRequest)
+ return
+ }
+ } else {
+ v, err := strconv.ParseInt(cookie.Value, 10, 0)
+ if err != nil {
+ http.Error(w, fmt.Sprintf("invalid cookie.Value : %s", cookie.Value), http.StatusBadRequest)
+ return
+ }
+ cookie.Value = fmt.Sprintf("%d", v+1)
+ access = int(v) + 1
+ }
+ http.SetCookie(w, cookie)
+ w.WriteHeader(http.StatusOK)
+
+ fmt.Fprintf(w, `
+
+
+ TinyGo HTTP Server
+
+
+
+ TinyGo HTTP Server
+
+
+ access: %d
+
+
+ /hello
+ /6
+
+
+ LED
+ /on
+ /off
+
+
+
+
+ /cnt
+ cnt:
+ incrCnt()
+
+
+
+
+ `, access)
+}
+
+func sixlines(w http.ResponseWriter, r *http.Request) {
+ // https://fukuno.jig.jp/3267
+ fmt.Fprint(w, ``)
+}
+
+func LED_ON(w http.ResponseWriter, r *http.Request) {
+ led.High()
+ w.Header().Set(`Content-Type`, `text/plain; charset=UTF-8`)
+ fmt.Fprintf(w, "led.High()")
+}
+
+func LED_OFF(w http.ResponseWriter, r *http.Request) {
+ led.Low()
+ w.Header().Set(`Content-Type`, `text/plain; charset=UTF-8`)
+ fmt.Fprintf(w, "led.Low()")
+}
+
+func hello(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set(`Content-Type`, `text/plain; charset=UTF-8`)
+ fmt.Fprintf(w, "hello")
+}
+
+var counter int
+
+func cnt(w http.ResponseWriter, r *http.Request) {
+ r.ParseForm()
+ if r.Method == "POST" {
+ c := r.Form.Get("cnt")
+ if c != "" {
+ i64, _ := strconv.ParseInt(c, 0, 0)
+ counter = int(i64)
+ }
+ }
+
+ w.Header().Set(`Content-Type`, `application/json`)
+ fmt.Fprintf(w, `{"cnt": %d}`, counter)
+}
+
+func message(msg string) {
+ println(msg, "\r")
+}
diff --git a/wifinina/http.go b/wifinina/http.go
new file mode 100644
index 0000000..e1358a0
--- /dev/null
+++ b/wifinina/http.go
@@ -0,0 +1,323 @@
+package wifinina
+
+import (
+ "bufio"
+ "bytes"
+ "fmt"
+ "io"
+ "strconv"
+ "strings"
+ "time"
+
+ "tinygo.org/x/drivers/net/http"
+)
+
+func (d *Device) ListenAndServe(addr string, handler http.Handler) error {
+
+ if handler == nil {
+ handler = http.DefaultServeMux
+ }
+
+ server := newServer(d, handler)
+
+ if err := server.listen(addr); err != nil {
+ return err
+ }
+
+ for {
+ client, err := server.accept()
+ if err != nil {
+ return err
+ }
+
+ if err := client.handleHTTP(); err != nil {
+ return err
+ }
+
+ if err = client.stop(); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+// Server stuff
+
+type server struct {
+ device *Device
+ handler http.Handler
+ sock uint8
+ clients map[uint8]*client // keyed by client sock
+}
+
+func newServer(device *Device, handler http.Handler) *server {
+ return &server{
+ device: device,
+ handler: handler,
+ sock: NoSocketAvail,
+ clients: make(map[uint8]*client),
+ }
+}
+
+func portFromAddr(addr string) (uint16, error) {
+ // ignore anything before ':' in address
+ i := strings.LastIndex(addr, ":")
+ if i < 0 {
+ return 0, fmt.Errorf("Missing ':' in address")
+ }
+ v, err := strconv.ParseUint(addr[i+1:], 10, 16)
+ if err != nil {
+ return 0, fmt.Errorf("Parsing address err: %s", err)
+ }
+ return uint16(v), nil
+}
+
+func (s *server) listen(addr string) error {
+ port, err := portFromAddr(addr)
+ if err != nil {
+ return fmt.Errorf("Getting port err: %s", err)
+ }
+
+ s.sock, err = s.device.GetSocket()
+ if err != nil {
+ return fmt.Errorf("Getting socket err: %s", err)
+ }
+ if s.sock == NoSocketAvail {
+ return fmt.Errorf("No socket available")
+ }
+
+ return s.device.StartServer(port, s.sock, ProtoModeTCP)
+}
+
+func (s *server) availServer(sock uint8) (uint8, error) {
+ d := s.device
+
+ d.mu.Lock()
+ defer d.mu.Unlock()
+
+ if err := d.waitForChipSelect(); err != nil {
+ d.spiChipDeselect()
+ return NoSocketAvail, fmt.Errorf("Wait for CS: %s", err)
+ }
+
+ l := d.sendCmd(CmdAvailDataTCP, 1)
+ l += d.sendParam8(sock, true)
+ d.addPadding(l)
+ d.spiChipDeselect()
+ _, err := d.waitRspCmd1(CmdAvailDataTCP)
+ if err != nil {
+ return NoSocketAvail, fmt.Errorf("Wait for Rsp: %s", err)
+ }
+ newsock, err := d.getUint16(2, err)
+ if err != nil {
+ return NoSocketAvail, fmt.Errorf("getUint16: %s", err)
+ }
+ return uint8(newsock >> 8), nil
+}
+
+func (s *server) accept() (*client, error) {
+
+ for {
+ sock, err := s.availServer(s.sock)
+ if err != nil {
+ return nil, fmt.Errorf("accept: %w", err)
+ }
+
+ if sock == NoSocketAvail {
+ continue
+ }
+
+ if client, ok := s.clients[sock]; ok {
+ return client, nil
+ }
+
+ client := newClient(s, sock)
+ s.clients[sock] = client
+
+ return client, nil
+ }
+}
+
+// client stuff
+
+type client struct {
+ server *server
+ device *Device
+ sock uint8
+
+ // HTTP request
+ req *http.Request
+ reqBuf bytes.Buffer
+ readBuf [256]byte
+
+ // HTTP response
+ res bytes.Buffer
+ resHdr http.Header
+ resBuf bytes.Buffer
+ statusCode int
+}
+
+func newClient(server *server, sock uint8) *client {
+ return &client{
+ server: server,
+ device: server.device,
+ sock: sock,
+ }
+}
+
+// client implements http.ResponseWriter interface
+
+func (c *client) Header() http.Header {
+ return c.resHdr
+}
+
+func (c *client) Write(b []byte) (int, error) {
+ return c.resBuf.Write(b)
+}
+
+func (c *client) WriteHeader(statusCode int) {
+ c.statusCode = statusCode
+}
+
+func (c *client) status() uint8 {
+ d := c.device
+
+ d.mu.Lock()
+ defer d.mu.Unlock()
+
+ if err := d.waitForChipSelect(); err != nil {
+ d.spiChipDeselect()
+ return 0
+ }
+
+ l := d.sendCmd(CmdGetClientStateTCP, 1)
+ l += d.sendParam8(c.sock, true)
+ d.addPadding(l)
+ d.spiChipDeselect()
+ _, err := d.waitRspCmd1(CmdGetClientStateTCP)
+ if err != nil {
+ return 0
+ }
+ status, err := d.getUint8(1, err)
+ if err != nil {
+ return 0
+ }
+ return status
+}
+
+func (c *client) stop() error {
+ if err := c.device.StopClient(c.sock); err != nil {
+ return err
+ }
+
+ // Wait max 5 secs for the connection to close
+ for i := 0; i < 50 && c.status() != TCPStateClosed; i++ {
+ time.Sleep(100 * time.Millisecond)
+ }
+
+ if c.status() != TCPStateClosed {
+ return fmt.Errorf("stop failed, client status %x", c.status())
+ }
+
+ return nil
+}
+
+func (c *client) handleHTTP() error {
+
+ c.reqBuf.Reset()
+ end := -1
+
+ // read the request
+
+ start := time.Now()
+ for {
+
+ // TODO use Server.ReadTimeout
+ if time.Since(start) > 1*time.Second {
+ return fmt.Errorf("ReadTimeout")
+ }
+
+ n, err := c.device.GetDataBuf(c.sock, c.readBuf[:])
+ if err != nil {
+ return fmt.Errorf("GetDataBuf: %s", err)
+ }
+ if n == 0 {
+ time.Sleep(1 * time.Millisecond)
+ continue
+ }
+
+ c.reqBuf.Write(c.readBuf[:n])
+ bytesSoFar := c.reqBuf.Bytes()
+
+ if end == -1 {
+
+ // search for blank line marking end-of-header
+ end = bytes.Index(bytesSoFar, []byte("\r\n\r\n"))
+ if end == -1 {
+ continue
+ }
+
+ // found end-of-header; parse header
+ end += len([]byte("\r\n\r\n"))
+ bufio := bufio.NewReader(bytes.NewReader(bytesSoFar[:end]))
+ c.req, err = http.ReadRequest(bufio)
+ if err != nil {
+ return err
+ }
+ }
+
+ v := c.req.Header.Get("Content-Length")
+ if v == "" {
+ // no body; we're done reading request
+ break
+ }
+
+ length, _ := strconv.Atoi(v)
+ if end+length == len(bytesSoFar) {
+ // got the whole body
+ body := bytes.NewReader(bytesSoFar[end:])
+ c.req.Body = io.NopCloser(body)
+ break
+ }
+
+ // continue reading request...
+ }
+
+ // build the response
+
+ c.statusCode = 200
+
+ c.resHdr = http.Header{}
+ c.resHdr.Add(`Content-Type`, `text/html; charset=UTF-8`)
+ c.resHdr.Add(`Connection`, `close`)
+
+ c.resBuf.Reset()
+ c.server.handler.ServeHTTP(c, c.req)
+
+ c.resHdr.Add(`Content-Length`, fmt.Sprintf("%d", c.resBuf.Len()))
+
+ c.res.Reset()
+ fmt.Fprintf(&c.res, "HTTP/1.1 %d %s\r\n", c.statusCode,
+ http.StatusText(c.statusCode))
+ if err := c.resHdr.Write(&c.res); err != nil {
+ return err
+ }
+ c.res.WriteByte(byte('\n'))
+ c.res.Write(c.resBuf.Bytes())
+
+ // send the response
+
+ written, err := c.device.SendData(c.res.Bytes(), c.sock)
+ if err != nil {
+ return err
+ }
+ if written == 0 {
+ return ErrDataNotWritten
+ }
+ if sent, _ := c.device.CheckDataSent(c.sock); !sent {
+ return ErrCheckDataError
+ }
+
+ return nil
+}