feat: make it nice

This commit is contained in:
2025-04-04 09:58:59 +02:00
parent a2ba61d570
commit d55ae2851a
3 changed files with 343 additions and 166 deletions

230
src/httpClient.zig Normal file
View File

@@ -0,0 +1,230 @@
const std = @import("std");
const cNet = @cImport({
@cInclude("pico/cyw43_arch.h");
@cInclude("lwip/init.h");
@cInclude("lwip/tcp.h");
@cInclude("lwip/dns.h");
});
fn print(text: []const u8) void {
var buffer: [4096]u8 = undefined;
var fba = std.heap.FixedBufferAllocator.init(&buffer);
const allocator = fba.allocator();
const c_text = allocator.alloc(u8, text.len + 1) catch unreachable;
allocator.free(c_text);
for (0..text.len) |i| {
c_text[i] = text[i];
}
c_text[c_text.len - 1] = 0;
_ = cNet.printf(c_text.ptr);
_ = cNet.printf("\r\n");
}
fn print_usize(num: usize) void {
_ = cNet.printf("%d", num);
_ = cNet.printf("\r\n");
}
pub const HTTP_METHOD = enum {
GET,
POST,
PUT,
PATCH,
DELETE,
};
pub const HttpRequest = struct {
url: std.Uri,
method: HTTP_METHOD,
body: []const u8,
headers: []const HttpHeader,
};
pub const HttpHeader = struct {
name: []const u8,
value: []const u8,
};
pub const HttpContext = struct { client: *const Client, request: *const HttpRequest };
fn tcp_recv_callback(_: ?*anyopaque, pcb: ?*cNet.tcp_pcb, p1: ?*cNet.pbuf, _: cNet.err_t) callconv(.C) cNet.err_t {
print("asd2");
if (p1 == null) {
print("asd3");
_ = cNet.tcp_close(pcb);
return cNet.ERR_OK;
}
defer {
_ = cNet.pbuf_free(p1);
}
print("asd4");
// std.debug.print("Received data: {s}\n", .{@ptrCast([*]const u8, p1.?.payload)});
return cNet.ERR_OK;
}
fn send_http_request(pcb: ?*cNet.tcp_pcb, context: *HttpContext) !void {
print("Preparing request");
print_usize(@intFromPtr(context));
// const request = "POST /todo-channel-name HTTP/1.1\r\nHost: ntfy.sh\r\nTitle: Csengo\r\nContent-Length: 6\r\n\r\nCsengo";
print("Getting request");
if (context.request.url.host != null) {
print("HOST");
} else {
print("NOT HOST");
}
const host = if (context.request.url.host) |host| host else unreachable;
const host_string = switch (host) {
.raw => |v| v,
.percent_encoded => |v| v,
};
print("Got host");
print(host_string);
print("Getting path");
const path_string = switch (context.request.url.path) {
.raw => |v| v,
.percent_encoded => |v| v,
};
print("Got path");
print(path_string);
const request_data = .{ @tagName(context.request.method), path_string, host_string, context.request.body.len, context.request.body };
// const raw_request = try std.fmt.allocPrint(context.client.allocator, "{} {} HTTP/1.1\r\nHost: {}\r\nContent-Length: {}\r\n\r\n{}", request_data);
const raw_request = try std.fmt.allocPrint(context.client.allocator, "{s} {s} HTTP/1.1\r\nHost: {s}\r\nTitle: Csengo\r\nContent-Length: {}\r\n\r\n{s}", request_data);
print("Sending request");
print(raw_request);
defer context.client.allocator.free(raw_request);
_ = cNet.tcp_write(pcb, raw_request.ptr, @intCast(raw_request.len), cNet.TCP_WRITE_FLAG_COPY);
_ = cNet.tcp_output(pcb);
}
fn tcp_connected_callback(context: ?*anyopaque, pcb: ?*cNet.tcp_pcb, err: cNet.err_t) callconv(.C) cNet.err_t {
print("tcp_connected_callback");
print_usize(@intFromPtr(context.?));
print(if (err != cNet.ERR_OK) "not ok" else "ok");
if (err != cNet.ERR_OK) {
return err;
}
// std.debug.print("Connected to server\n", .{});
print("Connected to server");
_ = cNet.tcp_recv(pcb, tcp_recv_callback);
// TODO: handle this error
print("asd1");
const http_context: *HttpContext = @ptrCast(@alignCast(context));
print_usize(@intFromPtr(http_context));
send_http_request(pcb, http_context) catch unreachable;
print("asd2");
return cNet.ERR_OK;
}
fn open_tcp_connection(ipaddr: [*c]const cNet.struct_ip4_addr, context: *HttpContext, context2: ?*anyopaque) void {
print("open_tcp_connection");
if (context.request.url.host != null) {
print("HOST");
} else {
print("NOT HOST");
}
print_usize(@intFromPtr(context));
print_usize(@intFromPtr(context2.?));
const pcb = cNet.tcp_new();
if (pcb == null) {
return;
}
if (context.request.url.host != null) {
print("HOST");
} else {
print("NOT HOST");
}
cNet.tcp_arg(pcb, context2);
_ = cNet.tcp_connect(pcb, ipaddr, context.request.url.port orelse 80, tcp_connected_callback);
}
fn dns_callback(_: [*c]const u8, ipaddr: [*c]const cNet.struct_ip4_addr, context: ?*anyopaque) callconv(.C) void {
print("DNS resolution returned!");
if (ipaddr) |addr| {
print("DNS resolution successful!");
const http_context: *HttpContext = @ptrCast(@alignCast(context));
print_usize(@intFromPtr(http_context));
print(http_context.request.url.scheme);
if (http_context.request.url.host != null) {
print("HOST");
} else {
print("NOT HOST");
}
open_tcp_connection(addr, http_context, context);
}
}
pub const Client = struct {
allocator: std.mem.Allocator,
pub fn sendRequest(client: *const Client, request: *const HttpRequest) !void {
print("Client.sendRequest");
const asd: *const HttpContext = &.{ .client = client, .request = request };
const context: *HttpContext = @constCast(asd);
print_usize(@intFromPtr(context));
cNet.cyw43_arch_lwip_begin();
var cached_address = cNet.ip_addr_t{};
if (context.request.url.host != null) {
print("HOST");
} else {
print("NOT HOST");
}
const host = if (request.url.host) |host| host else unreachable;
const host_string = switch (host) {
.raw => |v| v,
.percent_encoded => |v| v,
};
if (context.request.url.host != null) {
print("HOST");
} else {
print("NOT HOST");
}
const host_c_string = try client.allocator.alloc(u8, host_string.len + 1);
defer client.allocator.free(host_c_string);
print_usize(host_string.len);
print_usize(host_c_string.len);
host_c_string[host_c_string.len - 1] = 0;
// @memcpy(host_c_string, host_string);
for (0..host_string.len) |i| {
host_c_string[i] = host_string[i];
}
if (context.request.url.host != null) {
print("HOST");
} else {
print("NOT HOST");
}
print("Request host is");
print(host_c_string);
print_usize(host_string.len);
print_usize(host_c_string.len);
print_usize(@intFromPtr(context));
if (context.request.url.host != null) {
print("HOST");
} else {
print("NOT HOST");
}
print(context.request.url.scheme);
const result = cNet.dns_gethostbyname(@ptrCast(host_c_string), &cached_address, dns_callback, @ptrCast(@alignCast(context)));
if (result == cNet.ERR_OK) {
print("DNS resolution returned with OK");
} else if (result == cNet.ERR_INPROGRESS) {
print("DNS resolution returned with INPROGRESS");
} else if (result == cNet.ERR_ARG) {
print("DNS resolution returned with ERR_ARG");
}
print("Calling cyw43_arch_lwip_end");
cNet.cyw43_arch_lwip_end();
print("Ending sendRequest...");
while (true) {
cNet.sleep_ms(2000);
print("loop...");
}
}
};

