v1.0
This commit is contained in:
commit
1f030bec1d
|
@ -0,0 +1 @@
|
|||
/zig-*/
|
|
@ -0,0 +1,12 @@
|
|||
This project uses the Linux's `ioclt` command KDMKTONE to play clock-tower chimes on the internal tty "speaker" every 15 minutes. It needs to be run as root in order to do this.
|
||||
|
||||
I wanted to do this because the computer that I use as a server in my room doesn't have a real speaker and I didn't want to buy any hardware to achive my dream of being able to hear a clock tower.
|
||||
|
||||
Also, I used this project as an excuse to learn Zig. The Zig code I've written is probably not the nicest way to do things, and I'd welcome feedback on it.
|
||||
|
||||
Also included are the Systemd service and timer files that I use to run this script every 15 minutes.
|
||||
```
|
||||
sudo cp chime.* /etc/systemd/system/
|
||||
```
|
||||
|
||||
This is free and unencumbered software released into the public domain, under the terms of The Unlicense.
|
|
@ -0,0 +1,35 @@
|
|||
const std = @import("std");
|
||||
|
||||
pub fn build(b: *std.build.Builder) void {
|
||||
// Standard target options allows the person running `zig build` to choose
|
||||
// what target to build for. Here we do not override the defaults, which
|
||||
// means any target is allowed, and the default is native. Other options
|
||||
// for restricting supported target set are available.
|
||||
const target = b.standardTargetOptions(.{});
|
||||
|
||||
// Standard release options allow the person running `zig build` to select
|
||||
// between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall.
|
||||
const mode = b.standardReleaseOptions();
|
||||
|
||||
const exe = b.addExecutable("chime", "src/main.zig");
|
||||
exe.setTarget(target);
|
||||
exe.setBuildMode(mode);
|
||||
exe.linkLibC();
|
||||
exe.install();
|
||||
|
||||
const run_cmd = exe.run();
|
||||
run_cmd.step.dependOn(b.getInstallStep());
|
||||
if (b.args) |args| {
|
||||
run_cmd.addArgs(args);
|
||||
}
|
||||
|
||||
const run_step = b.step("run", "Run the app");
|
||||
run_step.dependOn(&run_cmd.step);
|
||||
|
||||
const exe_tests = b.addTest("src/main.zig");
|
||||
exe_tests.setTarget(target);
|
||||
exe_tests.setBuildMode(mode);
|
||||
|
||||
const test_step = b.step("test", "Run unit tests");
|
||||
test_step.dependOn(&exe_tests.step);
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
# The service file. Is run every 15 minutes by the timer. Just needs to run chime and exit
|
||||
[Unit]
|
||||
Description=Makes a chime sound depending on what time it is
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/home/matthias/Programming/chime/zig-out/bin/chime
|
|
@ -0,0 +1,10 @@
|
|||
[Unit]
|
||||
Description=Fires chime.service every 15 minutes, making a chime sound
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
||||
[Timer]
|
||||
# > Values may be suffixed with "/" and a repetition value, which indicates that the value itself and the value plus all multiples of the repetition value are matched.
|
||||
# - SYSTEMD.TIME(7)
|
||||
OnCalendar=*:00/15
|
|
@ -0,0 +1,129 @@
|
|||
const std = @import("std");
|
||||
const ioctl = std.os.linux.ioctl;
|
||||
|
||||
// Need current local time, which Zig std doesn't support. Need libc
|
||||
const time_h = @cImport(
|
||||
@cInclude("time.h")
|
||||
);
|
||||
|
||||
const kd_h = @cImport(
|
||||
@cInclude("linux/kd.h")
|
||||
);
|
||||
const KDMKTONE = kd_h.KDMKTONE;
|
||||
// const KIOCSOUND = kd_h.KIOCSOUND;
|
||||
|
||||
const fcntl_h = @cImport(
|
||||
@cInclude("fcntl.h")
|
||||
);
|
||||
const O_RDWR = fcntl_h.O_RDWR;
|
||||
const O_RDONLY = fcntl_h.O_RDONLY;
|
||||
|
||||
// From https://github.com/johnath/beep/blob/0d790fa45777896749a885c3b93b2c1476d59f20/beep.c#L48
|
||||
// Mentioned in console_ioctl(2) but under KIOCSOUND not KDMKTONE. I still don't understand the difference
|
||||
// I think KIOCSOUND just doesn't let you control the time, you have to use a second syscall to stop it
|
||||
// So we're using KDMKTONE
|
||||
const CLOCK_TICK_RATE = 1193180;
|
||||
|
||||
const Note = enum { B, E, F_sharp, G_sharp, E3 };
|
||||
|
||||
fn note_to_freq (note: Note) usize {
|
||||
return switch (note) {
|
||||
// E key
|
||||
.B => 247, // B3
|
||||
.E => 330, // E4
|
||||
.G_sharp => 415, // G#4
|
||||
.F_sharp => 370, // F#4
|
||||
.E3 => 165 // E3 is used for hour bells,
|
||||
};
|
||||
}
|
||||
|
||||
// I spent so long trying to find a better way to do this in Zig
|
||||
// :15 (set 1)
|
||||
const sq_one = [_]Note{.G_sharp, .F_sharp, .E, .B};
|
||||
// :30 (sets 2 and 3)
|
||||
const sq_two = [_]Note{.E, .G_sharp, .F_sharp, .B, .E, .F_sharp, .G_sharp, .E};
|
||||
// :45 (sets 4, 5, 1)
|
||||
const sq_three = [_]Note{.G_sharp, .E, .F_sharp, .B, .B, .F_sharp, .G_sharp, .E, .G_sharp, .F_sharp, .E, .B};
|
||||
// :60 (sets 2, 3, 4, 5)
|
||||
const sq_four = [_]Note{.E, .G_sharp, .F_sharp, .B, .E, .F_sharp, .G_sharp, .G_sharp, .E, .F_sharp, .B, .B, .F_sharp, .G_sharp, .E};
|
||||
const sequences = [4][]const Note{ sq_one[0..], sq_two[0..], sq_three[0..], sq_four[0..]};
|
||||
|
||||
const dur = 500;
|
||||
|
||||
fn play_sequence(sequence: []const Note) std.os.OpenError!void {
|
||||
for (sequence) |note, i| {
|
||||
try play_tone(if (i % 4 == 3) dur*2 else dur, note_to_freq(note));
|
||||
|
||||
if (i % 4 == 3) {
|
||||
// After each quartet, take a full note rest
|
||||
std.time.sleep(1000000 * dur);
|
||||
}else {
|
||||
// Add a quarter note rest between notes
|
||||
std.time.sleep(1000000 * dur / 4);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Function play_tone
|
||||
/// Takes a time in milliseconds, and a frequency in Hz
|
||||
/// Plays that tone and waits milliseconds before returning
|
||||
fn play_tone (ms: usize, freq: usize) std.os.OpenError!void {
|
||||
//TODO: make sure there's not a tone playing
|
||||
// Error handling, etc
|
||||
|
||||
// TODO?: Save fd between tones?
|
||||
// O_RDWR is 02
|
||||
// print("{}\n", .{ @typeInfo( @TypeOf (kd )) });
|
||||
// Right now I'm using os.open which is open(2), the Linux syscall
|
||||
// Since I'm linking against glibc anyways I think I could use fopen(3)
|
||||
|
||||
// > Linux reserves the special, nonstandard access mode 3 (binary 11) in flags to mean: check for read and write permission on the file and re-
|
||||
// > turn a file descriptor that can't be used for reading or writing. This nonstandard access mode is used by some Linux drivers to return a
|
||||
// > file descriptor that is to be used only for device-specific ioctl(2) operations.
|
||||
// - open(2)
|
||||
// The third option is for extra options and is unused (for these flags) but Zig won't let me pass only two paramaters like C will
|
||||
const fd: i32 = try std.os.open("/dev/tty0", 3, 0);
|
||||
|
||||
// i32, u32, usize
|
||||
// fd, request, argp
|
||||
// Instantly returns, but the tone persists for `ms` regardless
|
||||
_ = ioctl(@intCast(i32, fd), KDMKTONE, (ms << 16) + CLOCK_TICK_RATE / freq);
|
||||
|
||||
//nanoseconds to sleep for
|
||||
std.time.sleep(1000000 * ms);
|
||||
|
||||
// TODO: close the fd??
|
||||
}
|
||||
|
||||
pub fn main() anyerror!void {
|
||||
var rawtime: time_h.time_t = undefined;
|
||||
_ = time_h.time(&rawtime);
|
||||
|
||||
//localtime returns a c pointer to a tm struct
|
||||
var current_time: [*c]time_h.tm = time_h.localtime( &rawtime );
|
||||
|
||||
// 0, 1, 2, or 3 for :15, :30, :45, :00, with rounding to the closest
|
||||
const quarter = (@floatToInt(u8, @round(@intToFloat(f64, current_time.*.tm_min) / 60.0 * 4)) + 3) % 4;
|
||||
|
||||
std.log.info("Quarter {}", .{ quarter });
|
||||
try play_sequence(sequences[quarter]);
|
||||
|
||||
// If it's the 4th quarter (0-index ofc), sound bell
|
||||
if (quarter == 3) {
|
||||
var hours = @mod((current_time.*.tm_hour + 11), 12) + 1;
|
||||
if (current_time.*.tm_min >= 53) {
|
||||
hours += 1;
|
||||
}
|
||||
while (hours > 0) {
|
||||
// Rest before playing hour bells
|
||||
std.time.sleep(1000000 * dur);
|
||||
|
||||
// Play a whole note chime
|
||||
try play_tone(dur, note_to_freq(.E3));
|
||||
// Rest 2/4
|
||||
std.time.sleep(1000000 * dur / 2);
|
||||
|
||||
hours -= 1;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
#include <time.h>
|
||||
#include <stdio.h>
|
||||
|
||||
// An example of getting the current hour and minute with time.h's `localtime`
|
||||
void main () {
|
||||
time_t rawtime;
|
||||
time(&rawtime);
|
||||
|
||||
struct tm *current_time;
|
||||
current_time = localtime( &rawtime );
|
||||
|
||||
printf("%d:%d", current_time->tm_hour, current_time->tm_min);
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
const std = @import("std");
|
||||
|
||||
/// An example of getting the current *UTC* hour and minute with the Zig standard library, no libc needed
|
||||
/// Unfortunately, the Zig standard library doesn't support local time zones :(
|
||||
pub fn main() anyerror!void {
|
||||
const now = std.time.epoch.EpochSeconds{
|
||||
.secs = @intCast(u64, std.time.timestamp())
|
||||
};
|
||||
|
||||
std.debug.print("{}", .{
|
||||
std.time.epoch.EpochSeconds.getDaySeconds(now).getHoursIntoDay()
|
||||
});
|
||||
}
|
Loading…
Reference in New Issue