Code Appendix

/**

* Adapted from:

* Copyright (c) 2022 Raspberry Pi (Trading) Ltd.

* SPDX-License-Identifier: BSD-3-Clause

* the tcp/ip routines are mostly just copied into this code from

* https://github.com/raspberrypi/pico-examples/blob/master/pico_w/wifi/tcp_server/picow_tcp_server.c

* The html and response parser was modified.

*

* Integrate LWIP with protothreads AND with VGA driver

*  This required changing the VGA DMA channels to use the "claim" protocol

*  rather than hard code actual channel numbers.

*  Also, the LWIP uses PIO1, so the VGA driver must run in PIO0

*

* Threads:

* --blink cyw43 LED at a few Hz

* -- serial for debugging and commands

* -- graphics thread to write LED status of server

* *** All tcp is triggered from ISR started by cyw43 (not a thread)

*

* the VGA must be cpnnected for display!

*

*  access_point_ssid = "picow_test"

   password = "password"

   access_point_IP4_ADDR(192.168.4.1) p

   attach to the access point point, then browse the IP address

*

* MAIN:

   starts serial

   starts VGA

   network init

   -- start the cyw43 in access point mode -- assigns ssid, password

   -- assign the IP address of the server

      I think this starts the ISR which handles all transactions

   -- start the DHCP server

   -- start the DNS server

   -- start the TCP server

      define data structures, bind to port 80

      start tcp_listen

      does tcp_server_accept

           sets up tcp_poll

   starts the threader with core1 running the VGA,

   and core0 running the tcpip and serial

   -- receiving a GET request -- happens from ISR callback

       triggers content parsing and analysis

       generate response in the form of header and html

       do some error checking for too much space used

       tcp_write the html response

*/

/*

* Hardware Connections

*  - RP2040 w GPIO 3 ---> Analog output pin

*  - RP2040 w 3.3v ---> Capacitive Soil Moisture Sensor VCC

*  - RP2040 w GND ---> Capacitive Soil Moisture Sensor GND

* Serial Connections

* - GPIO 0 ---> UART RX (white)

* - GPIO 1 ---> UART TX (green)

* - RP2040 w GND ---> UART GND

*/

// ==========================================

// === VGA graphics library

// ==========================================

#include "vga256_graphics.h"

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <math.h>

#include "pico/stdlib.h"

#include "hardware/pio.h"

#include "hardware/dma.h"

// =====================================

// network includes

#include <string.h>

#include <time.h>

#include <pico/multicore.h>

#include "hardware/sync.h"

#include "hardware/uart.h"

#include "hardware/rtc.h"

#include "hardware/adc.h"

#include "pico/util/datetime.h"

#include "pico/stdlib.h"

#include "pico/cyw43_arch.h"

#include "lwip/dns.h"

#include "lwip/pbuf.h"

#include "lwip/tcp.h"

//========================================

// protothreads

#include "pt_cornell_rp2040_v1_1_2.h"

// log the access point LED for graphics thread to use

int access_led_state;

// int temp_sensor ; // turning on the temp_sensor kills the network!

//  user state variables

int user_number;

int test_field;

// gpio2 % intensity

int test2_field = 25;

int ec_field;

int color, new_color = true;

float temp;

// =======================================

// access point setup

// copied from pico_examples git

// html was modified and expanded

// =======================================

// add a 'printf' to the line below to dump

// internal state info  e.g.:

// #define DEBUG_printf  printf

#define DEBUG_printf

//

#include "dhcpserver/dhcpserver.h"

#include "dnsserver/dnsserver.h"

//

#define TCP_PORT 80

#define POLL_TIME_S 5

#define HTTP_GET "GET"

#define HTTP_RESPONSE_HEADERS "HTTP/1.1 %d OK\nContent-Length: %d\nContent-Type: text/html;\

                              charset=utf-8\nConnection: close\n\n"

// the html. format specs represent server state to be resolved at send time

// this is one part I changed and expanded from the demo code

// the one-line javascript is hacked slightly from

// https://stackoverflow.com/questions/74320571/inserting-oninput-to-an-input-field

// and allows the numerical value of the slider to change as yuo move the slider

