From d55ae2851ae40186838892dcc52c12ed29e96ad4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81d=C3=A1m=20Kov=C3=A1cs?= Date: Fri, 4 Apr 2025 09:58:59 +0200 Subject: [PATCH] feat: make it nice --- src/httpClient.zig | 230 +++++++++++++++++++++++++++++++++++++++++++ src/main.zig | 238 ++++++++++++++------------------------------- src/platform.zig | 41 ++++++++ 3 files changed, 343 insertions(+), 166 deletions(-) create mode 100644 src/httpClient.zig create mode 100644 src/platform.zig diff --git a/src/httpClient.zig b/src/httpClient.zig new file mode 100644 index 0000000..8d06163 --- /dev/null +++ b/src/httpClient.zig @@ -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..."); + } + } +}; diff --git a/src/main.zig b/src/main.zig index 2b7eb8d..80957bb 100644 --- a/src/main.zig +++ b/src/main.zig @@ -2,17 +2,10 @@ 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"); }); -const c = @cImport({ - @cInclude("pico/cyw43_arch.h"); - @cInclude("lwip/init.h"); - @cInclude("lwip/tcp.h"); - @cInclude("lwip/dns.h"); -}); +const httpClient = @import("httpClient.zig"); +const platform = @import("platform.zig"); const std = @import("std"); 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 }; +fn print(text: []const u8) void { + _ = p.printf(text.ptr); + _ = p.printf("\r\n"); +} + // Basically the pico_w blink sample export fn main() c_int { - p.sleep_ms(5000); _ = p.stdio_init_all(); + p.sleep_ms(5000); + print("Starting ..."); p.gpio_init(BUTTON_PIN); p.gpio_set_dir(BUTTON_PIN, GPIO_IN); - if (p.cyw43_arch_init() != 0) { - return -1; - } + platform.init_arch(); - p.sleep_run_from_xosc(); - p.sleep_goto_dormant_until_edge_high(BUTTON_PIN); - p.sleep_power_up(); + // platform.sleep_until_gpio_high(BUTTON_PIN); + print("Connecting to wifi..."); + platform.connect_wifi(@embedFile("wifi.txt"), @embedFile("password.txt")); + print("Connected!"); - p.cyw43_arch_enable_sta_mode(); - 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(); + main2() catch unreachable; // p.cyw43_arch_gpio_put(p.CYW43_WL_GPIO_LED_PIN, true); // p.sleep_ms(200); @@ -84,144 +62,72 @@ export fn main() c_int { // p.sleep_power_up(); // _ = p.stdio_init_all(); // 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); // while (p.gpio_get(BUTTON_PIN)) { // 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); } } -fn httpRequest() void { - // Create a general purpose allocator - // var gpa = std.heap.GeneralPurposeAllocator(.{}){}; - // defer _ = gpa.deinit(); +// fn tcp_recv_callback(_: ?*anyopaque, pcb: ?*c.tcp_pcb, p1: ?*c.pbuf, _: c.err_t) callconv(.C) c.err_t { +// if (p1 == null) { +// _ = c.tcp_close(pcb); +// 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 { +// 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); +// _ = c.tcp_output(pcb); +// } +// +// 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 { +// 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 { + // c.cyw43_arch_lwip_begin(); + // var cached_address = c.ip_addr_t{}; // - // // Create a HTTP client - // var client = std.http.Client{ .allocator = gpa.allocator() }; - // defer client.deinit(); + // const result = c.dns_gethostbyname("ntfy.sh", &cached_address, dns_callback, null); // - // // Allocate a buffer for server headers - // var buf: [4096]u8 = undefined; - // - // // Start the HTTP request - // const uri = try std.Uri.parse("https://www.google.com?q=zig"); - // var req = try client.open(.GET, uri, .{ .server_header_buffer = &buf }); - // defer req.deinit(); - // - // // Send the HTTP request headers - // 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 - // try req.wait(); - // - // 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 { - if (p1 == null) { - _ = c.tcp_close(pcb); - 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 { - 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); - _ = c.tcp_output(pcb); -} - -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); + // if (result == c.ERR_OK) {} else if (result == c.ERR_INPROGRESS) {} else if (result == c.ERR_ARG) {} + // c.cyw43_arch_lwip_end(); + + var store: [8192]u8 = undefined; + var fba = std.heap.FixedBufferAllocator.init(&store); + const allocator = fba.allocator(); + + var client = &httpClient.Client{ .allocator = allocator }; + + const request = &httpClient.HttpRequest{ .method = .POST, .url = try std.Uri.parse("http://ntfy.sh/todo-channel-name"), .body = "Csengo", .headers = &[_]httpClient.HttpHeader{ + httpClient.HttpHeader{ .name = "Title", .value = "Csengo" }, + } }; + try client.sendRequest(request); } diff --git a/src/platform.zig b/src/platform.zig new file mode 100644 index 0000000..1f966a1 --- /dev/null +++ b/src/platform.zig @@ -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(); +}