diff --git a/src/main.zig b/src/main.zig index 2cff5a0..1f4644e 100644 --- a/src/main.zig +++ b/src/main.zig @@ -7,7 +7,7 @@ const DeviceAction = enum { toggle, }; -fn sendWemoCommand(ip: []const u8, action: DeviceAction, allocator: std.mem.Allocator) !void { +fn sendWemoCommand(url_base: []const u8, action: DeviceAction, allocator: std.mem.Allocator) !void { const state = switch (action) { .on => "1", .off => "0", @@ -27,13 +27,13 @@ fn sendWemoCommand(ip: []const u8, action: DeviceAction, allocator: std.mem.Allo , .{state}); defer allocator.free(soap_body); - const url = try std.fmt.allocPrint(allocator, "http://{s}/upnp/control/basicevent1", .{ip}); + const url = try std.fmt.allocPrint(allocator, "{s}/upnp/control/basicevent1", .{url_base}); defer allocator.free(url); var client = std.http.Client{ .allocator = allocator }; defer client.deinit(); - std.log.debug("sending wemo command to {s}, action={s}", .{ ip, @tagName(action) }); + std.log.debug("sending wemo command to {s}, action={s}", .{ url_base, @tagName(action) }); const res = try client.fetch(.{ .method = .POST, .payload = soap_body, @@ -47,18 +47,72 @@ fn sendWemoCommand(ip: []const u8, action: DeviceAction, allocator: std.mem.Allo if (res.status == .ok) { var stdout_writer = std.fs.File.stdout().writer(&.{}); const stdout = &stdout_writer.interface; - try stdout.print("WeMo command sent: IP={s}, Action={s}\n", .{ ip, @tagName(action) }); + try stdout.print("WeMo command sent: url={s}, Action={s}\n", .{ url_base, @tagName(action) }); } else { var stderr_writer = std.fs.File.stderr().writer(&.{}); const stderr = &stderr_writer.interface; - try stderr.print("WeMo command sent: IP={s}, Action={s}\n", .{ ip, @tagName(action) }); + try stderr.print("WeMo command sent: url={s}, Action={s}\n", .{ url_base, @tagName(action) }); } } fn loadDeviceConfig(allocator: std.mem.Allocator) !std.StringHashMap([]const u8) { var devices = std.StringHashMap([]const u8).init(allocator); - const file = try std.fs.cwd().openFile("devices.txt", .{}); + var stderr_writer = std.fs.File.stderr().writer(&.{}); + const stderr = &stderr_writer.interface; + // Try controlData.json first + if (std.fs.cwd().openFile("controlData.json", .{})) |file| { + defer file.close(); + + const content = try file.readToEndAlloc(allocator, 1024 * 1024); + defer allocator.free(content); + + const parsed = std.json.parseFromSlice(std.json.Value, allocator, content, .{}) catch |err| { + try stderr.print( + "Failed to parse controlData.json: {}. Ignoring controlData.json, looking for devices.txt", + .{err}, + ); + return loadDevicesFromTxt(allocator); + }; + defer parsed.deinit(); + + const root = parsed.value.object; + const device_array = root.get("devices").?.array; + + for (device_array.items) |device| { + const device_obj = device.object; + const name = device_obj.get("name").?.string; + const url = device_obj.get("url").?.string; + + if (name.len > 0) { + const name_copy = try allocator.alloc(u8, name.len); + _ = std.ascii.lowerString(name_copy, name); + try devices.put(name_copy, try allocator.dupe(u8, url)); + std.log.debug("Loaded device: '{s}' -> '{s}'\n", .{ name, url }); + } + } + + return devices; + } else |_| { + return loadDevicesFromTxt(allocator); + } +} + +fn loadDevicesFromTxt(allocator: std.mem.Allocator) !std.StringHashMap([]const u8) { + var devices = std.StringHashMap([]const u8).init(allocator); + + const file = std.fs.cwd().openFile("devices.txt", .{}) catch |err| switch (err) { + error.FileNotFound => { + var stderr_writer = std.fs.File.stderr().writer(&.{}); + const stderr = &stderr_writer.interface; + try stderr.print( + \\Error: could not load configuration. Please make sure controlData.json or + \\devices.txt is available in the directory with this program + , .{}); + return error.ConfigurationNotAvailable; + }, + else => return err, + }; defer file.close(); const content = try file.readToEndAlloc(allocator, 1024 * 1024); @@ -71,9 +125,11 @@ fn loadDeviceConfig(allocator: std.mem.Allocator) !std.StringHashMap([]const u8) if (std.mem.indexOf(u8, trimmed, "=")) |eq_pos| { const name = std.mem.trim(u8, trimmed[0..eq_pos], " \t"); - const ip = std.mem.trim(u8, trimmed[eq_pos + 1 ..], " \t"); - if (name.len > 0 and ip.len > 0) { - try devices.put(try allocator.dupe(u8, name), try allocator.dupe(u8, ip)); + const url_base = std.mem.trim(u8, trimmed[eq_pos + 1 ..], " \t"); + if (name.len > 0 and url_base.len > 0) { + const name_copy = try allocator.alloc(u8, name.len); + _ = std.ascii.lowerString(name_copy, name); + try devices.put(name, try allocator.dupe(u8, url_base)); } } } @@ -92,19 +148,23 @@ fn parseAction(action_words: [][]const u8) ?DeviceAction { return null; } -fn extractDevice(allocator: std.mem.Allocator, object_words: [][]const u8, devices: *std.StringHashMap([]const u8)) ?[]const u8 { +fn extractDevice(allocator: std.mem.Allocator, object_words: [][]const u8, devices: *std.StringHashMap([]const u8)) !?[]const u8 { if (object_words.len == 0) return null; - if (object_words.len == 1) { - return devices.get(object_words[0]); - } else if (object_words.len == 2) { - var device_name = std.ArrayList(u8){}; - defer device_name.deinit(allocator); - device_name.appendSlice(allocator, object_words[0]) catch return null; - device_name.append(allocator, ' ') catch return null; - device_name.appendSlice(allocator, object_words[1]) catch return null; - return devices.get(device_name.items); + var total_size: usize = 0; + for (object_words) |word| + total_size += word.len + 1; + var allocating_writer = try std.Io.Writer.Allocating.initCapacity(allocator, total_size); + defer allocating_writer.deinit(); + const writer = &allocating_writer.writer; + for (object_words, 1..) |word, i| { + var buf: [256]u8 = undefined; + const lower_string = std.ascii.lowerString(&buf, word); + try writer.print("{s}{s}", .{ lower_string, if (i < object_words.len) " " else "" }); } + + if (devices.get(allocating_writer.written())) |url_base| return url_base; + std.log.warn("Could not find device '{s}'", .{allocating_writer.written()}); return null; } @@ -165,9 +225,15 @@ pub fn main() !void { }; defer gpa.allocator().free(object_words); + std.debug.print("Object words: ", .{}); + for (object_words) |word| { + std.debug.print("'{s}' ", .{word}); + } + std.debug.print("\n", .{}); + if (parseAction(action_words)) |action| { - if (extractDevice(allocator, object_words, &devices)) |ip| { - try sendWemoCommand(ip, action, gpa.allocator()); + if (try extractDevice(allocator, object_words, &devices)) |url_base| { + try sendWemoCommand(url_base, action, gpa.allocator()); } else { try stdout.print("Device not found in configuration\n", .{}); }