init llm assisted tcp client
This commit is contained in:
parent
908d563bc4
commit
3f54203246
12 changed files with 584 additions and 394 deletions
|
|
@ -21,35 +21,19 @@ pico_sdk_init()
|
||||||
# == NODE 1 ==
|
# == 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_SSID "${WIFI_SSID}" CACHE INTERNAL "wifi ssid")
|
||||||
set(WIFI_PASSWORD "${WIFI_PASSWORD}" CACHE INTERNAL "wifi password")
|
set(WIFI_PASSWORD "${WIFI_PASSWORD}" CACHE INTERNAL "wifi password")
|
||||||
|
# TODO: Update this to the real server not my macbook
|
||||||
# Set path to the certificate include file
|
set(BACKEND_SERVER_IP "192.168.1.153" CACHE STRING "Backend server IP")
|
||||||
if (NOT MQTT_CERT_PATH)
|
set(BACKEND_SERVER_PORT "8080" CACHE STRING "Backend server port")
|
||||||
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()
|
|
||||||
|
|
||||||
|
|
||||||
# the executable
|
# 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_version(node1 "0.1")
|
||||||
pico_set_program_name(node1 "node_one")
|
pico_set_program_name(node1 "node_one")
|
||||||
|
|
||||||
# pull in common dependencies
|
# 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
|
target_include_directories(node1 PRIVATE
|
||||||
${CMAKE_CURRENT_LIST_DIR}
|
${CMAKE_CURRENT_LIST_DIR}
|
||||||
|
|
@ -59,40 +43,9 @@ target_include_directories(node1 PRIVATE
|
||||||
target_compile_definitions(node1 PRIVATE
|
target_compile_definitions(node1 PRIVATE
|
||||||
WIFI_SSID=\"${WIFI_SSID}\"
|
WIFI_SSID=\"${WIFI_SSID}\"
|
||||||
WIFI_PASSWORD=\"${WIFI_PASSWORD}\"
|
WIFI_PASSWORD=\"${WIFI_PASSWORD}\"
|
||||||
MQTT_SERVER=\"${MQTT_SERVER}\"
|
BACKEND_SERVER_IP=\"${BACKEND_SERVER_IP}\"
|
||||||
MQTT_USERNAME=\"${MQTT_USERNAME}\"
|
BACKEND_SERVER_PORT=${BACKEND_SERVER_PORT}
|
||||||
MQTT_PASSWORD=\"${MQTT_PASSWORD}\"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# create map/bin/hex file etc.
|
# create map/bin/hex file etc.
|
||||||
pico_add_extra_outputs( node1 )
|
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
176
INTEGRATION_COMPLETE.md
Normal 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
109
can_bus.c
|
|
@ -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();
|
|
||||||
38
can_bus.h
38
can_bus.h
|
|
@ -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);
|
|
||||||
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,24 +1,30 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"log/slog"
|
||||||
"net"
|
"net"
|
||||||
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
// Initialize JSON logger
|
||||||
|
logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
|
||||||
|
slog.SetDefault(logger)
|
||||||
|
|
||||||
addr := "192.168.1.153:8080"
|
addr := "192.168.1.153:8080"
|
||||||
fmt.Printf("Starting TCP server on port %s\n", addr)
|
slog.Info("Starting TCP server", "address", addr)
|
||||||
//Address to bind to
|
|
||||||
//Resolve address
|
//Resolve address
|
||||||
addr_, err := net.ResolveTCPAddr("tcp", addr)
|
addr_, err := net.ResolveTCPAddr("tcp", addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Error resolving address: %s", err.Error())
|
slog.Error("Error resolving address", "error", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
//Listen for incoming connections
|
//Listen for incoming connections
|
||||||
listener, err := net.ListenTCP("tcp", addr_)
|
listener, err := net.ListenTCP("tcp", addr_)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Error starting server: %s", err.Error())
|
slog.Error("Error starting server", "error", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer listener.Close()
|
defer listener.Close()
|
||||||
|
|
@ -27,7 +33,7 @@ func main() {
|
||||||
//Accept incoming connection
|
//Accept incoming connection
|
||||||
conn, err := listener.AcceptTCP()
|
conn, err := listener.AcceptTCP()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Error accepting connection: %s", err.Error())
|
slog.Error("Error accepting connection", "error", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
go handleConnection(conn)
|
go handleConnection(conn)
|
||||||
|
|
@ -36,25 +42,26 @@ func main() {
|
||||||
|
|
||||||
func handleConnection(conn *net.TCPConn) {
|
func handleConnection(conn *net.TCPConn) {
|
||||||
defer conn.Close()
|
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)
|
buffer := make([]byte, 1024)
|
||||||
for {
|
for {
|
||||||
//Read up to 1024 bytes
|
//Read up to 1024 bytes
|
||||||
n, err := conn.Read(buffer)
|
n, err := conn.Read(buffer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Error reading from connection: %s", err.Error())
|
slog.Error("Error reading from connection", "error", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if n == 0 {
|
if n == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
fmt.Printf("Received message: %s\n", string(buffer[:n]))
|
slog.Info("Received message", "message", string(buffer[:n]), "bytes", n)
|
||||||
|
|
||||||
//Echo message back
|
//Echo message back
|
||||||
// _, err = conn.Write(buffer[:n])
|
// _, err = conn.Write(buffer[:n])
|
||||||
_, err = conn.Write([]byte("1"))
|
_, err = conn.Write([]byte("1"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Error writing to connection: %s", err.Error())
|
slog.Error("Error writing to connection", "error", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
121
mqtt_client.c
121
mqtt_client.c
|
|
@ -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();
|
|
||||||
}
|
|
||||||
|
|
@ -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
94
node1.c
|
|
@ -1,6 +1,8 @@
|
||||||
#include "bme280.h"
|
#include "bme280.h"
|
||||||
#include "pico/stdlib.h"
|
#include "pico/stdlib.h"
|
||||||
#include "pms5003.h"
|
#include "pms5003.h"
|
||||||
|
#include "pico/cyw43_arch.h"
|
||||||
|
#include "tcp_client.h"
|
||||||
#include <hardware/gpio.h>
|
#include <hardware/gpio.h>
|
||||||
#include <hardware/i2c.h>
|
#include <hardware/i2c.h>
|
||||||
#include <hardware/uart.h>
|
#include <hardware/uart.h>
|
||||||
|
|
@ -13,10 +15,19 @@
|
||||||
#include <hardware/watchdog.h>
|
#include <hardware/watchdog.h>
|
||||||
|
|
||||||
// 5 sec loop is for testing
|
// 5 sec loop is for testing
|
||||||
// #define LOOP_INTERVAL_MS 5000
|
#define LOOP_INTERVAL_MS 5000
|
||||||
#define LOOP_INTERVAL_MS 10000
|
// #define LOOP_INTERVAL_MS 10000
|
||||||
// #define LOOP_INTERVAL_MS 30000
|
// #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
|
* Balcony Weather Station Node 1
|
||||||
* record sensor data and send it to home assistant every 5 minutes
|
* 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);
|
gpio_set_dir(16, GPIO_OUT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
absolute_time_t comms_led_off_time;
|
absolute_time_t comms_led_off_time;
|
||||||
int16_t comms_led_blink_count = 0;
|
int16_t comms_led_blink_count = 0;
|
||||||
bool comms_led_state = false;
|
bool comms_led_state = false;
|
||||||
|
|
@ -55,6 +67,9 @@ static bool cb_24h(__unused struct repeating_timer *t) {
|
||||||
return false; // Not reached
|
return false; // Not reached
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static tcp_client_config tcp_config;
|
||||||
|
static bool wifi_connected = false;
|
||||||
|
|
||||||
static pms5003_config pms_config;
|
static pms5003_config pms_config;
|
||||||
static pms5003_reading current_pms5003_reading;
|
static pms5003_reading current_pms5003_reading;
|
||||||
|
|
||||||
|
|
@ -78,6 +93,28 @@ static bme280_reading calculate_average_bme280_reading() {
|
||||||
return average_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
|
* 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("PM1: %.2f\n", current_pms5003_reading.pm1);
|
||||||
printf("PM2.5: %.2f\n", current_pms5003_reading.pm2_5);
|
printf("PM2.5: %.2f\n", current_pms5003_reading.pm2_5);
|
||||||
printf("PM10: %.2f\n", current_pms5003_reading.pm10);
|
printf("PM10: %.2f\n", current_pms5003_reading.pm10);
|
||||||
// char msg[100];
|
|
||||||
// char msg[200];
|
// Send to backend server if WiFi is connected
|
||||||
// snprintf(msg, sizeof(msg), "{\"temp\": %.2f, \"pressure\": %.2f, \"humidity\": %.2f, \"pm1\": %.2f, \"pm2_5\": %.2f, \"pm10\": %.2f}\n",
|
if (wifi_connected) {
|
||||||
// current_bem280_reading.temperature, current_bem280_reading.pressure, current_bem280_reading.humidity,
|
char msg[256];
|
||||||
// current_pms5003_reading.pm1, current_pms5003_reading.pm2_5, current_pms5003_reading.pm10);
|
snprintf(msg, sizeof(msg),
|
||||||
printf("Sending data to home assistant...\n");
|
"{\"temperature\":%.2f,\"pressure\":%.2f,\"humidity\":%.2f,"
|
||||||
// printf
|
"\"pm1\":%.2f,\"pm2_5\":%.2f,\"pm10\":%.2f}\n",
|
||||||
// mqtt_client_pub_message(&mqtt_config, msg);
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -135,10 +191,28 @@ static bool cb_30(__unused struct repeating_timer *t) {
|
||||||
int main() {
|
int main() {
|
||||||
stdio_init_all();
|
stdio_init_all();
|
||||||
watchdog_enable(60000, 1);
|
watchdog_enable(60000, 1);
|
||||||
|
sleep_ms(2000); // Give time for USB serial
|
||||||
|
|
||||||
// Initialize communication LED
|
// Initialize communication LED
|
||||||
comms_led_init();
|
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
|
// Setup BME280
|
||||||
bme280_init(&bem_config, i2c1, 14, 15);
|
bme280_init(&bem_config, i2c1, 14, 15);
|
||||||
|
|
|
||||||
255
tcp_client.c
Normal file
255
tcp_client.c
Normal 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
45
tcp_client.h
Normal 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
|
||||||
Loading…
Add table
Add a link
Reference in a new issue