Compare commits

...

10 Commits

4 changed files with 132 additions and 30 deletions

View File

@ -46,6 +46,10 @@ pub fn build(b: *std.build.Builder) !void {
exe.addIncludePath("lib/i2cdriver"); exe.addIncludePath("lib/i2cdriver");
exe.install(); exe.install();
// TODO: I believe we can use runArtifact on a second
// exe with a different source file for font generation
// taking us to a series of 5 byte arrays for each
// character in a font.
exe.step.dependOn(&AsciiPrintableStep.create(b, .{ .path = "src/images" }).step); exe.step.dependOn(&AsciiPrintableStep.create(b, .{ .path = "src/images" }).step);
// exe.step.dependOn((try fontGeneration(b, target))); // exe.step.dependOn((try fontGeneration(b, target)));
const run_cmd = exe.run(); const run_cmd = exe.run();

View File

@ -177,7 +177,13 @@ int readFromSerialPort(int fd, uint8_t *b, size_t s)
void writeToSerialPort(int fd, const uint8_t *b, size_t s) void writeToSerialPort(int fd, const uint8_t *b, size_t s)
{ {
write(fd, b, s); if (write(fd, b, s) == -1){
printf("WRITE FAILED %u: ", (int)s);
int i;
for (i = 0; i < s; i++)
printf("%02x ", 0xff & b[i]);
printf("\n");
}
#ifdef VERBOSE #ifdef VERBOSE
printf("WRITE %u: ", (int)s); printf("WRITE %u: ", (int)s);
int i; int i;
@ -526,7 +532,7 @@ int i2c_commands(I2CDriver *sd, int argc, char *argv[])
i2c_monitor(sd, 1); i2c_monitor(sd, 1);
printf("[Hit return to exit monitor mode]\n"); printf("[Hit return to exit monitor mode]\n");
fgets(line, sizeof(line) - 1, stdin); if (!fgets(line, sizeof(line) - 1, stdin)) return 1;
i2c_monitor(sd, 0); i2c_monitor(sd, 0);
} }
break; break;

BIN
src/images/blank.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

View File

@ -17,15 +17,20 @@ const HEIGHT = 64;
const FONT_WIDTH = 5; const FONT_WIDTH = 5;
const FONT_HEIGHT = 8; const FONT_HEIGHT = 8;
const CHARS_PER_LINE = 21; // 21 * 6 = 126 so we have 2px left over const CHARS_PER_LINE = 25; // 25 * 5 = 125 so we have 3px left over
const BORDER_LEFT = 1; // 1 empty px left, 2 empty on right
const LINES = 8; const LINES = 8;
// Device specifications // Device specifications
const PAGES = 8; const PAGES = 8;
var lines: [LINES]*const [:0]u8 = undefined;
fn usage(args: [][]u8) !void { fn usage(args: [][]u8) !void {
const stderr = std.io.getStdErr(); const writer = std.io.getStdErr().writer();
try stderr.writer().print("usage: {s} <image file> <device>\n", .{args[0]}); // TODO: will need more try writer.print("usage: {s} <device> [-bg <image file>] [-<num> text]...\n", .{args[0]});
try writer.print("\t-<num> text: line number and text to display\n", .{});
try writer.print("\te.g. \"-1 'hello world'\" will display \"hello world\" on line 1\n", .{});
std.os.exit(1); std.os.exit(1);
} }
pub fn main() !void { pub fn main() !void {
@ -33,11 +38,11 @@ pub fn main() !void {
//defer alloc.deinit(); //defer alloc.deinit();
const args = try std.process.argsAlloc(alloc); const args = try std.process.argsAlloc(alloc);
defer std.process.argsFree(alloc, args); defer std.process.argsFree(alloc, args);
if (args.len < 3) try usage(args); if (args.len < 2) try usage(args);
const prefix = "/dev/ttyUSB"; const prefix = "/dev/ttyUSB";
const device = try alloc.dupeZ(u8, args[2]); const device = try alloc.dupeZ(u8, args[1]);
defer alloc.free(device); defer alloc.free(device);
if (!std.mem.startsWith(u8, device, prefix)) try usage(args); if (!std.mem.eql(u8, device, "-") and !std.mem.startsWith(u8, device, prefix)) try usage(args);
// stdout is for the actual output of your application, for example if you // stdout is for the actual output of your application, for example if you
// are implementing gzip, then only the compressed bytes should be sent to // are implementing gzip, then only the compressed bytes should be sent to
@ -47,11 +52,45 @@ pub fn main() !void {
const stdout = bw.writer(); const stdout = bw.writer();
defer bw.flush() catch unreachable; // don't forget to flush! defer bw.flush() catch unreachable; // don't forget to flush!
const filename = args[1]; var filename: [:0]u8 = @constCast("");
try stdout.print("Converting {s}\n", .{filename}); var b: [1]u8 = undefined;
const nothing: [:0]u8 = try std.fmt.bufPrintZ(&b, "", .{});
for (lines, 0..) |_, i| {
lines[i] = &nothing;
}
var is_filename = false;
var line_number: ?usize = null;
for (args, 0..) |arg, i| {
if (std.mem.eql(u8, "-bg", arg)) {
is_filename = true;
continue;
}
if (is_filename) {
filename = args[i]; // arg capture changes value...
break;
}
if ((arg[0] == '-' and arg.len > 1) and areDigits(arg[1..])) {
line_number = try std.fmt.parseInt(usize, arg[1..], 10);
continue;
}
if (line_number) |line| {
if (arg.len > CHARS_PER_LINE) {
try std.io.getStdErr().writer().print(
"ERROR: text for line {d} has {d} chars, exceeding maximum length {d}\n",
.{ line, arg.len, CHARS_PER_LINE },
);
std.os.exit(1);
}
std.debug.print("line {d} text: \"{s}\"\n", .{ line, arg });
lines[line] = &args[i];
line_number = null;
continue;
}
}
if (filename.len > 0) try stdout.print("Converting {s}\n", .{filename});
var pixels: [WIDTH * HEIGHT]u8 = undefined; var pixels: [WIDTH * HEIGHT]u8 = undefined;
try convertImage(alloc, filename, &pixels); try convertImage(alloc, filename, &pixels, textForLine);
// try convertImage(alloc, filename, &pixels);
try stdout.print("Sending pixels to display\n", .{}); try stdout.print("Sending pixels to display\n", .{});
// var i: usize = 0; // var i: usize = 0;
// while (i < HEIGHT) { // while (i < HEIGHT) {
@ -68,7 +107,17 @@ pub fn main() !void {
// try stdout.print("Run `zig build test` to run the tests.\n", .{}); // try stdout.print("Run `zig build test` to run the tests.\n", .{});
} }
fn areDigits(bytes: []u8) bool {
for (bytes) |byte| {
if (!std.ascii.isDigit(byte)) return false;
}
return true;
}
fn sendPixels(pixels: []const u8, file: [:0]const u8, device_id: u8) !void { fn sendPixels(pixels: []const u8, file: [:0]const u8, device_id: u8) !void {
if (std.mem.eql(u8, file, "-"))
return sendPixelsToStdOut(pixels);
if (@import("builtin").os.tag != .linux) if (@import("builtin").os.tag != .linux)
@compileError("Linux only please!"); @compileError("Linux only please!");
@ -80,6 +129,16 @@ fn sendPixels(pixels: []const u8, file: [:0]const u8, device_id: u8) !void {
return error.LinuxNativeNotImplemented; return error.LinuxNativeNotImplemented;
} }
fn sendPixelsToStdOut(pixels: []const u8) !void {
const stdout_file = std.io.getStdOut().writer();
var bw = std.io.bufferedWriter(stdout_file);
const stdout = bw.writer();
defer bw.flush() catch unreachable; // don't forget to flush!
for (0..HEIGHT) |i| {
try stdout.print("{d:0>2}: {s}\n", .{ i, fmtSliceGreyscaleImage(pixels[(i * WIDTH)..((i + 1) * WIDTH)]) });
}
}
fn sendPixelsThroughI2CDriver(pixels: []const u8, file: [*:0]const u8, device_id: u8) !void { fn sendPixelsThroughI2CDriver(pixels: []const u8, file: [*:0]const u8, device_id: u8) !void {
var pixels_write_command = [_]u8{0x00} ** ((WIDTH * PAGES) + 1); var pixels_write_command = [_]u8{0x00} ** ((WIDTH * PAGES) + 1);
pixels_write_command[0] = 0x40; pixels_write_command[0] = 0x40;
@ -141,7 +200,7 @@ fn packPixelsToDeviceFormat(pixels: []const u8, packed_pixels: []u8) void {
const column = i % WIDTH; const column = i % WIDTH;
const page = i / WIDTH; const page = i / WIDTH;
if (column == 0) std.debug.print("{d}: ", .{page}); // if (column == 0) std.debug.print("{d}: ", .{page});
// pixel array will be 8x as "high" as the data array we are sending to // pixel array will be 8x as "high" as the data array we are sending to
// the device. So the device column above is only our starter // the device. So the device column above is only our starter
// Display has 8 pages, which is a set of 8 pixels with LSB at top of page // Display has 8 pages, which is a set of 8 pixels with LSB at top of page
@ -172,8 +231,8 @@ fn packPixelsToDeviceFormat(pixels: []const u8, packed_pixels: []u8) void {
(pixels[(6 + row) * WIDTH + column] & 0x01) << 6 | (pixels[(6 + row) * WIDTH + column] & 0x01) << 6 |
(pixels[(7 + row) * WIDTH + column] & 0x01) << 7; (pixels[(7 + row) * WIDTH + column] & 0x01) << 7;
std.debug.print("{s}", .{std.fmt.fmtSliceHexLower(&[_]u8{b.*})}); // std.debug.print("{s}", .{std.fmt.fmtSliceHexLower(&[_]u8{b.*})});
if (column == 127) std.debug.print("\n", .{}); // if (column == 127) std.debug.print("\n", .{});
// Last 2 pages are yellow...16 pixels vertical // Last 2 pages are yellow...16 pixels vertical
// if (page == 6 or page == 7) b.* = 0xff; // if (page == 6 or page == 7) b.* = 0xff;
@ -212,7 +271,10 @@ fn reportMagickError(mw: ?*c.MagickWand) !void {
defer description = @ptrCast([*c]u8, c.MagickRelinquishMemory(description)); defer description = @ptrCast([*c]u8, c.MagickRelinquishMemory(description));
try std.io.getStdErr().writer().print("{s}\n", .{description}); try std.io.getStdErr().writer().print("{s}\n", .{description});
} }
fn convertImage(alloc: std.mem.Allocator, filename: [:0]u8, pixels: *[WIDTH * HEIGHT]u8) !void { fn textForLine(line: usize) []u8 {
return lines[line].*;
}
fn convertImage(alloc: std.mem.Allocator, filename: [:0]u8, pixels: *[WIDTH * HEIGHT]u8, text_fn: *const fn (usize) []u8) !void {
_ = alloc; _ = alloc;
c.MagickWandGenesis(); c.MagickWandGenesis();
defer c.MagickWandTerminus(); defer c.MagickWandTerminus();
@ -223,8 +285,19 @@ fn convertImage(alloc: std.mem.Allocator, filename: [:0]u8, pixels: *[WIDTH * HE
// Reading an image into ImageMagick is problematic if it isn't a bmp // Reading an image into ImageMagick is problematic if it isn't a bmp
// as the library needs a bunch of dependencies available // as the library needs a bunch of dependencies available
// var status = c.MagickReadImage(mw, "logo:"); var status: c.MagickBooleanType = undefined;
var status = c.MagickReadImage(mw, filename); if (filename.len > 0) {
status = c.MagickReadImage(mw, filename);
} else {
// TODO: if there is no background image AND
// we precompute monochrome bit patterns for our font
// we can completely avoid ImageMagick here. Even with
// a background we can do the conversion, then do our
// own text overlay after monochrome conversion.
// Faster and smaller binary (maybe multi-font support?)
const blob = @embedFile("images/blank.bmp");
status = c.MagickReadImageBlob(mw, blob, blob.len);
}
if (status == c.MagickFalse) { if (status == c.MagickFalse) {
if (!std.mem.eql(u8, filename[filename.len - 3 ..], "bmp")) if (!std.mem.eql(u8, filename[filename.len - 3 ..], "bmp"))
try std.io.getStdErr().writer().print("File is not .bmp. That is probably the problem\n", .{}); try std.io.getStdErr().writer().print("File is not .bmp. That is probably the problem\n", .{});
@ -275,18 +348,25 @@ fn convertImage(alloc: std.mem.Allocator, filename: [:0]u8, pixels: *[WIDTH * HE
if (status == c.MagickFalse) if (status == c.MagickFalse)
return error.CouldNotSetExtent; return error.CouldNotSetExtent;
mw = try drawCharacter( for (0..LINES) |i| {
mw.?, const text = text_fn(i);
'4', if (text.len == 0) continue;
-5 * 3, // We have text!
-8, const y: isize = FONT_HEIGHT * @intCast(isize, i);
); var x: isize = BORDER_LEFT;
mw = try drawCharacter( var left_spaces: isize = 0;
mw.?, for (text) |ch| {
'2', if (ch == ' ') {
-5 * 4, left_spaces += 1;
-8, continue;
); }
break;
}
std.debug.print("left_spaces: {d}. Text: \"{s}\"\n", .{ left_spaces, text });
x += (FONT_WIDTH * left_spaces);
mw = try drawString(mw, text[@intCast(usize, left_spaces)..], x, y);
}
// We make the image monochrome by quantizing the image with 2 colors in the // We make the image monochrome by quantizing the image with 2 colors in the
// gray colorspace. See: // gray colorspace. See:
// https://www.imagemagick.org/Usage/quantize/#monochrome // https://www.imagemagick.org/Usage/quantize/#monochrome
@ -319,6 +399,18 @@ fn convertImage(alloc: std.mem.Allocator, filename: [:0]u8, pixels: *[WIDTH * HE
} }
} }
} }
fn drawString(mw: ?*c.MagickWand, str: []const u8, x: isize, y: isize) !?*c.MagickWand {
var rc = mw;
for (str, 0..) |ch, i| {
rc = try drawCharacter(
rc,
ch,
-(x + @intCast(isize, FONT_WIDTH * i)),
-y,
);
}
return rc;
}
fn drawCharacter(mw: ?*c.MagickWand, char: u8, x: isize, y: isize) !?*c.MagickWand { fn drawCharacter(mw: ?*c.MagickWand, char: u8, x: isize, y: isize) !?*c.MagickWand {
// Create a second wand. Does this need to exist after the block? // Create a second wand. Does this need to exist after the block?