From 100d49b4c7a4db3e3a2859cb221693636b5f90de Mon Sep 17 00:00:00 2001 From: Emil Lerch Date: Tue, 9 Dec 2025 21:13:52 -0800 Subject: [PATCH] working implementation --- src/main.zig | 174 ++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 122 insertions(+), 52 deletions(-) diff --git a/src/main.zig b/src/main.zig index 9c4fcb5..7aab4e8 100644 --- a/src/main.zig +++ b/src/main.zig @@ -228,37 +228,55 @@ fn getDevices(allocator: std.mem.Allocator, id_token: []const u8, username: []co } /// Starts recirculation for the specified device with given duration -fn startRecirculation(allocator: std.mem.Allocator, id_token: []const u8, serial_number: []const u8, duration_minutes: u32) !void { +fn setRecirculation(allocator: std.mem.Allocator, id_token: []const u8, thing_name: []const u8, duration_minutes: ?u32) !void { var client = http.Client{ .allocator = allocator }; defer client.deinit(); const url = try std.fmt.allocPrint(allocator, \\{s}/{s}/shadow - , .{ shadow_api_url, serial_number }); + , .{ shadow_api_url, thing_name }); defer allocator.free(url); - const body = try std.fmt.allocPrint(allocator, - \\{{"recirculation_duration":{d},"set_recirculation_enabled":true}} - , .{duration_minutes}); + const body = if (duration_minutes) |min| + try std.fmt.allocPrint(allocator, + \\{{"recirculation_duration":"{d}","set_recirculation_enabled":true}} + , .{min}) + else + try allocator.dupe(u8, + \\{"set_recirculation_enabled":false} + ); defer allocator.free(body); + // std.debug.print("DEBUG: PATCH URL: {s}\n", .{url}); + // std.debug.print("DEBUG: PATCH Body: {s}\n", .{body}); + const uri = try std.Uri.parse(url); const auth_header = try std.fmt.allocPrint(allocator, \\Bearer {s} , .{id_token}); defer allocator.free(auth_header); + var response_buf: [4096]u8 = undefined; + var writer = std.Io.Writer.fixed(&response_buf); const result = try client.fetch(.{ .location = .{ .uri = uri }, .method = .PATCH, .payload = body, + .response_writer = &writer, .extra_headers = &.{ .{ .name = "Authorization", .value = auth_header }, .{ .name = "Content-Type", .value = "application/json" }, }, }); - if (result.status != .ok) return error.StartRecirculationFailed; + const response_body = response_buf[0..writer.end]; + // std.debug.print("DEBUG: PATCH Response Status: {}\n", .{result.status}); + // std.debug.print("DEBUG: PATCH Response Body: {s}\n", .{response_body}); + + if (result.status != .ok) { + std.debug.print("PATCH failed - Status: {}, Body: {s}\n", .{ result.status, response_body }); + return error.StartRecirculationFailed; + } } const DeviceShadow = struct { @@ -372,6 +390,10 @@ pub fn main() !void { defer _ = gpa.deinit(); const allocator = gpa.allocator(); + const args = try std.process.argsAlloc(allocator); + defer std.process.argsFree(allocator, args); + const debug_mode = args.len > 1 and std.mem.eql(u8, args[1], "--debug"); + var stdout_buffer: [1024]u8 = undefined; var stdout_writer = std.fs.File.stdout().writer(&stdout_buffer); const stdout = &stdout_writer.interface; @@ -393,6 +415,12 @@ pub fn main() !void { defer allocator.free(auth.id_token); defer allocator.free(auth.user_uuid); try stdout.print("āœ“ User UUID: {s}\n\n", .{auth.user_uuid}); + + if (debug_mode) { + try stdout.print("\n=== DEBUG MODE ===\n", .{}); + try stdout.print("IdToken:\n{s}\n\n", .{auth.id_token}); + } + try stdout.print("šŸ“± Fetching devices...\n", .{}); try stdout.flush(); var result = try getDevices( @@ -422,54 +450,96 @@ pub fn main() !void { 1 => { const device = result.devices[0]; - if (device.serial_id) |sid| { - try stdout.print("šŸ” Checking recirculation status for {?s}...\n", .{device.device_name}); - try stdout.flush(); - - var status = try getRecirculationStatus(allocator, auth.id_token, sid); - defer status.deinit(); - - try stdout.print("\n{f}", .{status}); - - const recirc_enabled = status.recirculation_enabled orelse false; - - if (recirc_enabled) { - try stdout.print("\nāœ“ Recirculation is already active\n", .{}); - } else { - try stdout.print("\n🚿 Starting 15-minute recirculation...\n", .{}); - try stdout.flush(); - - startRecirculation( - allocator, - auth.id_token, - sid, - 15, - ) catch |e| { - try stderr.print("āŒ Failed to start recirculation\n", .{}); - try stderr.flush(); - return e; - }; - - try stdout.print("āœ“ Recirculation command sent\n", .{}); - try stdout.print("ā³ Waiting 20 seconds for device to respond...\n", .{}); - try stdout.flush(); - - std.Thread.sleep(20 * std.time.ns_per_s); - - var post_command_state = try getRecirculationStatus( - allocator, - auth.id_token, - sid, - ); - defer post_command_state.deinit(); - try stdout.print("\n{f}", .{post_command_state}); - } - try stdout.flush(); - } else { - try stderr.print("āŒ No serial_id found for device\n", .{}); + if (device.serial_id == null or + device.thing_name == null) + { + try stderr.print("āŒ thing_name and serial_id both required\n", .{}); try stderr.flush(); - return error.NoSerialId; + return error.SerialIdAndThingNameRequired; } + + const sid = device.serial_id.?; + const tn = device.thing_name.?; + + try stdout.print("šŸ” Checking recirculation status for {?s}...\n", .{device.device_name}); + try stdout.flush(); + + var status = try getRecirculationStatus(allocator, auth.id_token, sid); + defer status.deinit(); + + try stdout.print("\n{f}", .{status}); + + if (debug_mode) { + try stdout.print("\n=== CURL COMMANDS ===\n", .{}); + try stdout.print("Reset recirculation:\n", .{}); + try stdout.print("curl -X PATCH '{s}/{s}/shadow' \\\n", .{ shadow_api_url, tn }); + try stdout.print(" -H 'Authorization: Bearer {s}' \\\n", .{auth.id_token}); + try stdout.print(" -H 'Content-Type: application/json' \\\n", .{}); + try stdout.print(" -d '{{\"set_recirculation_enabled\":false}}'\n\n", .{}); + + try stdout.print("Start recirculation:\n", .{}); + try stdout.print("curl -X PATCH '{s}/{s}/shadow' \\\n", .{ shadow_api_url, tn }); + try stdout.print(" -H 'Authorization: Bearer {s}' \\\n", .{auth.id_token}); + try stdout.print(" -H 'Content-Type: application/json' \\\n", .{}); + try stdout.print(" -d '{{\"recirculation_duration\":15,\"set_recirculation_enabled\":true}}'\n\n", .{}); + try stdout.flush(); + return; + } + + const recirc_enabled = status.recirculation_enabled orelse false; + + if (recirc_enabled) { + try stdout.print("\nāœ“ Recirculation is already active\n", .{}); + } else { + try stdout.print("\n🚿 Starting 15-minute recirculation...\n", .{}); + try stdout.flush(); + + if (status.set_recirculation_enabled) |en| { + if (en) { + try stdout.print("āš ļø set_recirculation_enabled is already true, resetting first...\n", .{}); + try stdout.flush(); + + // The shadow state doesn't seem to update. The react native + // application doesn't wait, it just yolo's here, so + // we'll do the same + try setRecirculation( + allocator, + auth.id_token, + sid, + null, + ); + + try stdout.print("ā³ Waiting 3 seconds...\n", .{}); + try stdout.flush(); + std.Thread.sleep(3 * std.time.ns_per_s); + } + } + setRecirculation( + allocator, + auth.id_token, + tn, + 15, + ) catch |e| { + try stderr.print("āŒ Failed to start recirculation\n", .{}); + try stderr.flush(); + return e; + }; + + try stdout.print("āœ“ Recirculation command sent\n", .{}); + try stdout.print("ā³ Waiting 20 seconds for device to respond...\n", .{}); + try stdout.flush(); + + std.Thread.sleep(20 * std.time.ns_per_s); + + var post_command_state = try getRecirculationStatus( + allocator, + auth.id_token, + sid, + ); + defer post_command_state.deinit(); + try stdout.print("\n{f}", .{post_command_state}); + } + try stdout.flush(); }, else => { try stderr.print("āŒ More than one device found for user, not currently equipped to handle this\n", .{});