View File

@@ -2,17 +2,10 @@ pub const p = @cImport({
@cInclude("pico.h"); @cInclude("pico.h");
@cInclude("stdio.h"); @cInclude("stdio.h");
@cInclude("pico/stdlib.h"); @cInclude("pico/stdlib.h");
@cInclude("pico/sleep.h");
// PICO W specific header
@cInclude("pico/cyw43_arch.h");
}); });
const c = @cImport({ const httpClient = @import("httpClient.zig");
@cInclude("pico/cyw43_arch.h"); const platform = @import("platform.zig");
@cInclude("lwip/init.h");
@cInclude("lwip/tcp.h");
@cInclude("lwip/dns.h");
});
const std = @import("std"); const std = @import("std");
const BUTTON_PIN = 15; const BUTTON_PIN = 15;
@@ -20,44 +13,29 @@ const GPIO_IN = false;
pub const std_options: std.Options = .{ .page_size_max = 4 * 1024, .page_size_min = 4 * 1024 }; pub const std_options: std.Options = .{ .page_size_max = 4 * 1024, .page_size_min = 4 * 1024 };
fn print(text: []const u8) void {
_ = p.printf(text.ptr);
_ = p.printf("\r\n");
}
// Basically the pico_w blink sample // Basically the pico_w blink sample
export fn main() c_int { export fn main() c_int {
p.sleep_ms(5000);
_ = p.stdio_init_all(); _ = p.stdio_init_all();
p.sleep_ms(5000);
print("Starting ...");
p.gpio_init(BUTTON_PIN); p.gpio_init(BUTTON_PIN);
p.gpio_set_dir(BUTTON_PIN, GPIO_IN); p.gpio_set_dir(BUTTON_PIN, GPIO_IN);
if (p.cyw43_arch_init() != 0) { platform.init_arch();
return -1;
}
p.sleep_run_from_xosc(); // platform.sleep_until_gpio_high(BUTTON_PIN);
p.sleep_goto_dormant_until_edge_high(BUTTON_PIN);
p.sleep_power_up();
print("Connecting to wifi...");
platform.connect_wifi(@embedFile("wifi.txt"), @embedFile("password.txt"));
print("Connected!");
p.cyw43_arch_enable_sta_mode(); main2() catch unreachable;
if (p.cyw43_arch_wifi_connect_timeout_ms(@embedFile("wifi.txt"), @embedFile("password.txt"), p.CYW43_AUTH_WPA2_AES_PSK, 10000) == 1) {
p.cyw43_arch_gpio_put(p.CYW43_WL_GPIO_LED_PIN, true);
return -1;
}
p.cyw43_arch_gpio_put(p.CYW43_WL_GPIO_LED_PIN, true);
p.sleep_ms(200);
p.cyw43_arch_gpio_put(p.CYW43_WL_GPIO_LED_PIN, false);
p.sleep_ms(200);
// while (true) {
// p.cyw43_arch_gpio_put(p.CYW43_WL_GPIO_LED_PIN, true);
// p.sleep_ms(500);
// p.cyw43_arch_gpio_put(p.CYW43_WL_GPIO_LED_PIN, false);
// p.sleep_ms(1000);
// }
// _ = p.printf("Hello world\n");
// httpRequest();
main2();
// p.cyw43_arch_gpio_put(p.CYW43_WL_GPIO_LED_PIN, true); // p.cyw43_arch_gpio_put(p.CYW43_WL_GPIO_LED_PIN, true);
// p.sleep_ms(200); // p.sleep_ms(200);
@@ -84,144 +62,72 @@ export fn main() c_int {
// p.sleep_power_up(); // p.sleep_power_up();
// _ = p.stdio_init_all(); // _ = p.stdio_init_all();
// printf("Now awake for 10s\n"); // printf("Now awake for 10s\n");
p.cyw43_arch_gpio_put(p.CYW43_WL_GPIO_LED_PIN, true); platform.set_cyw43_led(true);
p.sleep_ms(5000); p.sleep_ms(5000);
// while (p.gpio_get(BUTTON_PIN)) { // while (p.gpio_get(BUTTON_PIN)) {
// p.sleep_ms(50); // p.sleep_ms(50);
// } // }
p.cyw43_arch_gpio_put(p.CYW43_WL_GPIO_LED_PIN, false); platform.set_cyw43_led(false);
p.sleep_ms(2000); p.sleep_ms(2000);
} }
} }
fn httpRequest() void { // fn tcp_recv_callback(_: ?*anyopaque, pcb: ?*c.tcp_pcb, p1: ?*c.pbuf, _: c.err_t) callconv(.C) c.err_t {
// Create a general purpose allocator // if (p1 == null) {
// var gpa = std.heap.GeneralPurposeAllocator(.{}){}; // _ = c.tcp_close(pcb);
// defer _ = gpa.deinit(); // return c.ERR_OK;
// }
// defer {
// _ = c.pbuf_free(p1);
// }
// // std.debug.print("Received data: {s}\n", .{@ptrCast([*]const u8, p1.?.payload)});
// return c.ERR_OK;
// }
// //
// // Create a HTTP client // fn send_http_request(pcb: ?*c.tcp_pcb) void {
// var client = std.http.Client{ .allocator = gpa.allocator() }; // const request = "POST /todo-channel-name HTTP/1.1\r\nHost: ntfy.sh\r\nTitle: Csengo\r\nContent-Length: 6\r\n\r\nCsengo";
// defer client.deinit(); // _ = c.tcp_write(pcb, request, request.len, c.TCP_WRITE_FLAG_COPY);
// _ = c.tcp_output(pcb);
// }
// //
// // Allocate a buffer for server headers // fn tcp_connected_callback(_: ?*anyopaque, pcb: ?*c.tcp_pcb, err: c.err_t) callconv(.C) c.err_t {
// var buf: [4096]u8 = undefined; // if (err != c.ERR_OK) {
// return err;
// }
// // std.debug.print("Connected to server\n", .{});
// _ = c.tcp_recv(pcb, tcp_recv_callback);
// send_http_request(pcb);
// return c.ERR_OK;
// }
// //
// // Start the HTTP request // fn dns_callback(_: [*c]const u8, ipaddr: [*c]const c.struct_ip4_addr, _: ?*anyopaque) callconv(.C) void {
// const uri = try std.Uri.parse("https://www.google.com?q=zig"); // if (ipaddr) |addr| {
// var req = try client.open(.GET, uri, .{ .server_header_buffer = &buf }); // const pcb = c.tcp_new();
// defer req.deinit(); // if (pcb == null) {
// return;
// }
// _ = c.tcp_connect(pcb, addr, 80, tcp_connected_callback);
// }
// }
pub fn main2() !void {
// c.cyw43_arch_lwip_begin();
// var cached_address = c.ip_addr_t{};
// //
// // Send the HTTP request headers // const result = c.dns_gethostbyname("ntfy.sh", &cached_address, dns_callback, null);
// try req.send();
// // Finish the body of a request
// try req.finish();
// //
// // Waits for a response from the server and parses any headers that are sent // if (result == c.ERR_OK) {} else if (result == c.ERR_INPROGRESS) {} else if (result == c.ERR_ARG) {}
// try req.wait(); // c.cyw43_arch_lwip_end();
//
// std.debug.print("status={d}\n", .{req.response.status});
}
fn tcp_recv_callback(_: ?*anyopaque, pcb: ?*c.tcp_pcb, p1: ?*c.pbuf, _: c.err_t) callconv(.C) c.err_t { var store: [8192]u8 = undefined;
if (p1 == null) { var fba = std.heap.FixedBufferAllocator.init(&store);
_ = c.tcp_close(pcb); const allocator = fba.allocator();
return c.ERR_OK;
}
defer {
_ = c.pbuf_free(p1);
}
// std.debug.print("Received data: {s}\n", .{@ptrCast([*]const u8, p1.?.payload)});
return c.ERR_OK;
}
fn send_http_request(pcb: ?*c.tcp_pcb) void { var client = &httpClient.Client{ .allocator = allocator };
const request = "POST /todo-channel-name HTTP/1.1\r\nHost: ntfy.sh\r\nTitle: Csengo\r\nContent-Length: 6\r\n\r\nCsengo";
_ = c.tcp_write(pcb, request, request.len, c.TCP_WRITE_FLAG_COPY); const request = &httpClient.HttpRequest{ .method = .POST, .url = try std.Uri.parse("http://ntfy.sh/todo-channel-name"), .body = "Csengo", .headers = &[_]httpClient.HttpHeader{
_ = c.tcp_output(pcb); httpClient.HttpHeader{ .name = "Title", .value = "Csengo" },
} } };
try client.sendRequest(request);
fn tcp_connected_callback(_: ?*anyopaque, pcb: ?*c.tcp_pcb, err: c.err_t) callconv(.C) c.err_t {
if (err != c.ERR_OK) {
return err;
}
// std.debug.print("Connected to server\n", .{});
_ = c.tcp_recv(pcb, tcp_recv_callback);
send_http_request(pcb);
return c.ERR_OK;
}
fn dns_callback(_: [*c]const u8, ipaddr: [*c]const c.struct_ip4_addr, _: ?*anyopaque) callconv(.C) void {
p.cyw43_arch_gpio_put(p.CYW43_WL_GPIO_LED_PIN, true);
p.sleep_ms(3000);
p.cyw43_arch_gpio_put(p.CYW43_WL_GPIO_LED_PIN, false);
p.sleep_ms(1000);
if (ipaddr) |addr| {
const pcb = c.tcp_new();
if (pcb == null) {
return;
}
_ = c.tcp_connect(pcb, addr, 80, tcp_connected_callback);
}
}
pub fn main2() void {
p.cyw43_arch_gpio_put(p.CYW43_WL_GPIO_LED_PIN, true);
p.sleep_ms(300);
p.cyw43_arch_gpio_put(p.CYW43_WL_GPIO_LED_PIN, false);
p.sleep_ms(2000);
// c.lwip_init();
// c.dns_init();
c.cyw43_arch_lwip_begin();
p.cyw43_arch_gpio_put(p.CYW43_WL_GPIO_LED_PIN, true);
p.sleep_ms(1000);
p.cyw43_arch_gpio_put(p.CYW43_WL_GPIO_LED_PIN, false);
p.sleep_ms(2000);
// Set DNS server (Google DNS: 8.8.8.8)
// const dns_server = c.ip_addr_t{
// .u_addr = .{
// .ip4 = c.ip4_addr{
// .addr = c.PP_HTONL(0x08080808), // 8.8.8.8 in hexadecimal, network byte order
// },
// },
// .type = c.IPADDR_TYPE_V4,
// };
// c.dns_setserver(0, &dns_server);
var cached_address = c.ip_addr_t{};
const result = c.dns_gethostbyname("ntfy.sh", &cached_address, dns_callback, null);
p.cyw43_arch_gpio_put(p.CYW43_WL_GPIO_LED_PIN, true);
p.sleep_ms(1000);
p.cyw43_arch_gpio_put(p.CYW43_WL_GPIO_LED_PIN, false);
p.sleep_ms(2000);
if (result == c.ERR_OK) {
p.cyw43_arch_gpio_put(p.CYW43_WL_GPIO_LED_PIN, true);
p.sleep_ms(1000);
p.cyw43_arch_gpio_put(p.CYW43_WL_GPIO_LED_PIN, false);
p.sleep_ms(2000);
} else if (result == c.ERR_INPROGRESS) {
p.cyw43_arch_gpio_put(p.CYW43_WL_GPIO_LED_PIN, true);
p.sleep_ms(3000);
p.cyw43_arch_gpio_put(p.CYW43_WL_GPIO_LED_PIN, false);
p.sleep_ms(2000);
} else if (result == c.ERR_ARG) {
p.cyw43_arch_gpio_put(p.CYW43_WL_GPIO_LED_PIN, true);
p.sleep_ms(5000);
p.cyw43_arch_gpio_put(p.CYW43_WL_GPIO_LED_PIN, false);
p.sleep_ms(2000);
}
c.cyw43_arch_lwip_end();
p.cyw43_arch_gpio_put(p.CYW43_WL_GPIO_LED_PIN, true);
p.sleep_ms(300);
p.cyw43_arch_gpio_put(p.CYW43_WL_GPIO_LED_PIN, false);
p.sleep_ms(300);
p.cyw43_arch_gpio_put(p.CYW43_WL_GPIO_LED_PIN, true);
p.sleep_ms(300);
// p.sleep_ms(300);
} }

