@@ -1,11 +1,25 @@
name: []const u8,
address: []const u8,
state: State = .{},
kind: Kind = .{
.hardware = .{},
},
const Device = @This();
pub const State = struct {
pub const Kinds = enum {
software,
hardware,
};
pub const Kind = union(Kinds) {
software: Software,
hardware: Hardware,
};
pub const Software = struct {};
pub const Hardware = struct {
presence: ?bool = null,
detection_delay: ?bool = null,
fading_time: ?bool = null,
@@ -48,6 +62,104 @@ pub const State = struct {
color_temp_startup: ?usize = null,
color_temp: ?usize = null,
color_mode: ?ColorMode = null,
pub fn updateTyped(
hw: *Hardware,
T: type,
comptime fname: []const u8,
payload: []const u8,
dname: []const u8,
) bool {
const field: *T = &@field(hw, fname);
switch (T) {
?bool => {
const next = if (eql(u8, payload, "true")) true else false;
const edge = field.* == null or field.*.? != next;
field.* = next;
return edge;
},
?usize => {
const next: ?usize = parseInt(usize, payload, 0) catch |err| brk: {
log.err(
"unable to parseInt on {s} with [{s}]{any} because {}",
.{ fname, payload, payload, err },
);
break :brk null;
};
if (next) |nxt| {
const edge = field.* == null or field.*.? != nxt;
field.* = nxt;
return edge;
} else {
const edge = field.* != null;
field.* = null;
return edge;
}
},
?f64 => {
const next: ?f64 = parseFloat(f64, payload) catch |err| brk: {
log.err(
"unable to parseFloat on {s} with [{s}]{any} because {}",
.{ fname, payload, payload, err },
);
break :brk null;
};
if (next) |nxt| {
const edge = field.* == null or field.*.? != nxt;
field.* = nxt;
return edge;
} else {
const edge = field.* != null;
field.* = null;
return edge;
}
},
?PowerOn, ?Buttons, ?ColorMode => {
if (payload.len == 0) {
defer field.* = null;
return field.* != null;
}
inline for (@typeInfo(@typeInfo(T).optional.child).@"enum".fields) |en| {
if (eqlAny(en.name, payload)) {
defer field.* = @enumFromInt(en.value);
return field.* == null or field.*.? != @as(T, @enumFromInt(en.value));
}
} else {
log.err(
"unable to parse enum on {s} with [{s}]{any} for {s}",
.{ fname, payload, payload, dname },
);
return false;
}
},
?Many => {
if (payload.len == 0) {
defer field.* = null;
return field.* != null;
}
const prev_v = field.*;
inline for (@typeInfo(@typeInfo(T).optional.child).@"union".fields) |un| {
//const prev_t = field.* != null and field.* == un;
inline for (@typeInfo(un.type).@"enum".fields) |en| {
if (eqlAny(en.name, payload)) {
const next = @unionInit(Many, un.name, @as(un.type, @enumFromInt(en.value)));
defer field.* = next;
return prev_v == null or @TypeOf(prev_v) != @TypeOf(next);
}
}
} else {
log.err(
"unable to parse union on {s} with [{s}]{any} for {s}",
.{ fname, payload, payload, dname },
);
return false;
}
},
else => comptime unreachable,
}
}
};
pub const ManyKind = enum {
@@ -92,112 +204,27 @@ pub fn initZ2m(zb: *Zigbee, z2m_bd: Z2m.bridge.devices) !Device {
.address = try zb.alloc.dupe(u8, z2m_bd.ieee_address orelse return error.InvalidDevice),
};
}
pub fn updateTyped(d: *Device, T: type, comptime fname: []const u8, payload: []const u8) bool {
const field: *T = &@field(d.state, fname);
switch (T) {
?bool => {
const next = if (eql(u8, payload, "true")) true else false;
const edge = field.* == null or field.*.? != next;
field.* = next;
return edge;
},
?usize => {
const next: ?usize = parseInt(usize, payload, 0) catch |err| brk: {
log.err(
"unable to parseInt on {s} with [{s}]{any} because {}",
.{ fname, payload, payload, err },
);
break :brk null;
};
if (next) |nxt| {
const edge = field.* == null or field.*.? != nxt;
field.* = nxt;
return edge;
} else {
const edge = field.* != null;
field.* = null;
return edge;
}
},
?f64 => {
const next: ?f64 = parseFloat(f64, payload) catch |err| brk: {
log.err(
"unable to parseFloat on {s} with [{s}]{any} because {}",
.{ fname, payload, payload, err },
);
break :brk null;
};
if (next) |nxt| {
const edge = field.* == null or field.*.? != nxt;
field.* = nxt;
return edge;
} else {
const edge = field.* != null;
field.* = null;
return edge;
}
},
?PowerOn, ?Buttons, ?ColorMode => {
if (payload.len == 0) {
defer field.* = null;
return field.* != null;
}
inline for (@typeInfo(@typeInfo(T).optional.child).@"enum".fields) |en| {
if (eqlAny(en.name, payload)) {
defer field.* = @enumFromInt(en.value);
return field.* == null or field.*.? != @as(T, @enumFromInt(en.value));
}
} else {
log.err(
"unable to parse enum on {s} with [{s}]{any} for {s}",
.{ fname, payload, payload, d.name },
);
return false;
}
},
?Many => {
if (payload.len == 0) {
defer field.* = null;
return field.* != null;
}
const prev_v = field.*;
inline for (@typeInfo(@typeInfo(T).optional.child).@"union".fields) |un| {
//const prev_t = field.* != null and field.* == un;
inline for (@typeInfo(un.type).@"enum".fields) |en| {
if (eqlAny(en.name, payload)) {
const next = @unionInit(Many, un.name, @as(un.type, @enumFromInt(en.value)));
defer field.* = next;
return prev_v == null or @TypeOf(prev_v) != @TypeOf(next);
}
}
} else {
log.err(
"unable to parse union on {s} with [{s}]{any} for {s}",
.{ fname, payload, payload, d.name },
);
return false;
}
},
else => comptime unreachable,
}
pub fn updateSw(d: *Device, zb: *Zigbee, name: []const u8, payload: []const u8) !void {
_ = d;
_ = zb;
_ = name;
_ = payload;
log.err("Software device not implemented", .{});
}
pub fn update(d: *Device, zb: *Zigbee, name: []const u8, payload: []const u8) !void {
pub fn updateHw(d: *Device, zb: *Zigbee, name: []const u8, payload: []const u8) !void {
const target = name[d.name.len..];
if (target.len == 0) return;
inline for (@typeInfo(State).@"struct".fields) |field| {
inline for (@typeInfo(Hardware).@"struct".fields) |field| {
if (eql(u8, target[1..], field.name)) {
if (d.updateTyped(field.type, field.name, payload)) {
if (d.kind.hardware.updateTyped(field.type, field.name, payload, d.name)) {
if (eql(u8, "office/mmw0", d.name) and eql(u8, target, "/presence")) {
log.err("sendable because {any}", .{payload});
try zb.client.send(mqtt.Publish{
.topic_name = "zigbee2mqtt/office/lights/set",
.packet_ident = null,
.properties = "",
.payload = if (d.state.presence.?) "ON" else "OFF",
.payload = if (d.kind.hardware.presence.?) "ON" else "OFF",
});
}
if (!eql(u8, target, "/linkquality")) {
@@ -216,6 +243,13 @@ pub fn update(d: *Device, zb: *Zigbee, name: []const u8, payload: []const u8) !v
log.warn("device({s}) unsupported field {s} [{s}]-{any}", .{ d.name, target, payload, payload });
}
pub fn update(d: *Device, zb: *Zigbee, name: []const u8, payload: []const u8) !void {
switch (d.kind) {
.software => try d.updateSw(zb, name, payload),
.hardware => try d.updateHw(zb, name, payload),
}
}
pub const Zigbee = @import("Zigbee.zig");
const Z2m = @import("z2m-data.zig").Z2m;