#define LED_TEST_BODY \

   "<html><body><b>Page from PicoW -- ece4760</b>\

   <p>Onboard Led is %s and Sensor reads: %f\

   \

   <form>\

   \

   <label for=\"ledsel\">Set LED:</label>\

   <select name=\"led\" id=\"ledsel\" size=\"2\"  onchange=\"submit();\">\

   <option value=\"0\" %s>LED 0ff</option>\

   <option value=\"1\" %s>LED on </option>\

   </select>\

   \

   <svg width=\"20\" height=\"20\">\

   <circle cx=\"10\" cy=\"10\" r=\"10\" stroke=\"green\" stroke-width=\"1\" fill=%s />\

   </svg><p>\

   \

   <label for=\"test\">number:</label>\

   <input type=\"number\" id=\"test\" name=\"test\" value=%d min=\"0\" max=\"999\">\

   <input type=\"submit\">\

   entered:<b><font color=\"RED\"> %03d </font></b>\

   <meter value=%d min=\"0\" max=\"999\"></meter><br>\

   <p>\

   <label for=\"test2\">GPIO2 PWM intensity:</label>\

   <input type=\"range\" id=\"test2\" value=%d min=\"1\" max=\"99\" name=\"test2\"\

   oninput=\"this.nextElementSibling.value = this.value;\" onchange=\"submit()\">\

   <output>%d%%</output>\

   \

   </form>\

   <script> \

   setTimeout(function(){ location.reload(); }, 10000); \

   </script>\

   <p><a href=http://192.168.4.1/plantparenthood> PlantParenthood</a>\

   </body></html>"

// this page uses javascript submit() method so a button is not necessary

// it also tests <svg> graphics to draw a dynamic LED icon

// <label for=\"led_control1\">led on/off:</label><br>\

//   <input type=\"text\" id=\"led_control1\" name=\"led\" value=%d><br>\

