yaes/src/RotCtl.zig

221 lines
7.4 KiB
Zig
Raw Normal View History

const std = @import("std");
const config = @import("./Config.zig").global;
const LabjackYaesu = @import("./LabjackYaesu.zig");
const RotCtl = @This();
const log = std.log.scoped(.RotCtl);
writer: std.io.BufferedWriter(512, std.net.Stream.Writer),
running: bool,
rotator: LabjackYaesu,
pub fn run(allocator: std.mem.Allocator) !void {
// var server = std.net.StreamServer.init(.{ .reuse_address = true });
// defer server.deinit();
const listen_addr = try std.net.Address.parseIp(
config.rotctl.listen_address,
config.rotctl.listen_port,
);
var server = listen_addr.listen(.{ .reuse_address = true }) catch {
log.err("Could not listen on {}. Is it already in use?", .{listen_addr});
return;
};
log.info("Listening for client on: {}", .{listen_addr});
var interface: RotCtl = .{
.writer = undefined,
.running = true,
.rotator = try LabjackYaesu.init(allocator),
};
while (true) {
const client = try server.accept();
defer {
log.info("disconnecting client", .{});
client.stream.close();
}
interface.writer = .{ .unbuffered_writer = client.stream.writer() };
interface.running = true;
defer interface.running = false;
log.info("client connected from {}", .{client.address});
var readbuffer = [_]u8{0} ** 512;
var fbs = std.io.fixedBufferStream(&readbuffer);
const reader = client.stream.reader();
while (interface.running) : (fbs.reset()) {
reader.streamUntilDelimiter(fbs.writer(), '\n', readbuffer.len) catch break;
try interface.handleHamlibCommand(
std.mem.trim(u8, fbs.getWritten(), &std.ascii.whitespace),
);
}
}
}
fn write(self: *RotCtl, buf: []const u8) !void {
try self.writer.writer().writeAll(buf);
try self.writer.flush();
}
fn replyStatus(self: *RotCtl, comptime status: HamlibErrorCode) !void {
try self.write(comptime status.replyFrame() ++ "\n");
}
fn printReply(self: *RotCtl, comptime fmt: []const u8, args: anytype) !void {
try self.writer.writer().print(fmt ++ "\n", args);
try self.writer.flush();
}
fn handleHamlibCommand(
self: *RotCtl,
command: []const u8,
) !void {
var tokens = std.mem.tokenizeScalar(u8, command, ' ');
const first = tokens.next().?;
if (first.len == 1 or first[0] == '\\') {
switch (first[0]) {
'q', 'Q' => {
self.running = false;
self.replyStatus(.okay) catch {};
self.rotator.stop();
},
'P' => {
const pos = self.rotator.position();
try self.printReply("{d:.1} {d:.1}", .{ pos.azimuth, pos.elevation });
},
'\\' => {
try self.parseLongCommand(first[1..], &tokens);
},
else => {
log.err("unknown short command '{s}'", .{command});
try self.replyStatus(.not_implemented);
},
}
} else {
try self.parseLongCommand(first, &tokens);
}
}
fn parseLongCommand(
self: *RotCtl,
command: []const u8,
tokens: *std.mem.TokenIterator(u8, .scalar),
) !void {
_ = tokens;
for (rotctl_commands) |check| {
if (check.long) |long| {
if (command.len >= long.len and std.mem.eql(u8, long, command)) {
log.warn("Unsupported long command {s}", .{command});
break;
}
}
} else {
log.warn("Unknown long command '{s}'", .{command});
}
return self.replyStatus(.not_supported);
}
const HamlibErrorCode = enum(u8) {
okay = 0,
invalid_parameter = 1,
invalid_configuration = 2,
out_of_memory = 3,
not_implemented = 4,
timeout = 5,
io_error = 6,
internal_error = 7,
protocol_error = 8,
command_rejected = 9,
parameter_truncated = 10,
not_supported = 11,
not_targetable = 12,
bus_error = 13,
bus_busy = 14,
invalid_arg = 15,
invalid_vfo = 16,
domain_error = 17,
deprecated = 18,
security = 19,
power = 20,
fn replyFrame(comptime self: HamlibErrorCode) []const u8 {
return std.fmt.comptimePrint(
"RPRT {d}",
.{-@as(i8, @intCast(@intFromEnum(self)))},
);
}
};
const HamlibCommand = struct {
short: ?u8 = null,
long: ?[]const u8 = null,
};
const rotctl_commands = [_]HamlibCommand{
.{ .short = 'q' }, // quit
.{ .short = 'Q' }, // quit
.{ .long = "AOS" },
.{ .long = "LOS" },
.{ .short = 'P', .long = "set_pos" }, // azimuth: f64, elevation: f64
.{ .short = 'p', .long = "get_pos" }, // return az: f64, el: f64
.{ .short = 'M', .long = "move" }, // direction: enum { up=2, down=4, left=8, right=16 }, speed: i8 (0-100 or -1)
.{ .short = 'S', .long = "stop" },
.{ .short = 'K', .long = "park" },
.{ .short = 'C', .long = "set_conf" }, // token: []const u8, value: []const u8
.{ .short = 'R', .long = "reset" }, // u1 (1 is reset all)
.{ .short = '_', .long = "get_info" }, // return Model name
.{ .short = 'K', .long = "park" },
.{ .long = "dump_state" }, // ???
.{ .short = '1', .long = "dump_caps" }, // ???
.{ .short = 'w', .long = "send_cmd" }, // []const u8, send serial command directly to the rotator
.{ .short = 'L', .long = "lonlat2loc" }, // return Maidenhead locator for given long: f64 and lat: f64, locator precision: u4 (2-12)
.{ .short = 'l', .long = "loc2lonlat" }, // the inverse of the above
.{ .short = 'D', .long = "dms2dec" }, // deg, min, sec, 0 (positive) or 1 (negative)
.{ .short = 'd', .long = "dec2dms" },
.{ .short = 'E', .long = "dmmm2dec" },
.{ .short = 'e', .long = "dec2dmmm" },
.{ .short = 'B', .long = "grb" },
.{ .short = 'A', .long = "a_sp2a_lp" },
.{ .short = 'a', .long = "d_sp2d_lp" },
.{ .long = "pause" },
};
// D, dms2dec 'Degrees' 'Minutes' 'Seconds' 'S/W'
// Returns 'Dec Degrees', a signed floating point value.
// 'Degrees' and 'Minutes' are integer values.
// 'Seconds' is a floating point value.
// 'S/W' is a flag with 1 indicating South latitude or West longitude and 0 North or East (the flag is needed as computers dont recognize a signed zero even though only the 'Degrees' value is typically signed in DMS notation).
// d, dec2dms 'Dec Degrees'
// Returns 'Degrees' 'Minutes' 'Seconds' 'S/W'.
// Values are as in dms2dec above.
// E, dmmm2dec 'Degrees' 'Dec Minutes' 'S/W'
// Returns 'Dec Degrees', a signed floating point value.
// 'Degrees' is an integer value.
// 'Dec Minutes' is a floating point value.
// 'S/W' is a flag as in dms2dec above.
// e, dec2dmmm 'Dec Deg'
// Returns 'Degrees' 'Minutes' 'S/W'.
// Values are as in dmmm2dec above.
// B, qrb 'Lon 1' 'Lat 1' 'Lon 2' 'Lat 2'
// Returns 'Distance' and 'Azimuth'.
// 'Distance' is in km.
// 'Azimuth' is in degrees.
// Supplied Lon/Lat values are signed floating point numbers.
// A, a_sp2a_lp 'Short Path Deg'
// Returns 'Long Path Deg'.
// Both the supplied argument and returned value are floating point values within the range of 0.00 to 360.00.
// Note: Supplying a negative value will return an error message.
// a, d_sp2d_lp 'Short Path km'
// Returns 'Long Path km'.
// Both the supplied argument and returned value are floating point values.
// pause 'Seconds'
// Pause for the given whole (integer) number of 'Seconds' before sending the next command to the rotator.