41
src/platform.zig Normal file
View File

@@ -0,0 +1,41 @@
pub const p = @cImport({
@cInclude("pico.h");
@cInclude("stdio.h");
@cInclude("pico/stdlib.h");
@cInclude("pico/sleep.h");
// PICO W specific header
@cInclude("pico/cyw43_arch.h");
});
fn print(text: []const u8) void {
_ = p.printf(text.ptr);
_ = p.printf("\r\n");
}
pub fn init_arch() void {
if (p.cyw43_arch_init() != 0) {
//TODO: error
print("error in init_arch");
return;
}
}
pub fn connect_wifi(ssid: []const u8, password: []const u8) void {
p.cyw43_arch_enable_sta_mode();
if (p.cyw43_arch_wifi_connect_timeout_ms(ssid.ptr, password.ptr, p.CYW43_AUTH_WPA2_AES_PSK, 10000) == 1) {
p.cyw43_arch_gpio_put(p.CYW43_WL_GPIO_LED_PIN, true);
//TODO: error
print("error in connect_wifi");
return;
}
}
pub fn set_cyw43_led(input: bool) void {
p.cyw43_arch_gpio_put(p.CYW43_WL_GPIO_LED_PIN, input);
}
pub fn sleep_until_gpio_high(button_pin: u32) void {
p.sleep_run_from_xosc();
p.sleep_goto_dormant_until_edge_high(button_pin);
p.sleep_power_up();
}