#define NEW_PAGE_BODY \

   "<html><head>\

   <meta charset=\"UTF-8\">\

   <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\

   <title>Reload Part of HTML</title>\

   </head>\

   <body>\

   \

   <div id=\"content\">\

       <p id=\"reloadMe\">This content can be reloaded. %d </p>\

   </div>\

   <div id=\"content2\">\

       <p id=\"reloadMeNOT\">This content cannot be reloaded.</p>\

   </div>\

   \

   <button onclick=\"reloadContent()\">Reload Content</button>\

   \

   <script>\

       function reloadContent() {\

       fetch(http://192.168.4.1/newpage)\

           .then(response => response.text())\

           .then(data => {\

           document.getElementById('reloadMe').innerHTML = data;\

           })\

           .catch(error => console.error('Error:', error));\

       }\

   </script>\

   </body></html>"

// light-green

#define RPEE2040_BODY \

   "<html><body style=\"background-color: #4EB182;\">\

   <p style=\"color: white; font-family: Arial; font-size: 90px; text-align: center; padding: 700px 0\">\

   Welcome to\

   <br> R<strong>PEE</strong>2040\

   </p>\

   <script>\

   setTimeout(function(){ location.href = \"/plantparenthood\"; }, 3000); \

   </script>\

   </body></html>"

// rgb(230, 211, 190)

// rgb(252, 236, 202)

// rgb(179, 136, 105)

#define PLANTPARENTHOOD_BODY \

   "<html><body style=\"background-color: #4EAFB1;\">\

   <style>\

   form {color: #dfe2df; font-family: Arial; font-size: 90px; text-align: center}\

   input, textarea {color: #3A7781; font-family: Arial; font-size: 70px; text-align: center}\

   button {background-color: #c8e6e7; color: #3A7781; font-family: Arial; font-size: 50px; text-align: center}\

   meter {color: #e4e6e4; font-family: Arial; font-size: 120px; text-align: center}\

   h1 {color: #dfe2df; font-family: Arial; font-size: 90px; text-align: center}\

   body {color: #dfe2df; font-family: Arial; font-size: 70px; text-align: center}\

   h2 {color: white; font-family: Arial; font-size: 70px; text-align: center}\

   </style>\

   <h1>\

   Plant Parenthood<br><br>\

   </h1>\

   \

   <meter value=%f min=\"0\" max=\"100\" low=\"25\" high=\"50\" optimum=\"51\"> </meter><br><br>\

   \

   <body> Please input EC Sensor Reading in micromhos <br><br></body>\

   <form>\

   <input type=\"number\" id=\"EC\" name=\"EC\" value=%d min=\"0\" max=\"2000\">\

   <input type=\"hidden\">\

       <button> Submit </button>\

   </input> \

   <br><br><br> \

   </form>\

   \

   <h2>\

   %s\

   </h2>\

   <script>\

   setTimeout(function(){ location.reload(); }, 5000); \

   </script>\

   </body></html>"

// <label for=\"EC\">Moisture Value: %f</label> <br><br>\

// the names in these parse templates have to match the names

// in the <form> above: used to find data in the 'query string'

// part of the URL requested by the browser

// <p><input type=\"submit\">\

#define LED_PARAM "led=%d"

#define TEST_FMT "test=%d"

#define TEST2_FMT "test2=%d"

#define EC_FMT "EC=%d"

#define COLOR_PARAM "color=%d"

// web page redirect name

#define LED_TEST "/ledtest"

#define NEW_PAGE "/newpage"

#define RPEE2040 "/rpee2040"

#define PLANTPARENTHOOD "/plantparenthood"

// The LED is on cy243 gpio 0 NOT the rp2040

#define LED_GPIO 0

// the redirect using the IP adddress specified in MAIN

#define HTTP_RESPONSE_REDIRECT "HTTP/1.1 302 Redirect\nLocation: http://%s" RPEE2040 "\n\n"

// GPIO PINS

#define MOISTURE_PIN 27

static float moisture_value = 200.0;

static float wet_reading = 0.95;

static float dry_reading = 1.7;

static int ec_value = 200;

static char message[100];

static int count = 1;

// ===========================

// connect state structures

// ===========================

typedef struct TCP_SERVER_T_

{

   struct tcp_pcb *server_pcb;

   bool complete;

   ip_addr_t gw;

   async_context_t *context;

} TCP_SERVER_T;

typedef struct TCP_CONNECT_STATE_T_

{

   struct tcp_pcb *pcb;

   int sent_len;

   char headers[512]; // 128

   char result[2048]; // 256

   int header_len;

   int result_len;

   ip_addr_t *gw;

} TCP_CONNECT_STATE_T;

// =====================================

// called at end of 'tcp_server_sent'

// and several times in 'tcp_server_recv'

// and other places that open a conneciton

static err_t tcp_close_client_connection(TCP_CONNECT_STATE_T *con_state, struct tcp_pcb *client_pcb, err_t close_err)

{

   if (client_pcb)

   {

       assert(con_state && con_state->pcb == client_pcb);

       tcp_arg(client_pcb, NULL);

       tcp_poll(client_pcb, NULL, 0);

       tcp_sent(client_pcb, NULL);

       tcp_recv(client_pcb, NULL);

       tcp_err(client_pcb, NULL);

       err_t err = tcp_close(client_pcb);

       if (err != ERR_OK)

       {

           DEBUG_printf("close failed %d, calling abort\n", err);

           tcp_abort(client_pcb);

           close_err = ERR_ABRT;

       }

       if (con_state)

       {

           free(con_state);

       }

   }

   return close_err;

}

// =======================================

// not called in this pgm, since code never exits

/*

static void tcp_server_close(TCP_SERVER_T *state) {

   if (state->server_pcb) {

       tcp_arg(state->server_pcb, NULL);

       tcp_close(state->server_pcb);

       state->server_pcb = NULL;

   }

}

*/

// ========================================

//

static err_t tcp_server_sent(void *arg, struct tcp_pcb *pcb, u16_t len)

{

   TCP_CONNECT_STATE_T *con_state = (TCP_CONNECT_STATE_T *)arg;

   DEBUG_printf("tcp_server_sent %u\n", len);

   con_state->sent_len += len;

   if (con_state->sent_len >= con_state->header_len + con_state->result_len)

   {

       DEBUG_printf("all done\n");

       return tcp_close_client_connection(con_state, pcb, ERR_OK);

   }

   return ERR_OK;

}

// ========================================

// This routine actially parses the GET response to figure out what

// the browwer user asked for on the web page.

// This is another part I chnaged from the example

// to support more general parsing of several data items

static int test_server_content(const char *request, const char *params, char *result, size_t max_result_len)

{

   int len = 0;

   // three data items sent in this example, but could be many more

   static char param1[16], param2[16], param3[16];

   static char *token;

   // is it the ledtest page?

   if (strncmp(request, LED_TEST, sizeof(LED_TEST) - 1) == 0)

   {

       // Get the curent state of the led

       // not used here, except as backup if web form fails

       // bool value;

       // cyw43_gpio_get(&cyw43_state, LED_GPIO, &value);

       // int led_state = value;

       // See what data the user asked for:

       // the params string is trimmed by the receive funciton below to have

       // only user-supplied data

       // general data format is

       // http://192.168.4.1/ledtest?name1=value1&name2=data2&name3=data3 and etc

       // by the time the string gets here, the trimmed version is

       // name1=value1&name2=data2&name3=data3

       if (params)

       {

           // parse the params using '&' delimeter

           token = strtok((char *)params, "& ");

           strcpy(param1, token);

           token = strtok(NULL, "&  ");

           strcpy(param2, token);

           token = strtok(NULL, "&  ");

           strcpy(param3, token);

           // now get the actual numbers

           sscanf(param1, LED_PARAM, &access_led_state);

           // force to binary value and set LED state

           access_led_state = access_led_state > 0;

           cyw43_gpio_set(&cyw43_state, 0, access_led_state);

           // set global to signal graphics thread

           // the second and third parameter begining is delimited by an '&'

           // these tell the graphics thread what numbers to draw

           sscanf(param2, TEST_FMT, &test_field);

           sscanf(param3, TEST2_FMT, &test2_field);

       }

       // Generate result web page using the above data

       // by filling in the format fields in the http string.

       // test2_field occcurs twice: once for the slider position

       // and once to update the numwerical value interactively.

       len = snprintf(result, max_result_len, LED_TEST_BODY, access_led_state ? "ON" : "OFF", moisture_value,

                      access_led_state ? " " : "selected", access_led_state ? "selected" : " ",

                      access_led_state ? "LawnGreen" : "black",

                      test_field, test_field, test_field, test2_field, test2_field);

   }

   // // is it the new page

   if (strncmp(request, NEW_PAGE, sizeof(NEW_PAGE) - 1) == 0)

   {

       len = snprintf(result, max_result_len, NEW_PAGE_BODY, count);

   }

   // is it the welcome page?

   else if (strncmp(request, RPEE2040, sizeof(RPEE2040) - 1) == 0)

   {

       len = snprintf(result, max_result_len, RPEE2040_BODY);

   }

   // is it the main plant parenthood page?

   else if (strncmp(request, PLANTPARENTHOOD, sizeof(PLANTPARENTHOOD) - 1) == 0)

   {

       int m = 50;

       if (params)

       {

           // parse the params using '&' delimeter

           token = strtok((char *)params, "&  ");

           strcpy(param2, token);

           // the second parameter beginning is delimited by an '&'

           // these tell the graphics thread what numbers to draw

           sscanf(param2, EC_FMT, &ec_field);

           // if EC reading is in millimhos, set units to micromhos

           if (ec_field < 20)

           {

               ec_field = ec_field * 100;

           }

           // set global EC value

           ec_value = ec_field;

       }

       len = snprintf(result, max_result_len, PLANTPARENTHOOD_BODY, moisture_value, ec_field, message);

   }

   return len;

}

// ========================================

// receives brwoser url request, parses out the query string

// to send to the funciton just above, does some error checking

// then sends the html back to the browser

err_t tcp_server_recv(void *arg, struct tcp_pcb *pcb, struct pbuf *p, err_t err)

{

   TCP_CONNECT_STATE_T *con_state = (TCP_CONNECT_STATE_T *)arg;

   if (!p)

   {

       DEBUG_printf("connection closed\n");

       return tcp_close_client_connection(con_state, pcb, ERR_OK);

   }

   assert(con_state && con_state->pcb == pcb);

   if (p->tot_len > 0)

   {

       DEBUG_printf("tcp_server_recv %d err %d\n", p->tot_len, err);

#if 0

       for (struct pbuf *q = p; q != NULL; q = q->next) {

           DEBUG_printf("in: %.*s\n", q->len, q->payload);

       }

#endif

       // Copy the request into the buffer

       pbuf_copy_partial(p, con_state->headers, p->tot_len > sizeof(con_state->headers) - 1 ? sizeof(con_state->headers) - 1 : p->tot_len, 0);

       // Handle GET request

       if (strncmp(HTTP_GET, con_state->headers, sizeof(HTTP_GET) - 1) == 0)

       {

           char *request = con_state->headers + sizeof(HTTP_GET); // + space

           char *result = con_state->result;

           // find the params start in the request (indicated by '?')

           char *params = strchr(request, '?');

           // find the param end (i think) and force a null

           if (params)

           {

               if (*params)

               {

                   char *space = strchr(request, ' ');

                   *params++ = 0;

                   if (space)

                   {

                       *space = 0;

                   }

               }

               else

               {

                   params = NULL;

               }

           }

           // Generate content html

           con_state->result_len = test_server_content(request, params, con_state->result, sizeof(con_state->result));

           DEBUG_printf("Request: %s?%s\n", request, params);

           DEBUG_printf("Result: %d\n", con_state->result_len);

           // Check we had enough buffer space

           if (con_state->result_len > sizeof(con_state->result) - 1)

           {

               DEBUG_printf("Too much result data %d\n", con_state->result_len);

               return tcp_close_client_connection(con_state, pcb, ERR_CLSD);

           }

           // Generate web page

           if (con_state->result_len > 0)

           {

               con_state->header_len = snprintf(con_state->headers, sizeof(con_state->headers), HTTP_RESPONSE_HEADERS,

                                                200, con_state->result_len);

               if (con_state->header_len > sizeof(con_state->headers) - 1)

               {

                   DEBUG_printf("Too much header data %d\n", con_state->header_len);

                   return tcp_close_client_connection(con_state, pcb, ERR_CLSD);

               }

           }

           else

           {

               // Send redirect

               con_state->header_len = snprintf(con_state->headers, sizeof(con_state->headers), HTTP_RESPONSE_REDIRECT,

                                                ipaddr_ntoa(con_state->gw));

               DEBUG_printf("Sending redirect %s", con_state->headers);

           }

           // Send the headers to the client

           con_state->sent_len = 0;

           err_t err = tcp_write(pcb, con_state->headers, con_state->header_len, 0);

           if (err != ERR_OK)

           {

               DEBUG_printf("failed to write header data %d\n", err);

               return tcp_close_client_connection(con_state, pcb, err);

           }

           // Send the body to the client

           if (con_state->result_len)

           {

               err = tcp_write(pcb, con_state->result, con_state->result_len, 0);

               if (err != ERR_OK)

               {

                   DEBUG_printf("failed to write result data %d\n", err);

                   return tcp_close_client_connection(con_state, pcb, err);

               }

           }

       }

       tcp_recved(pcb, p->tot_len);

   }

   pbuf_free(p);

   return ERR_OK;

}

// ========================================

//

static err_t tcp_server_poll(void *arg, struct tcp_pcb *pcb)

{

   TCP_CONNECT_STATE_T *con_state = (TCP_CONNECT_STATE_T *)arg;

   DEBUG_printf("tcp_server_poll_fn\n");

   return tcp_close_client_connection(con_state, pcb, ERR_OK); // Just disconnect clent?

}

// ========================================

//

static void tcp_server_err(void *arg, err_t err)

{

   TCP_CONNECT_STATE_T *con_state = (TCP_CONNECT_STATE_T *)arg;

   if (err != ERR_ABRT)

   {

       DEBUG_printf("tcp_client_err_fn %d\n", err);

       tcp_close_client_connection(con_state, con_state->pcb, err);

   }

}

// ========================================

//

static err_t tcp_server_accept(void *arg, struct tcp_pcb *client_pcb, err_t err)

{

   TCP_SERVER_T *state = (TCP_SERVER_T *)arg;

   if (err != ERR_OK || client_pcb == NULL)

   {

       DEBUG_printf("failure in accept\n");

       return ERR_VAL;

   }

   DEBUG_printf("client connected\n");

   // Create the state for the connection

   TCP_CONNECT_STATE_T *con_state = calloc(1, sizeof(TCP_CONNECT_STATE_T));

   if (!con_state)

   {

       DEBUG_printf("failed to allocate connect state\n");

       return ERR_MEM;

   }

   con_state->pcb = client_pcb; // for checking

   con_state->gw = &state->gw;

   // setup connection to client

   tcp_arg(client_pcb, con_state);

   tcp_sent(client_pcb, tcp_server_sent);

   tcp_recv(client_pcb, tcp_server_recv);

   tcp_poll(client_pcb, tcp_server_poll, POLL_TIME_S * 2);

   tcp_err(client_pcb, tcp_server_err);

   return ERR_OK;

}

// =======================================

// called from MAIN to start tcp server

// =======================================

static bool tcp_server_open(void *arg, const char *ap_name)

{

   TCP_SERVER_T *state = (TCP_SERVER_T *)arg;

   DEBUG_printf("starting server on port %d\n", TCP_PORT);

   struct tcp_pcb *pcb = tcp_new_ip_type(IPADDR_TYPE_ANY);

   if (!pcb)

   {

       DEBUG_printf("failed to create pcb\n");

       return false;

   }

   err_t err = tcp_bind(pcb, IP_ANY_TYPE, TCP_PORT);

   if (err)

   {

       DEBUG_printf("failed to bind to port %d\n", TCP_PORT);

       return false;

   }

   state->server_pcb = tcp_listen_with_backlog(pcb, 1);

   if (!state->server_pcb)

   {

       DEBUG_printf("failed to listen\n");

       if (pcb)

       {

           tcp_close(pcb);

       }

       return false;

   }

   tcp_arg(state->server_pcb, state);

   tcp_accept(state->server_pcb, tcp_server_accept);

   printf("Try connecting to '%s' with password 'password'\n", ap_name);

   return true;

}

//=========================================

// never truning off wifi in my code

/*

// This "worker" function is called to safely perform work when instructed by key_pressed_func

void key_pressed_worker_func(async_context_t *context, async_when_pending_worker_t *worker) {

   //>>>>assert(worker->user_data);

   printf("Disabling wifi\n");

   cyw43_arch_disable_ap_mode();

   //>>>>((TCP_SERVER_T*)(worker->user_data))->complete = true;

}

*/

/*

static async_when_pending_worker_t key_pressed_worker = {

       .do_work = key_pressed_worker_func

};

*/

void key_pressed_func(void *param)

{

   assert(param);

   int key = getchar_timeout_us(0); // get any pending key press but don't wait

   if (key == 'd' || key == 'D')

   {

       // We are probably in irq context so call wifi in a "worker"

       //>>>> async_context_set_work_pending(((TCP_SERVER_T*)param)->context, &key_pressed_worker);

   }

}

// ==================================================

// access thread protothread

// ==================================================

static PT_THREAD(protothread_access(struct pt *pt))

{

   PT_BEGIN(pt);

   // if (!state)

   //    return;

   while (true)

   {

       //

       PT_YIELD_usec(100000);

   }

   PT_END(pt);

}

// ==================================================

// gpio2 intensity

// this is really just a test of multitasking

// compatability with LWIP

// ==================================================

static PT_THREAD(protothread_toggle_gpio2(struct pt *pt))

{

   PT_BEGIN(pt);

   //

   // data structure for interval timer

   PT_INTERVAL_INIT();

   // set up LED gpio 2

   gpio_init(2);

   gpio_set_dir(2, GPIO_OUT);

   gpio_put(4, true);

   adc_init();

   adc_gpio_init(MOISTURE_PIN);

   adc_select_input(1);

   while (1)

   {

       // cheesy PWM

       PT_YIELD_INTERVAL(10000);

       //

       gpio_put(2, true);

       // intensity % range 0-99 works

       PT_YIELD_usec(test2_field * 100);

       gpio_put(2, false);

       // get moisture reading

       adc_select_input(1);

       const float conversion_factor = 3.3f / (1 << 12);

       moisture_value = (float)adc_read() * conversion_factor;

       moisture_value = 100 - ((moisture_value - wet_reading) / (dry_reading - wet_reading)) * 100;

       if (moisture_value < 5.0)

       {

           moisture_value = 5.0;

       }

       if (moisture_value > 50.0)

       {

           strncpy(message, "Your plant is well-fed", 100);

       }

       else if (ec_value <= 500)

       {

           strncpy(message, "Your plant is hungry. Add fertilizer until green", 100);

       }

       else

       {

           strncpy(message, "Your plant is thirsty. Add water until green", 100);

       }

       // strncpy(message, "Your plant is thirsty. Add water until green", 100);

       sprintf(pt_serial_out_buffer, "\n%f\n", moisture_value);

       // sprintf(pt_serial_out_buffer, "hey");

       serial_write;

       count = count + 1;

       // sprintf(pt_serial_out_buffer, "%f", 200.0);

       // serial_write;

       // moisture_value = 700;

       // NEVER exit while

   } // END WHILE(1)

   PT_END(pt);

} // blink thread

// ==================================================

// user input

// ==================================================

static PT_THREAD(protothread_serial(struct pt *pt))

{

   PT_BEGIN(pt);

   static char cmd[16], arg1[16], arg2[16], arg3[16], arg4[16], arg5[16], arg6[16];

   static char *token;

   //

   printf("\nType 'help' for commands\n\r");

   while (1)

   {

       // pause for 0.1 to let another thread print a message

       PT_YIELD_usec(100000);

       // print prompt

       sprintf(pt_serial_out_buffer, "cmd> ");

       // sprintf(pt_serial_out_buffer, "%f", &moisture_value);

       // spawn a thread to do the non-blocking write

       serial_write;

       // spawn a thread to do the non-blocking serial read

       serial_read;

       // sscanf(pt_serial_in_buffer, "%s %f %f %f %f", arg0, &arg1, arg2, arg3, arg4) ;

       //  tokenize

       token = strtok(pt_serial_in_buffer, "  ");

       strcpy(cmd, token);

       token = strtok(NULL, "  ");

       strcpy(arg1, token);

       token = strtok(NULL, "  ");

       strcpy(arg2, token);

       token = strtok(NULL, "  ");

       strcpy(arg3, token);

       token = strtok(NULL, "  ");

       strcpy(arg4, token);

       token = strtok(NULL, "  ");

       strcpy(arg5, token);

       token = strtok(NULL, "  ");

       strcpy(arg6, token);

       // parse by command

       if (strcmp(cmd, "help") == 0)

       {

           // filter design commands

           printf("***\n\rget ntp \n\r");

       }

       //

       else if (strcmp(cmd, "num") == 0)

       {

           //

           sscanf(arg1, "%d", &user_number);

       }

       //

       // no valid command

       else

           printf("Huh?\n\r");

       // NEVER exit while

   } // END WHILE(1)

   PT_END(pt);

} // serial thread

// =========================================

// graphics thread

// =========================================

static PT_THREAD(protothread_graphics(struct pt *pt))

{

   PT_BEGIN(pt);

   static char video_string[80];

   // the protothreads interval timer

   PT_INTERVAL_INIT();

   // background

   fillRect(0, 0, 319, 239, BLACK); //

   // Draw some filled rectangles

   fillRect(0, 0, 76, 10, BLUE);     // blue box

   fillRect(100, 0, 200, 10, GREEN); // red box

   // fillRect(200, 0, 76, 10, GREEN); // green box

   // Write some text

   setTextColor(WHITE);

   setCursor(10, 1);

   setTextSize(1);

   writeString("ECE 4760");

   setTextColor(BLACK);

   setCursor(102, 1);

   setTextSize(1);

   writeString("LWIP: Access point + webserver");

   adc_init();

   adc_set_temp_sensor_enabled(true);

   adc_select_input(4);

   while (true)

   {

       // few times per second

       PT_YIELD_INTERVAL(200000);

       setTextColor2(WHITE, BLACK);

       setCursor(50, 80);

       setTextSize(1);

       sprintf(video_string, "LED state from /ledtest %d    ", access_led_state);

       writeString(video_string);

       fillCircle(35, 83, 5, access_led_state ? rgb(0, 7, 0) : rgb(0, 3, 0));

       setCursor(50, 100);

       setTextSize(1);

       sprintf(video_string, "NUMBER input from /ledtest %d    ", test_field);

       writeString(video_string);

       setCursor(50, 120);

       setTextSize(1);

       sprintf(video_string, "GPIO2 intensity from /ledtest %d%%  ", test2_field);

       writeString(video_string);

       // setCursor(50, 140) ;

       // setTextSize(1) ;

       // sprintf(video_string, "Input from server console %d    ", user_number) ;

       // writeString(video_string) ;

       // slider-set color

       if (new_color)

       {

           setCursor(50, 180);

           setTextSize(1);

           sprintf(video_string, "Color from slider from /newpage ");

           writeString(video_string);

           drawRect(119, 199, 42, 32, GRAY2);

           fillRect(120, 200, 40, 30, color);

           drawRect(120, 200, 40, 30, BLACK);

           new_color = false;

       }

       const float conversion_factor = 3.3f / (1 << 12);

       // conver to volts

       float result = (float)adc_read() * conversion_factor;

       // printf("adc %d v %f \n", adc_read(), result);

       /// convert volts to ~temp (from hardare manual)

       temp = 27.0f - (result - 0.706f) / 0.001721f;

       setCursor(50, 160);

       setTextSize(1);

       sprintf(video_string, "Internal Temp = %3.1f C", temp);

       writeString(video_string);

       //

   }

   PT_END(pt);

} // graphics thread

// ========================================

// === core 1 main -- started in main below

// ========================================

void core1_main()

{

   //  === add threads  ====================

   // for core 1

   pt_add_thread(protothread_graphics);

   //

   // === initalize the scheduler ==========

   pt_schedule_start;

   // NEVER exits

   // ======================================

}

// ========================================

// === core 0 main

// ========================================

int main()

{

   // set the clock -- if you want

   // set_sys_clock_khz(250000, true); // 171us

   //========================================

   // start the serial i/o

   stdio_init_all();

   // ======================================

   // Initialize the VGA system

   initVGA();

   // =====================================

   // turn on ADC (move to thread)

   // adc_init();

   // adc_set_temp_sensor_enabled(true);

   // adc_select_input(4);

   // GPIO INITIALIZE

   // // MAP SPI SIGNAL TO GPIO PORT

   // gpio_set_function(MOISTURE_PIN, GPIO_FUNC_SPI);

   // gpio_init()

   // adc_init();

   // adc_gpio_init(MOISTURE_PIN);

   // adc_select_input(1);

   // moisture_value = (float) adc_read();

   // gpio_set_dir(MOISTURE_PIN, GPIO_IN);

   //========================================

   // network init

   // start the cyw43 in access point mode

   // start the DHCP server

   // start the DNS server

   // start the TCP server

   // =======================================

   TCP_SERVER_T *state = calloc(1, sizeof(TCP_SERVER_T));

   if (!state)

   {

       DEBUG_printf("failed to allocate state\n");

       return 1;

   }

   if (cyw43_arch_init())

   {

       DEBUG_printf("failed to initialise\n");

       return 1;

   }

   // Get notified if the user presses a key

   state->context = cyw43_arch_async_context();

   //>>>> key_pressed_worker.user_data = state;

   //>>>>async_context_add_when_pending_worker(cyw43_arch_async_context(), &key_pressed_worker);

   stdio_set_chars_available_callback(key_pressed_func, state);

   // access point SSID and PASSWORD

   // WPA2 authorization

   const char *ap_name = "picow_test";

#if 1

   const char *password = "password";

#else

   const char *password = NULL;

#endif

   cyw43_arch_enable_ap_mode(ap_name, password, CYW43_AUTH_WPA2_AES_PSK);

   // 'state' is a pointer to type TCP_SERVER_T

   ip4_addr_t mask;

   IP4_ADDR(ip_2_ip4(&state->gw), 192, 168, 4, 1);

   IP4_ADDR(ip_2_ip4(&mask), 255, 255, 255, 0);

   // Start the dhcp server

   // and set picoW IP address from 'state' structure

   // set 'mask' as defined above

   dhcp_server_t dhcp_server;

   dhcp_server_init(&dhcp_server, &state->gw, &mask);

   // Start the dns server

   // and set picoW IP address from 'state' structure

   dns_server_t dns_server;

   dns_server_init(&dns_server, &state->gw);

   if (!tcp_server_open(state, ap_name))

   {

       DEBUG_printf("failed to open server\n");

       return 1;

   }

   // ======================================

   // init thread communication semaphores

   // init pt strucdtures

   //========================================

   // start core 1 threads --

   multicore_reset_core1();

   multicore_launch_core1(&core1_main);

   // === config threads ========================

   // for core 0

   // announce the threader version on system reset

   printf("\n\rStarting Protothreads RP2040 v1.1.2 two-core\n\r");

   // pt_add_thread(protothread_access);

   pt_add_thread(protothread_toggle_gpio2);

   //   pt_add_thread(protothread_serial) ;

   // pt_add_thread(protothread_graphics) ;

   //

   // === initalize the scheduler ===============

   pt_schedule_start;

   // NEVER exits

   // ===========================================

   // NEVER gets here

   // tcp_server_close(state);

   dns_server_deinit(&dns_server);

   dhcp_server_deinit(&dhcp_server);

   cyw43_arch_deinit();

} // end main

 ///////////

 // end ////

 ///////////