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 ////
///////////