init llm assisted tcp client

This commit is contained in:
Travis Shears 2025-12-21 17:32:42 +01:00
parent 908d563bc4
commit 3f54203246
Signed by: travisshears
GPG key ID: CB9BF1910F3F7469
12 changed files with 584 additions and 394 deletions

View file

@ -21,35 +21,19 @@ pico_sdk_init()
# == NODE 1 ==
if ((NOT MQTT_SERVER) OR (NOT MQTT_USERNAME) OR (NOT MQTT_PASSWORD))
message("Missing MQTT_SERVER or MQTT_USERNAME or MQTT_PASSWORD")
return()
endif()
set(MQTT_SERVER "${MQTT_SERVER}" CACHE INTERNAL "MQTT server")
set(MQTT_USERNAME "${MQTT_USERNAME}" CACHE INTERNAL "MQTT user name")
set(MQTT_PASSWORD "${MQTT_PASSWORD}" CACHE INTERNAL "MQTT password")
set(WIFI_SSID "${WIFI_SSID}" CACHE INTERNAL "wifi ssid")
set(WIFI_PASSWORD "${WIFI_PASSWORD}" CACHE INTERNAL "wifi password")
# Set path to the certificate include file
if (NOT MQTT_CERT_PATH)
set(MQTT_CERT_PATH ${CMAKE_CURRENT_LIST_DIR}/certs/${MQTT_SERVER})
endif()
# Set the name of the certificate include file
if (NOT MQTT_CERT_INC)
set(MQTT_CERT_INC mqtt_client.inc)
endif()
# TODO: Update this to the real server not my macbook
set(BACKEND_SERVER_IP "192.168.1.153" CACHE STRING "Backend server IP")
set(BACKEND_SERVER_PORT "8080" CACHE STRING "Backend server port")
# the executable
add_executable(node1 node1.c bme280.c pms5003.c)
add_executable(node1 node1.c bme280.c pms5003.c tcp_client.c)
pico_set_program_version(node1 "0.1")
pico_set_program_name(node1 "node_one")
# pull in common dependencies
target_link_libraries(node1 pico_stdlib hardware_i2c pico_cyw43_arch_lwip_threadsafe_background pico_lwip_mqtt)
target_link_libraries(node1 pico_stdlib hardware_i2c pico_cyw43_arch_lwip_threadsafe_background)
target_include_directories(node1 PRIVATE
${CMAKE_CURRENT_LIST_DIR}
@ -59,40 +43,9 @@ target_include_directories(node1 PRIVATE
target_compile_definitions(node1 PRIVATE
WIFI_SSID=\"${WIFI_SSID}\"
WIFI_PASSWORD=\"${WIFI_PASSWORD}\"
MQTT_SERVER=\"${MQTT_SERVER}\"
MQTT_USERNAME=\"${MQTT_USERNAME}\"
MQTT_PASSWORD=\"${MQTT_PASSWORD}\"
BACKEND_SERVER_IP=\"${BACKEND_SERVER_IP}\"
BACKEND_SERVER_PORT=${BACKEND_SERVER_PORT}
)
# create map/bin/hex file etc.
pico_add_extra_outputs( node1 )
# == TCP TEST ==
add_executable(tcp_test tcp.c)
target_include_directories(tcp_test PRIVATE
${CMAKE_CURRENT_LIST_DIR}
${CMAKE_CURRENT_LIST_DIR}/.. # for our common lwipopts
)
target_link_libraries(tcp_test
pico_cyw43_arch_lwip_threadsafe_background
pico_stdlib
)
target_compile_definitions(tcp_test PRIVATE
WIFI_SSID=\"${WIFI_SSID}\"
WIFI_PASSWORD=\"${WIFI_PASSWORD}\"
)
pico_add_extra_outputs(tcp_test)
# == CAN BUS SENDER TEST ==
add_executable( can_bus_sender_test can_bus_sender_test.c can_bus.c )
pico_set_program_version(can_bus_sender_test "0.1")
pico_set_program_name(can_bus_sender_test "Can Bus Sender Test")
target_link_libraries(can_bus_sender_test pico_stdlib hardware_spi)
pico_add_extra_outputs(can_bus_sender_test)

176
INTEGRATION_COMPLETE.md Normal file
View file

@ -0,0 +1,176 @@
# TCP Client Integration Complete ✓
The `node1.c` file has been successfully updated to use the TCP client library to send sensor data to your Go backend server.
## Changes Made
### 1. node1.c
**Added:**
- WiFi and backend server configuration defines
- `wifi_init()` function to connect to WiFi
- TCP client initialization in `main()`
- Actual message sending in `cb_30` callback (replaced commented-out code)
- Error handling for WiFi/TCP failures
**Key Features:**
- WiFi connects on startup with 30-second timeout
- If WiFi fails, sensor readings continue but data isn't sent
- Every 5 minutes (10 readings × 10 seconds), sends JSON to your Go server
- Comms LED blinks on successful sends
- Prints status messages to serial output
### 2. CmakeLists.txt
**Added:**
- `tcp_client.c` to node1 executable sources
- Backend server configuration variables
- Compile definitions for `BACKEND_SERVER_IP` and `BACKEND_SERVER_PORT`
## Message Format
The Pico sends JSON messages to `192.168.1.153:8080`:
```json
{"temperature":23.50,"pressure":1013.25,"humidity":45.00,"pm1":10.00,"pm2_5":25.50,"pm10":50.00}
```
Your Go server will receive this in the `handleConnection` function and log it with structured logging.
## Building
```bash
cd build
cmake .. \
-DWIFI_SSID="your_ssid" \
-DWIFI_PASSWORD="your_password" \
-DMQTT_SERVER="your_mqtt_server" \
-DMQTT_USERNAME="mqtt_user" \
-DMQTT_PASSWORD="mqtt_pass" \
-DBACKEND_SERVER_IP="192.168.1.153" \
-DBACKEND_SERVER_PORT=8080
make node1
```
Or if you have a build script that sets environment variables, just run it.
## Testing
1. **Start the Go server:**
```bash
cd event_proxy
go run main.go
```
You should see:
```
{"time":"...","level":"INFO","msg":"Starting TCP server","address":"192.168.1.153:8080"}
```
2. **Flash the Pico W:**
```bash
# Use your flash.sh script or:
cp build/node1.uf2 /Volumes/RPI-RP2/
```
3. **Monitor the Pico:**
```bash
# Use your screen.sh script or:
screen /dev/tty.usbmodem* 115200
```
4. **Watch for connection messages:**
**On Pico serial output:**
```
Initializing WiFi...
Connecting to WiFi 'your_ssid'...
WiFi connected successfully
TCP client ready: 192.168.1.153:8080
Making BME280 Reading
cb_30: 0
...
cb_30: 9
Preparing data to send
Temperature: 23.50
Pressure: 1013.25
Humidity: 45.00
PM1: 10.00
PM2.5: 25.50
PM10: 50.00
Sending data to backend server...
tcp_client: connecting to 192.168.1.153 port 8080
tcp_client: connected, sending message
tcp_client: sent 108 bytes
tcp_client: message sent completely
tcp_client: send success
✓ Data sent successfully
COMMS LED BLINK COUNT: 10
```
**On Go server output:**
```json
{"time":"...","level":"INFO","msg":"New connection","remote_addr":"192.168.1.xxx:xxxxx"}
{"time":"...","level":"INFO","msg":"Received message","message":"{\"temperature\":23.50,\"pressure\":1013.25,\"humidity\":45.00,\"pm1\":10.00,\"pm2_5\":25.50,\"pm10\":50.00}\n","bytes":108}
{"time":"...","level":"INFO","msg":"Error reading from connection","error":"EOF"}
```
## Configuration
All configuration is in CMake variables or defines:
| Variable | Default | Description |
|----------|---------|-------------|
| `WIFI_SSID` | - | Your WiFi network name |
| `WIFI_PASSWORD` | - | Your WiFi password |
| `BACKEND_SERVER_IP` | `192.168.1.153` | Your Go server IP |
| `BACKEND_SERVER_PORT` | `8080` | Your Go server port |
| `LOOP_INTERVAL_MS` | `10000` | Time between readings (10 sec) |
## Timing
- **Reading interval:** 10 seconds (configured by `LOOP_INTERVAL_MS`)
- **Readings per batch:** 10
- **Send interval:** 100 seconds (~1.7 minutes)
- **Connection timeout:** 10 seconds
- **Watchdog timeout:** 60 seconds
- **Auto-restart:** 24 hours
## Troubleshooting
### WiFi not connecting
- Check SSID and password in build command
- Ensure WiFi is 2.4GHz (Pico W doesn't support 5GHz)
- Monitor serial output for detailed error messages
### TCP connection fails
- Verify Go server is running: `go run event_proxy/main.go`
- Check server IP is correct: `192.168.1.153`
- Ensure both devices are on same network
- Check firewall settings on server machine
### Data not arriving at server
- Verify `wifi_connected` is true in serial output
- Check for "✓ Data sent successfully" messages
- Look for connection logs in Go server output
- Increase timeout if network is slow (change 10000 to 20000)
## Next Steps
1. **Run it!** Flash the Pico and start the Go server
2. **Monitor both** to verify data flow
3. **Process the data** in your Go server (parse JSON, store to DB, forward to Home Assistant, etc.)
4. **Adjust timing** if needed by changing `LOOP_INTERVAL_MS`
## Files Reference
- `tcp_client.h` - TCP client header
- `tcp_client.c` - TCP client implementation
- `node1.c` - Main application (updated)
- `CmakeLists.txt` - Build configuration (updated)
- `TCP_CLIENT_README.md` - Detailed API documentation
- `event_proxy/main.go` - Your Go TCP server
## Success! 🎉
Your Pico W weather station now sends sensor data over WiFi to your Go backend server every ~1.7 minutes!

109
can_bus.c
View file

@ -1,109 +0,0 @@
#include "can_bus.h"
// https://docs.cirkitdesigner.com/component/a4fea22c-a62d-453b-8645-1c8efe324cbc/mcp2515
// https://learn.adafruit.com/adafruit-picowbell-can-bus-for-pico/overview
// Pico Lib: https://github.com/adamczykpiotr/pico-mcp2515
// Arduino Lib: https://github.com/autowp/arduino-mcp2515
// MCP2515 is the cheap aliexpress boards I have
// spi_inst_t* CHANNEL = spi0;
// uint32_t SPI_CLOCK = DEFAULT_SPI_CLOCK
//
//
// 10000000 //10Mhz
#define MCP_SPI_CLOCK 5000000 // 5Mhz
#define MCP_INSTRUCTION_RESET 0xC0
#define MCP_INSTRUCTION_WRITE 0x02
void mcp2515_init(mcp2515_config *config, uint8_t cs_pin, uint8_t tx_pin,
uint8_t rx_pin, uint8_t sck_pin, spi_inst_t *spi_inst) {
config->CHANNEL = spi_inst;
config->CS_PIN = cs_pin;
spi_init(config->CHANNEL, MCP_SPI_CLOCK);
gpio_set_function(rx_pin, GPIO_FUNC_SPI);
gpio_set_function(sck_pin, GPIO_FUNC_SPI);
gpio_set_function(tx_pin, GPIO_FUNC_SPI);
spi_set_format(config->CHANNEL, 8, SPI_CPOL_0, SPI_CPHA_0, SPI_MSB_FIRST);
// Chip select is active-low, so we'll initialise it to a driven-high state
gpio_init(config->CS_PIN);
gpio_set_dir(config->CS_PIN, GPIO_OUT);
// gpio_put(cs_pin, 0);
}
static void mcp2515_start_spi(mcp2515_config *config) {
asm volatile("nop \n nop \n nop");
gpio_put(config->CS_PIN, 0);
asm volatile("nop \n nop \n nop");
}
static void mcp2515_end_spi(mcp2515_config *config) {
asm volatile("nop \n nop \n nop");
gpio_put(config->CS_PIN, 1);
asm volatile("nop \n nop \n nop");
}
static void mcp2515_set_registers(mcp2515_config *config, const uint8_t reg,
const uint8_t values[], const uint8_t n) {
mcp2515_start_spi(config);
uint8_t data[2] = {MCP_INSTRUCTION_WRITE, reg};
spi_write_blocking(config->CHANNEL, data, 2);
spi_write_blocking(config->CHANNEL, values, n);
mcp2515_end_spi(config);
}
enum MCP_ERROR mcp2515_reset(mcp2515_config *config) {
mcp2515_start_spi(config);
uint8_t instruction = MCP_INSTRUCTION_RESET;
spi_write_blocking(config->CHANNEL, &instruction, 1);
mcp2515_end_spi(config);
sleep_ms(10);
uint8_t zeros[14];
memset(zeros, 0, sizeof(zeros));
// setRegisters(MCP_TXB0CTRL, zeros, 14);
// setRegisters(MCP_TXB1CTRL, zeros, 14);
// setRegisters(MCP_TXB2CTRL, zeros, 14);
// setRegister(MCP_RXB0CTRL, 0);
// setRegister(MCP_RXB1CTRL, 0);
// setRegister(MCP_CANINTE,
// CANINTF_RX0IF | CANINTF_RX1IF | CANINTF_ERRIF | CANINTF_MERRF);
// receives all valid messages using either Standard or Extended Identifiers
// that meet filter criteria. RXF0 is applied for RXB0, RXF1 is applied for
// RXB1
// modifyRegister(MCP_RXB0CTRL,
// RXBnCTRL_RXM_MASK | RXB0CTRL_BUKT | RXB0CTRL_FILHIT_MASK,
// RXBnCTRL_RXM_STDEXT | RXB0CTRL_BUKT | RXB0CTRL_FILHIT);
// modifyRegister(MCP_RXB1CTRL, RXBnCTRL_RXM_MASK | RXB1CTRL_FILHIT_MASK,
// RXBnCTRL_RXM_STDEXT | RXB1CTRL_FILHIT);
// clear filters and masks
// do not filter any standard frames for RXF0 used by RXB0
// do not filter any extended frames for RXF1 used by RXB1
// RXF filters[] = {RXF0, RXF1, RXF2, RXF3, RXF4, RXF5};
// for (int i = 0; i < 6; i++) {
// bool ext = (i == 1);
// ERROR result = setFilter(filters[i], ext, 0);
// if (result != ERROR_OK) {
// return result;
// }
// }
// MASK masks[] = {MASK0, MASK1};
// for (int i = 0; i < 2; i++) {
// ERROR result = setFilterMask(masks[i], true, 0);
// if (result != ERROR_OK) {
// return result;
// }
// }
return ERROR_OK;
}
// mcp2515.setBitrate(CAN_125KBPS);
// mcp2515.setLoopbackMode();

View file

@ -1,38 +0,0 @@
#include "boards/pico.h"
#include "hardware/spi.h"
#include "pico/stdlib.h"
#include "pico/time.h"
// 16MHz 125Kbps
#define MCP_CFG1 (0x03)
#define MCP_CFG2 (0xF0)
#define MCP_CFG3 (0x86)
typedef struct {
spi_inst_t *CHANNEL;
uint8_t CS_PIN;
// uint8_t TX_PIN;
// uint8_t RX_PIN;
// uint8_t SCK_PIN;
// uint32_t SPI_CLOCK;
} mcp2515_config;
struct can_frame {
uint32_t can_id; // 32 bit CAN_ID + EFF/RTR/ERR flags
char can_dlc; // frame payload length in byte (0 .. CAN_MAX_DLEN) */
char data[8]; // msg
};
enum MCP_ERROR {
ERROR_OK = 0,
ERROR_FAIL = 1,
ERROR_ALLTXBUSY = 2,
ERROR_FAILINIT = 3,
ERROR_FAILTX = 4,
ERROR_NOMSG = 5
};
void mcp2515_init(mcp2515_config *config, uint8_t cs_pin, uint8_t tx_pin,
uint8_t rx_pin, uint8_t sck_pin, spi_inst_t *spi_inst);
enum MCP_ERROR sendMessage(const struct can_frame *frame);
enum MPC_ERROR readMessage(struct can_frame *frame);

View file

View file

@ -1,20 +0,0 @@
#include "pico/stdlib.h"
#include <hardware/gpio.h>
#include <hardware/spi.h>
#include "can_bus.h"
static mcp2515_config can_bus_config;
int main() {
stdio_init_all();
// Setup BME280
mcp2515_init(&can_bus_config, 13, 11, 12, 10, spi1);
while (true) {
// TODO: send message
// mcp2515.reset();
sleep_ms(1000);
// tight_loop_contents();
}
}

View file

@ -1,24 +1,30 @@
package main
import (
"fmt"
"log/slog"
"net"
"os"
)
func main() {
// Initialize JSON logger
logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
slog.SetDefault(logger)
addr := "192.168.1.153:8080"
fmt.Printf("Starting TCP server on port %s\n", addr)
//Address to bind to
slog.Info("Starting TCP server", "address", addr)
//Resolve address
addr_, err := net.ResolveTCPAddr("tcp", addr)
if err != nil {
fmt.Printf("Error resolving address: %s", err.Error())
slog.Error("Error resolving address", "error", err)
return
}
//Listen for incoming connections
listener, err := net.ListenTCP("tcp", addr_)
if err != nil {
fmt.Printf("Error starting server: %s", err.Error())
slog.Error("Error starting server", "error", err)
return
}
defer listener.Close()
@ -27,7 +33,7 @@ func main() {
//Accept incoming connection
conn, err := listener.AcceptTCP()
if err != nil {
fmt.Printf("Error accepting connection: %s", err.Error())
slog.Error("Error accepting connection", "error", err)
continue
}
go handleConnection(conn)
@ -36,25 +42,26 @@ func main() {
func handleConnection(conn *net.TCPConn) {
defer conn.Close()
fmt.Printf("New connection from %s\n", conn.RemoteAddr().String())
slog.Info("New connection", "remote_addr", conn.RemoteAddr().String())
buffer := make([]byte, 1024)
for {
//Read up to 1024 bytes
n, err := conn.Read(buffer)
if err != nil {
fmt.Printf("Error reading from connection: %s", err.Error())
slog.Error("Error reading from connection", "error", err)
return
}
if n == 0 {
return
}
fmt.Printf("Received message: %s\n", string(buffer[:n]))
slog.Info("Received message", "message", string(buffer[:n]), "bytes", n)
//Echo message back
// _, err = conn.Write(buffer[:n])
_, err = conn.Write([]byte("1"))
if err != nil {
fmt.Printf("Error writing to connection: %s", err.Error())
slog.Error("Error writing to connection", "error", err)
return
}
}

View file

@ -1,121 +0,0 @@
/**
* MQTT Client to publish sensor data to homeassistant
* MQTT client doc: https://www.nongnu.org/lwip/2_1_x/group__mqtt.html#gafdfa0e65b217e92835d35858924565cf
* PICO W MQTT example: https://github.com/raspberrypi/pico-examples/blob/master/pico_w/wifi/mqtt/README
*/
#include "mqtt_client.h"
#include "pico/stdlib.h"
#include "pico/cyw43_arch.h"
#include "lwip/apps/mqtt.h"
#include "hardware/watchdog.h"
// #include <cstdio>
#include <string.h>
#include <stdio.h>
// keep alive in seconds
#define MQTT_KEEP_ALIVE_S 60
// qos passed to mqtt_subscribe
// At most once (QoS 0)
// At least once (QoS 1)
// Exactly once (QoS 2)
#define MQTT_PUBLISH_QOS 1
#define MQTT_PUBLISH_RETAIN 0
static void pub_request_cb(__unused void *arg, err_t err) {
if (err != 0) {
printf("pub_request_cb failed %d", err);
}
}
// static void publish_temperature(MQTT_CLIENT_DATA_T *state) {
// mqtt_publish(state->mqtt_client_inst, MQTT_TOPIC, temp_str, strlen(temp_str), MQTT_PUBLISH_QOS, MQTT_PUBLISH_RETAIN, pub_request_cb, state);
static void mqtt_connection_cb(mqtt_client_t *client, void *arg, mqtt_connection_status_t status) {
MQTT_CLIENT_DATA_T* state = (MQTT_CLIENT_DATA_T*)arg;
if (status == MQTT_CONNECT_ACCEPTED) {
state->connect_done = true;
printf("Connected to MQTT server\n");
} else if (status == MQTT_CONNECT_DISCONNECTED) {
if (!state->connect_done) {
panic("Failed to connect to mqtt server");
}
}
else {
panic("Unexpected status");
}
}
static void start_client(MQTT_CLIENT_DATA_T *state) {
const int port = MQTT_PORT;
state->mqtt_client_inst = mqtt_client_new();
if (!state->mqtt_client_inst) {
panic("MQTT client instance creation error");
}
printf("IP address of this device %s\n", ipaddr_ntoa(&(netif_list->ip_addr)));
printf("Connecting to mqtt server at %s\n", ipaddr_ntoa(&state->mqtt_server_address));
cyw43_arch_lwip_begin();
if (mqtt_client_connect(state->mqtt_client_inst, &state->mqtt_server_address, port, mqtt_connection_cb, state, &state->mqtt_client_info) != ERR_OK) {
panic("MQTT broker connection error");
}
cyw43_arch_lwip_end();
}
void mqtt_client_pub_message(mqtt_client_config *config, const char *message) {
if (config->state.connect_done || !mqtt_client_is_connected(config->state.mqtt_client_inst)) {
printf("Can't send MQTT message because client is not connected\n");
}
cyw43_arch_lwip_begin();
if (mqtt_publish(config->state.mqtt_client_inst, config->topic, message, strlen(message), MQTT_PUBLISH_QOS, MQTT_PUBLISH_RETAIN, pub_request_cb, NULL) != ERR_OK) {
panic("MQTT publish error");
}
cyw43_arch_lwip_end();
}
void mqtt_client_init(mqtt_client_config *config, const char *topic, const char *device_name) {
// Copy the topic string into the config struct, up to 200 characters
strncpy(config->topic, topic, 100);
// config->topic[100] = '\0'; // Ensure null-termination
strncpy(config->device_name, device_name, 50);
printf("mqtt client starting\n");
if (cyw43_arch_init()) {
panic("Failed to inizialize CYW43");
}
config->state.mqtt_client_info.client_id = config->device_name;
config->state.mqtt_client_info.keep_alive = MQTT_KEEP_ALIVE_S; // Keep alive in sec
config->state.mqtt_client_info.client_user = MQTT_USERNAME;
config->state.mqtt_client_info.client_pass = MQTT_PASSWORD;
ip_addr_t ip_addr;
// Initialize the IP address
if (ipaddr_aton(MQTT_SERVER, &ip_addr)) {
printf("IP address is valid and initialized.\n");
} else {
printf("Invalid IP address format.\n");
}
config->state.mqtt_server_address = ip_addr;
cyw43_arch_enable_sta_mode();
if (cyw43_arch_wifi_connect_timeout_ms(WIFI_SSID, WIFI_PASSWORD, CYW43_AUTH_WPA2_AES_PSK, 30000)) {
panic("Failed to connect");
}
printf("\nConnected to Wifi\n");
start_client(&config->state);
// while (!config->state.connect_done || mqtt_client_is_connected(config->state.mqtt_client_inst)) {
// cyw43_arch_poll();
// cyw43_arch_wait_for_work_until(make_timeout_time_ms(10000));
// }
// printf("mqtt client exiting\n");
// return 0;
}
void mqtt_client_do_network_stuff(mqtt_client_config *config) {
cyw43_arch_poll();
}

View file

@ -1,32 +0,0 @@
#ifndef MQTT_CLIENT_H
#define MQTT_CLIENT_H
// #include "pico/cyw43_arch.h"
#include "lwip/apps/mqtt.h"
#ifndef MQTT_SERVER
#error Need to define MQTT_SERVER
#endif
typedef struct {
mqtt_client_t* mqtt_client_inst;
struct mqtt_connect_client_info_t mqtt_client_info;
char data[MQTT_OUTPUT_RINGBUF_SIZE];
char topic[100];
uint32_t len;
ip_addr_t mqtt_server_address;
bool connect_done;
int subscribe_count;
bool stop_client;
} MQTT_CLIENT_DATA_T;
typedef struct {
char topic[201]; // max 200 chars + null terminator
char device_name[51]; // max 50 chars + null terminator
MQTT_CLIENT_DATA_T state;
} mqtt_client_config;
void mqtt_client_init(mqtt_client_config *config, const char *topic, const char *device_name);
void mqtt_client_pub_message(mqtt_client_config *config, const char *message);
void mqtt_client_do_network_stuff(mqtt_client_config *config);
#endif /* MQTT_CLIENT_H */

94
node1.c
View file

@ -1,6 +1,8 @@
#include "bme280.h"
#include "pico/stdlib.h"
#include "pms5003.h"
#include "pico/cyw43_arch.h"
#include "tcp_client.h"
#include <hardware/gpio.h>
#include <hardware/i2c.h>
#include <hardware/uart.h>
@ -13,10 +15,19 @@
#include <hardware/watchdog.h>
// 5 sec loop is for testing
// #define LOOP_INTERVAL_MS 5000
#define LOOP_INTERVAL_MS 10000
#define LOOP_INTERVAL_MS 5000
// #define LOOP_INTERVAL_MS 10000
// #define LOOP_INTERVAL_MS 30000
// WiFi and Backend Server Configuration
#ifndef BACKEND_SERVER_IP
#define BACKEND_SERVER_IP "192.168.1.153"
#endif
#ifndef BACKEND_SERVER_PORT
#define BACKEND_SERVER_PORT 8080
#endif
/**
* Balcony Weather Station Node 1
* record sensor data and send it to home assistant every 5 minutes
@ -28,6 +39,7 @@ void comms_led_init() {
gpio_set_dir(16, GPIO_OUT);
}
absolute_time_t comms_led_off_time;
int16_t comms_led_blink_count = 0;
bool comms_led_state = false;
@ -55,6 +67,9 @@ static bool cb_24h(__unused struct repeating_timer *t) {
return false; // Not reached
}
static tcp_client_config tcp_config;
static bool wifi_connected = false;
static pms5003_config pms_config;
static pms5003_reading current_pms5003_reading;
@ -78,6 +93,28 @@ static bme280_reading calculate_average_bme280_reading() {
return average_reading;
}
/**
* Initialize WiFi connection
*/
bool wifi_init(void) {
if (cyw43_arch_init()) {
printf("Failed to initialize CYW43\n");
return false;
}
cyw43_arch_enable_sta_mode();
printf("Connecting to WiFi '%s'...\n", WIFI_SSID);
if (cyw43_arch_wifi_connect_timeout_ms(WIFI_SSID, WIFI_PASSWORD,
CYW43_AUTH_WPA2_AES_PSK, 30000)) {
printf("Failed to connect to WiFi\n");
return false;
}
printf("WiFi connected successfully\n");
return true;
}
/**
* Callback function called every 30 seconds
*/
@ -97,14 +134,33 @@ static bool cb_30(__unused struct repeating_timer *t) {
printf("PM1: %.2f\n", current_pms5003_reading.pm1);
printf("PM2.5: %.2f\n", current_pms5003_reading.pm2_5);
printf("PM10: %.2f\n", current_pms5003_reading.pm10);
// char msg[100];
// char msg[200];
// snprintf(msg, sizeof(msg), "{\"temp\": %.2f, \"pressure\": %.2f, \"humidity\": %.2f, \"pm1\": %.2f, \"pm2_5\": %.2f, \"pm10\": %.2f}\n",
// current_bem280_reading.temperature, current_bem280_reading.pressure, current_bem280_reading.humidity,
// current_pms5003_reading.pm1, current_pms5003_reading.pm2_5, current_pms5003_reading.pm10);
printf("Sending data to home assistant...\n");
// printf
// mqtt_client_pub_message(&mqtt_config, msg);
// Send to backend server if WiFi is connected
if (wifi_connected) {
char msg[256];
snprintf(msg, sizeof(msg),
"{\"temperature\":%.2f,\"pressure\":%.2f,\"humidity\":%.2f,"
"\"pm1\":%.2f,\"pm2_5\":%.2f,\"pm10\":%.2f}\n",
current_bem280_reading.temperature,
current_bem280_reading.pressure,
current_bem280_reading.humidity,
current_pms5003_reading.pm1,
current_pms5003_reading.pm2_5,
current_pms5003_reading.pm10);
printf("Sending data to backend server...\n");
bool success = tcp_client_send_message(&tcp_config, msg);
if (success) {
printf("✓ Data sent successfully\n");
comms_led_blink();
} else {
printf("✗ Failed to send data\n");
}
} else {
printf("WiFi not connected, skipping send\n");
}
return true;
}
@ -135,10 +191,28 @@ static bool cb_30(__unused struct repeating_timer *t) {
int main() {
stdio_init_all();
watchdog_enable(60000, 1);
sleep_ms(2000); // Give time for USB serial
// Initialize communication LED
comms_led_init();
// Initialize WiFi
printf("Initializing WiFi...\n");
if (!wifi_init()) {
printf("WiFi initialization failed!\n");
// Continue anyway for sensor readings
} else {
wifi_connected = true;
// Initialize TCP client
if (!tcp_client_init(&tcp_config, BACKEND_SERVER_IP,
BACKEND_SERVER_PORT, 10000)) {
printf("TCP client initialization failed\n");
wifi_connected = false;
} else {
printf("TCP client ready: %s:%d\n", BACKEND_SERVER_IP, BACKEND_SERVER_PORT);
}
}
// Setup BME280
bme280_init(&bem_config, i2c1, 14, 15);

255
tcp_client.c Normal file
View file

@ -0,0 +1,255 @@
/**
* TCP Client for Pico W
* Simple TCP client for sending sensor data messages to a backend server
*/
#include "tcp_client.h"
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include "pico/stdlib.h"
#include "pico/cyw43_arch.h"
#include "lwip/pbuf.h"
#include "lwip/tcp.h"
#define DEBUG_printf printf
#define MAX_RETRY_COUNT 3
typedef struct {
struct tcp_pcb *tcp_pcb;
ip_addr_t remote_addr;
uint16_t remote_port;
const char *message;
int message_len;
int sent_len;
bool complete;
bool success;
bool connected;
uint32_t timeout_ms;
absolute_time_t timeout_time;
} tcp_client_state_t;
static err_t tcp_client_close(tcp_client_state_t *state) {
err_t err = ERR_OK;
if (state->tcp_pcb != NULL) {
tcp_arg(state->tcp_pcb, NULL);
tcp_poll(state->tcp_pcb, NULL, 0);
tcp_sent(state->tcp_pcb, NULL);
tcp_recv(state->tcp_pcb, NULL);
tcp_err(state->tcp_pcb, NULL);
err = tcp_close(state->tcp_pcb);
if (err != ERR_OK) {
DEBUG_printf("tcp_client: close failed %d, calling abort\n", err);
tcp_abort(state->tcp_pcb);
err = ERR_ABRT;
}
state->tcp_pcb = NULL;
}
return err;
}
static err_t tcp_client_result(tcp_client_state_t *state, int status) {
if (status == 0) {
DEBUG_printf("tcp_client: send success\n");
state->success = true;
} else {
DEBUG_printf("tcp_client: send failed %d\n", status);
state->success = false;
}
state->complete = true;
return tcp_client_close(state);
}
static err_t tcp_client_sent(void *arg, struct tcp_pcb *tpcb, u16_t len) {
tcp_client_state_t *state = (tcp_client_state_t*)arg;
DEBUG_printf("tcp_client: sent %u bytes\n", len);
state->sent_len += len;
if (state->sent_len >= state->message_len) {
DEBUG_printf("tcp_client: message sent completely\n");
return tcp_client_result(state, 0);
}
return ERR_OK;
}
static err_t tcp_client_connected(void *arg, struct tcp_pcb *tpcb, err_t err) {
tcp_client_state_t *state = (tcp_client_state_t*)arg;
if (err != ERR_OK) {
DEBUG_printf("tcp_client: connect failed %d\n", err);
return tcp_client_result(state, err);
}
state->connected = true;
DEBUG_printf("tcp_client: connected, sending message\n");
// Send the message
cyw43_arch_lwip_begin();
err_t write_err = tcp_write(tpcb, state->message, state->message_len, TCP_WRITE_FLAG_COPY);
if (write_err != ERR_OK) {
DEBUG_printf("tcp_client: failed to write data %d\n", write_err);
cyw43_arch_lwip_end();
return tcp_client_result(state, -1);
}
// Flush the data
err_t output_err = tcp_output(tpcb);
cyw43_arch_lwip_end();
if (output_err != ERR_OK) {
DEBUG_printf("tcp_client: failed to output data %d\n", output_err);
return tcp_client_result(state, -1);
}
return ERR_OK;
}
static err_t tcp_client_poll(void *arg, struct tcp_pcb *tpcb) {
tcp_client_state_t *state = (tcp_client_state_t*)arg;
DEBUG_printf("tcp_client: poll timeout\n");
return tcp_client_result(state, -1);
}
static void tcp_client_err(void *arg, err_t err) {
tcp_client_state_t *state = (tcp_client_state_t*)arg;
if (err != ERR_ABRT) {
DEBUG_printf("tcp_client: error %d\n", err);
state->success = false;
state->complete = true;
}
}
static err_t tcp_client_recv(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err) {
tcp_client_state_t *state = (tcp_client_state_t*)arg;
if (!p) {
DEBUG_printf("tcp_client: connection closed by server\n");
return tcp_client_result(state, 0);
}
cyw43_arch_lwip_check();
if (p->tot_len > 0) {
DEBUG_printf("tcp_client: received %d bytes (ignoring)\n", p->tot_len);
tcp_recved(tpcb, p->tot_len);
}
pbuf_free(p);
return ERR_OK;
}
static bool tcp_client_open(tcp_client_state_t *state) {
DEBUG_printf("tcp_client: connecting to %s port %u\n",
ip4addr_ntoa(&state->remote_addr), state->remote_port);
state->tcp_pcb = tcp_new_ip_type(IP_GET_TYPE(&state->remote_addr));
if (!state->tcp_pcb) {
DEBUG_printf("tcp_client: failed to create pcb\n");
return false;
}
tcp_arg(state->tcp_pcb, state);
tcp_poll(state->tcp_pcb, tcp_client_poll, 10);
tcp_sent(state->tcp_pcb, tcp_client_sent);
tcp_recv(state->tcp_pcb, tcp_client_recv);
tcp_err(state->tcp_pcb, tcp_client_err);
state->sent_len = 0;
state->timeout_time = make_timeout_time_ms(state->timeout_ms);
cyw43_arch_lwip_begin();
err_t err = tcp_connect(state->tcp_pcb, &state->remote_addr,
state->remote_port, tcp_client_connected);
cyw43_arch_lwip_end();
if (err != ERR_OK) {
DEBUG_printf("tcp_client: tcp_connect failed %d\n", err);
return false;
}
return true;
}
bool tcp_client_init(tcp_client_config *config, const char *server_ip,
uint16_t server_port, uint32_t timeout_ms) {
if (!config || !server_ip) {
return false;
}
strncpy(config->server_ip, server_ip, sizeof(config->server_ip) - 1);
config->server_ip[sizeof(config->server_ip) - 1] = '\0';
config->server_port = server_port;
config->timeout_ms = timeout_ms;
config->initialized = true;
config->internal_state = NULL;
DEBUG_printf("tcp_client: initialized for %s:%u\n", config->server_ip, config->server_port);
return true;
}
bool tcp_client_send_message(tcp_client_config *config, const char *message) {
if (!config || !config->initialized || !message) {
DEBUG_printf("tcp_client: invalid config or message\n");
return false;
}
tcp_client_state_t *state = calloc(1, sizeof(tcp_client_state_t));
if (!state) {
DEBUG_printf("tcp_client: failed to allocate state\n");
return false;
}
// Convert IP address
if (!ip4addr_aton(config->server_ip, &state->remote_addr)) {
DEBUG_printf("tcp_client: invalid IP address %s\n", config->server_ip);
free(state);
return false;
}
state->remote_port = config->server_port;
state->message = message;
state->message_len = strlen(message);
state->timeout_ms = config->timeout_ms;
state->complete = false;
state->success = false;
state->connected = false;
DEBUG_printf("tcp_client: sending message (%d bytes): %s\n",
state->message_len, message);
if (!tcp_client_open(state)) {
DEBUG_printf("tcp_client: failed to open connection\n");
free(state);
return false;
}
// Wait for completion or timeout
while (!state->complete) {
if (time_reached(state->timeout_time)) {
DEBUG_printf("tcp_client: operation timed out\n");
tcp_client_close(state);
state->complete = true;
state->success = false;
break;
}
#if PICO_CYW43_ARCH_POLL
cyw43_arch_poll();
cyw43_arch_wait_for_work_until(make_timeout_time_ms(100));
#else
sleep_ms(100);
#endif
}
bool success = state->success;
free(state);
return success;
}
void tcp_client_cleanup(tcp_client_config *config) {
if (config) {
config->initialized = false;
config->internal_state = NULL;
}
}

45
tcp_client.h Normal file
View file

@ -0,0 +1,45 @@
#ifndef TCP_CLIENT_H
#define TCP_CLIENT_H
#include <stdbool.h>
#include "lwip/ip_addr.h"
/**
* TCP Client Configuration
*/
typedef struct {
char server_ip[16]; // Server IP address as string (e.g., "192.168.1.100")
uint16_t server_port; // Server port number
uint32_t timeout_ms; // Connection timeout in milliseconds
bool initialized; // Internal state flag
void *internal_state; // Internal state pointer (opaque)
} tcp_client_config;
/**
* Initialize the TCP client configuration
*
* @param config Pointer to the configuration structure
* @param server_ip Server IP address as a string (e.g., "192.168.1.100")
* @param server_port Server port number
* @param timeout_ms Connection timeout in milliseconds
* @return true on success, false on failure
*/
bool tcp_client_init(tcp_client_config *config, const char *server_ip, uint16_t server_port, uint32_t timeout_ms);
/**
* Send a message to the TCP server
*
* @param config Pointer to the configuration structure
* @param message Null-terminated string message to send
* @return true on success, false on failure
*/
bool tcp_client_send_message(tcp_client_config *config, const char *message);
/**
* Close and cleanup the TCP client
*
* @param config Pointer to the configuration structure
*/
void tcp_client_cleanup(tcp_client_config *config);
#endif // TCP_CLIENT_H