Compare commits
3
Commits
main
..
3c56f6b7e1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3c56f6b7e1 | ||
|
|
66b711f6b0 | ||
|
|
76058b572a |
+2
-2
@@ -2,8 +2,8 @@ include <common.scad>
|
||||
use <left-half.scad>
|
||||
use <right-half.scad>
|
||||
|
||||
left_half_creaturedex();
|
||||
right_half_creaturedex();
|
||||
left_half();
|
||||
right_half();
|
||||
|
||||
#union() {
|
||||
right_half_components();
|
||||
|
||||
+3
-4
@@ -61,10 +61,9 @@ module main_display(pin_header = false) {
|
||||
left(sd_inset)
|
||||
cuboid([sd_width, sd_length, sd_height], anchor=TOP + RIGHT + FRONT);
|
||||
|
||||
if (pin_header)
|
||||
position(BOTTOM + LEFT)
|
||||
xrot(180)
|
||||
pin_header(14, spin=90, anchor=BOTTOM + BACK);
|
||||
position(BOTTOM + LEFT)
|
||||
xrot(180)
|
||||
pin_header(14, spin=90, anchor=BOTTOM + BACK);
|
||||
}
|
||||
|
||||
xcopies(length - (2 * hole_inset) - hole_diameter) {
|
||||
|
||||
+6
-220
@@ -1,226 +1,12 @@
|
||||
include <BOSL2/std.scad>
|
||||
include <common.scad>
|
||||
|
||||
module left_half_orig() {
|
||||
translate(BASE_MODEL_TRANSLATE) scale(BASE_MODEL_SCALE) import(file="left-half.3mf");
|
||||
module left_half_components() {
|
||||
cube([1,1,1]);
|
||||
}
|
||||
|
||||
BASE_MODEL_SCALE = 1.1;
|
||||
// Bounds: ~ [128.62, 185.46, 35.2]
|
||||
LEFT_HALF_BOUNDING_BOX = BASE_MODEL_SCALE * [116.93, 168.6, 32];
|
||||
|
||||
w = LEFT_HALF_BOUNDING_BOX.x;
|
||||
l = LEFT_HALF_BOUNDING_BOX.y;
|
||||
max_h = LEFT_HALF_BOUNDING_BOX.z;
|
||||
|
||||
// Proportional heights based on a 4.5 unit total scale
|
||||
base_h = max_h * (2.0 / 4.5);
|
||||
border_h = max_h * (2.5 / 4.5);
|
||||
top_h = max_h * (3.5 / 4.5);
|
||||
sphere_r = max_h - top_h;
|
||||
|
||||
recess_h = 3;
|
||||
|
||||
bump_height = 10;
|
||||
faceplate_thickness = 2;
|
||||
wall = 5;
|
||||
|
||||
top_bump_width_big = 55;
|
||||
top_bump_width_small = 35;
|
||||
|
||||
hinge_thickness = 22;
|
||||
|
||||
/// Builds the top bump.
|
||||
module top_bump(height, extra_width = 0, inverted = false, wall = 2, anchor = CENTER, spin = 0, orient = UP) {
|
||||
width_big = top_bump_width_big;
|
||||
width_small = top_bump_width_small;
|
||||
|
||||
// Assuming 'w' is your base width defined in the outer scope.
|
||||
// Lock the left side to the base width, add extra_width only to the right.
|
||||
left_x = -w / 2;
|
||||
right_x = (w / 2) + extra_width;
|
||||
|
||||
top_path = back(
|
||||
// center the path around the origin
|
||||
width_big / 2, [
|
||||
[right_x, 0], // Index 0, top-right
|
||||
[left_x, 0], // Index 1, top-left
|
||||
[left_x, -width_big], // Index 2, bottom-left
|
||||
[left_x / 2 + 20, -width_big], // Index 3, bottom-center-left
|
||||
[0, -width_small], // Index 4, middle-center
|
||||
[right_x, -width_small], // Index 5, bottom-right
|
||||
]
|
||||
);
|
||||
|
||||
radii_list = [5, 5, 5, 15, 5, 5];
|
||||
|
||||
top_poly = round_corners(top_path, radius=radii_list);
|
||||
inner_poly = offset(top_poly, delta=-wall);
|
||||
|
||||
// The new total width
|
||||
total_width = w + extra_width;
|
||||
part_size = [total_width, width_big, height];
|
||||
|
||||
// The geometric center of the asymmetric shape is shifted to the right
|
||||
part_offset = [extra_width / 2, 0, 0];
|
||||
|
||||
// Removed the `right(extra_width)` translation here.
|
||||
// Added `offset` so BOSL2 knows the bounding box isn't centered at [0,0,0],
|
||||
// keeping the CENTER anchor exactly on your original origin point.
|
||||
attachable(anchor, spin, orient, size=part_size, offset=part_offset) {
|
||||
if (inverted) {
|
||||
down(wall / 2)
|
||||
linear_sweep(inner_poly, height=height - wall + EPS, center=true);
|
||||
} else {
|
||||
diff("empty") {
|
||||
linear_sweep(top_poly, height=height, center=true) {
|
||||
attach(TOP)
|
||||
tag("empty")
|
||||
down(wall)
|
||||
left(wall / 4) // (Leaving your original shift here)
|
||||
linear_sweep(inner_poly, height=height - wall + EPS, anchor=TOP);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
children();
|
||||
}
|
||||
module left_half() {
|
||||
translate(BASE_MODEL_TRANSLATE) scale(BASE_MODEL_SCALE) import(file = "left-half.3mf");
|
||||
}
|
||||
|
||||
module top_bump_skirt(height = 5) {
|
||||
intersection() {
|
||||
diff("heel_holes") {
|
||||
tag("heel_holes")
|
||||
down(EPS / 2)
|
||||
top_bump(height=height * 2 + EPS, wall=wall, inverted=true, anchor=FRONT + BOTTOM);
|
||||
top_bump(height=height, wall=wall, anchor=FRONT + BOTTOM);
|
||||
}
|
||||
down(EPS / 2)
|
||||
cuboid([w, wall + (top_bump_width_big - top_bump_width_small) + EPS, height * 2 + EPS], anchor=FRONT + BOTTOM);
|
||||
}
|
||||
}
|
||||
|
||||
module top_bump_side_extension(width, height = 5) {
|
||||
intersection() {
|
||||
diff("heel_holes") {
|
||||
tag("heel_holes")
|
||||
down(EPS / 2)
|
||||
top_bump(height=height * 2 + EPS, wall=wall, inverted=true, anchor=RIGHT + TOP + BACK);
|
||||
top_bump(height=height, wall=wall, anchor=RIGHT + TOP + BACK);
|
||||
}
|
||||
back(EPS / 2)
|
||||
up(EPS / 2)
|
||||
cuboid([width, top_bump_width_small + EPS, height * 2 + EPS], anchor=RIGHT + TOP + BACK);
|
||||
}
|
||||
}
|
||||
|
||||
module top_bump_big_sphere(diameter, ring_extra_diameter, ring_height) {
|
||||
zcyl(d=diameter + ring_extra_diameter, h=ring_height, anchor=BOTTOM)
|
||||
position(TOP)
|
||||
top_half() sphere(diameter / 2);
|
||||
}
|
||||
|
||||
module top_bump_small_spheres(diameter, spacing) {
|
||||
top_half() {
|
||||
left(spacing) sphere(r=diameter / 2, anchor=CENTER);
|
||||
sphere(r=diameter / 2, anchor=CENTER);
|
||||
right(spacing) sphere(r=diameter / 2, anchor=CENTER);
|
||||
}
|
||||
}
|
||||
|
||||
/// Hinges to attach the right to the left (=this) half.
|
||||
/// Origin is at the bottom left front corner.
|
||||
module hinges(joint_axis_length = 80, hinge_thickness = 22) {
|
||||
hinge_inset = 10;
|
||||
hinge_inset_diameter = 6;
|
||||
|
||||
back(joint_axis_length)
|
||||
yflip_copy(offset=joint_axis_length) {
|
||||
right(border_h / 2 + 2)
|
||||
// cannot get it to align with edge otherwise
|
||||
ycyl(r=border_h / 2 + 2, l=hinge_thickness, anchor=BOTTOM + BACK)
|
||||
position(FRONT) tag("holes") fwd(EPS) ycyl(d=hinge_inset_diameter, l=hinge_inset, anchor=FRONT);
|
||||
cuboid([border_h / 2 + 2 + EPS, hinge_thickness, base_h], anchor=LEFT + BOTTOM + BACK);
|
||||
}
|
||||
}
|
||||
|
||||
module left_half_creaturedex() {
|
||||
left((w + hinge_thickness) / 2)
|
||||
diff("holes", "forcekeep")
|
||||
// Outer walls
|
||||
color_this("green")
|
||||
rect_tube(h=base_h, size=[w, l], wall=wall, rounding=[0, 5, 5, 0]) {
|
||||
|
||||
// Gap for extension of top bump
|
||||
position(RIGHT + BACK + BOTTOM)
|
||||
fwd(wall)
|
||||
move([EPS / 2, 0, -EPS / 2])
|
||||
tag("holes")
|
||||
cuboid([wall + EPS, top_bump_width_small - wall + EPS, EPS + bump_height + base_h], anchor=RIGHT + BOTTOM + BACK);
|
||||
|
||||
// Recessed Faceplate
|
||||
attach(TOP)
|
||||
recolor("blue")
|
||||
down(recess_h) {
|
||||
cuboid([w - EPS, l - EPS, faceplate_thickness], rounding=5, edges="Z", anchor=TOP) {
|
||||
position(BACK)
|
||||
tag("holes") top_bump(height=bump_height + faceplate_thickness + recess_h, wall=wall, inverted=true, anchor=BACK);
|
||||
}
|
||||
}
|
||||
|
||||
// Top Bump
|
||||
tag("forcekeep")
|
||||
recolor("Gold")
|
||||
position(TOP + BACK) {
|
||||
down(recess_h + faceplate_thickness)
|
||||
top_bump(height=bump_height + faceplate_thickness + recess_h, extra_width=hinge_thickness, wall=wall, anchor=BOTTOM + BACK) {
|
||||
position(TOP + BACK)
|
||||
fwd(20) {
|
||||
position(LEFT)
|
||||
right(w / 5)
|
||||
top_bump_big_sphere(diameter=23, ring_extra_diameter=4, ring_height=2);
|
||||
position(RIGHT)
|
||||
left(w / 5)
|
||||
top_bump_small_spheres(diameter=7, spacing=15);
|
||||
}
|
||||
position(FRONT + BOTTOM)
|
||||
fwd(wall)
|
||||
top_bump_skirt(recess_h + faceplate_thickness);
|
||||
position(RIGHT + BACK + TOP)
|
||||
down(EPS)
|
||||
back(EPS)
|
||||
recolor("red")
|
||||
top_bump_side_extension(width=hinge_thickness + wall, height=bump_height + base_h);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: do this properly!
|
||||
// 4. Cutouts/Details in the Recessed Area
|
||||
*up(base_h / 2 - max_h / 2) {
|
||||
// Main Portrait Display Cutout
|
||||
translate([0, 10, 0])
|
||||
tag("holes") cuboid([w - 40, l - 90, base_h + 2], rounding=2, edges="Z");
|
||||
|
||||
// Small Landscape Display Cutout (Bottom Left)
|
||||
translate([-w / 2 + 35, -l / 2 + 35, 0])
|
||||
tag("holes") cuboid([28, 12, base_h + 2], rounding=1, edges="Z");
|
||||
|
||||
// D-Pad Cutout (Bottom Right)
|
||||
translate([w / 2 - 35, -l / 2 + 35, 0])
|
||||
tag("holes") union() {
|
||||
cuboid([22, 8, base_h + 2], rounding=1, edges="Z");
|
||||
cuboid([8, 22, base_h + 2], rounding=1, edges="Z");
|
||||
}
|
||||
}
|
||||
|
||||
// 5. Right Side Hinges
|
||||
recolor("Red")
|
||||
position(BOTTOM + RIGHT + FRONT) {
|
||||
hinges();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
left_half_creaturedex();
|
||||
|
||||
left(300) left_half_orig();
|
||||
left_half();
|
||||
#left_half_components();
|
||||
@@ -106,10 +106,10 @@ module right_half_hinge_only() {
|
||||
}
|
||||
// !right_half_hinge_only();
|
||||
|
||||
module right_half_creaturedex() {
|
||||
module right_half() {
|
||||
right_half_door_only();
|
||||
right_half_hinge_only();
|
||||
}
|
||||
|
||||
// right_half_creaturedex();
|
||||
// right_half();
|
||||
// #right_half_components();
|
||||
|
||||
+117
-6
@@ -23,9 +23,118 @@ where
|
||||
struct PageParseError<const N: usize> {
|
||||
iteration: u8,
|
||||
bytes: [u8; N],
|
||||
status_code: Option<pn532::ErrorCode>,
|
||||
status_code: Option<CommandResult>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
struct CommandResult {
|
||||
/// # WrErr
|
||||
/// Set to logic 1, when data is written into the FIFO by the 80C51 during the AutoColl command or MFAuthent command or
|
||||
/// if data is written into the FIFO by the 80C51 during the time between sending the last bit on the RF interface and
|
||||
/// receiving the last bit on the RF interface.
|
||||
write_error: bool,
|
||||
/// # TempErr
|
||||
/// Set to logic 1, if the internal temperature sensor detects overheating. In this case the antenna drivers are switched off automatically.
|
||||
overheating: bool,
|
||||
/// # RFErr
|
||||
/// Set to logic 1, if in active communication mode the counterpart does not switch on the RF field in time as defined in NFCIP-1 standard.
|
||||
/// Note: RFErr is only used in active communication mode. The bit RxFraming or the bit TxFraming has to be set to 01h to enable
|
||||
/// this functionality.
|
||||
rf_timeout: bool,
|
||||
/// # BufferOvfl
|
||||
/// Set to logic 1, if the 80C51 or if the internal state machine (e.g. receiver) tries to write data into the FIFO buffer
|
||||
/// although the FIFO buffer is already full.
|
||||
buffer_overflow: bool,
|
||||
/// # CollErr
|
||||
/// Set to logic 1, if a bit-collision is detected. It is set to logic 0 automatically at receiver start phase.
|
||||
/// This flag is only valid during the bitwise anticollision at 106 kbit/s. During communication schemes at 212 and 424 kbit/s
|
||||
/// this flag is always set to logic 0.
|
||||
bit_collision: bool,
|
||||
/// # CRCErr
|
||||
/// Set to logic 1, if RxCRCEn in CIU_RxMode register is set to logic 1 and the CRC calculation fails. It is set to logic 0 automatically
|
||||
/// at receiver start-up phase.
|
||||
crc_error: bool,
|
||||
/// # ParityErr
|
||||
/// Set to logic 1, if the parity check has failed. It is set to logic 0 automatically at receiver start-up phase.
|
||||
/// Only valid for ISO/IEC 14443A/MIFARE or NFCIP-1 communication at 106 kbit/s.
|
||||
parity_error: bool,
|
||||
/// # ProtocollErr
|
||||
/// Set to logic 1, if one out of the following cases occurs:
|
||||
/// - Set to logic 1 if the SOF is incorrect. It is set to logic 0 automatically at receiver start-up phase.
|
||||
/// The bit is only valid for 106 kbit in Active and Passive Communication mode.
|
||||
/// - If bit DetectSync in CIU_Mode register is set to logic 1 during FeliCa communication or Active Communication
|
||||
/// with transfer speeds higher than 106 kbit, ProtocolErr is set to logic 1 in case of a byte length violation.
|
||||
/// - During the AutoColl command, ProtocolErr is set to logic 1, if the Initiator bit in CIU_Control register is set to logic 1.
|
||||
/// - During the MFAuthent Command, ProtocolErr is set to logic 1, if the number of bytes received in one data stream is incorrect.
|
||||
/// - Set to logic 1, if the Miller Decoder detects 2 pauses below the minimum time according to the ISO/IEC 14443A definitions.
|
||||
protocol_error: bool,
|
||||
}
|
||||
|
||||
impl CommandResult {
|
||||
pub fn is_error(&self) -> bool {
|
||||
self.write_error
|
||||
|| self.overheating
|
||||
|| self.rf_timeout
|
||||
|| self.buffer_overflow
|
||||
|| self.bit_collision
|
||||
|| self.crc_error
|
||||
|| self.parity_error
|
||||
|| self.protocol_error
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u8> for CommandResult {
|
||||
fn from(value: u8) -> Self {
|
||||
let mask = |bit| 1u8 << bit;
|
||||
let take_bit = |bit| (value & mask(bit)) != 0u8;
|
||||
|
||||
Self {
|
||||
write_error: take_bit(7),
|
||||
overheating: take_bit(6),
|
||||
rf_timeout: take_bit(5),
|
||||
buffer_overflow: take_bit(4),
|
||||
bit_collision: take_bit(3),
|
||||
crc_error: take_bit(2),
|
||||
parity_error: take_bit(1),
|
||||
protocol_error: take_bit(0),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl core::fmt::Display for CommandResult {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
if !self.is_error() {
|
||||
return write!(f, "ok");
|
||||
}
|
||||
|
||||
write!(f, "error: ")?;
|
||||
|
||||
let errors = [
|
||||
(self.write_error, "write_error(7)"),
|
||||
(self.overheating, "overheating(6)"),
|
||||
(self.rf_timeout, "rf_timeout(5)"),
|
||||
(self.buffer_overflow, "buffer_overflow(4)"),
|
||||
(self.bit_collision, "bit_collision(3)"),
|
||||
(self.crc_error, "crc_error(2)"),
|
||||
(self.parity_error, "parity_error(1)"),
|
||||
(self.protocol_error, "protocol_error(0)"),
|
||||
]
|
||||
.into_iter()
|
||||
.filter_map(|(active, name)| active.then_some(name));
|
||||
|
||||
let mut first = true;
|
||||
for error in errors {
|
||||
let separator = if !first { ", " } else { "" };
|
||||
write!(f, "{separator}{error}")?;
|
||||
first = false;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for CommandResult {}
|
||||
|
||||
impl<const N: usize> Error for PageParseError<N> {}
|
||||
|
||||
impl<const N: usize> core::fmt::Display for PageParseError<N> {
|
||||
@@ -38,7 +147,7 @@ impl<const N: usize> core::fmt::Display for PageParseError<N> {
|
||||
match status_code {
|
||||
Some(code) => write!(
|
||||
f,
|
||||
"NFC pages parse error in iteration {iteration} with code: {code:?}: {bytes:02x?}"
|
||||
"NFC pages parse error in iteration {iteration} with {code}: {bytes:02x?}"
|
||||
),
|
||||
None => write!(
|
||||
f,
|
||||
@@ -78,7 +187,7 @@ where
|
||||
.await
|
||||
{
|
||||
Ok(uid) => {
|
||||
info!("PN532 detected NFC target UID: 0x{uid:x?}");
|
||||
info!("PN532 detected NFC target UID: {uid:?}");
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => {
|
||||
@@ -101,7 +210,7 @@ where
|
||||
error => Err(PageParseError {
|
||||
iteration,
|
||||
bytes: arr,
|
||||
status_code: Some(pn532::ErrorCode::try_from(error.copied().unwrap()).unwrap()),
|
||||
status_code: error.copied().map(Into::into),
|
||||
}),
|
||||
}
|
||||
}
|
||||
@@ -129,12 +238,14 @@ where
|
||||
}
|
||||
};
|
||||
|
||||
log::info!("{bytes:x?}");
|
||||
match Self::parse_nfc_page(i, bytes) {
|
||||
Ok(data) => break data,
|
||||
Err(e) => {
|
||||
error!("Parsing page failed: {e}");
|
||||
return Err("Failed to parse NFC page");
|
||||
if e.status_code.is_some_and(|e| e.protocol_error) {
|
||||
// After protocol error, all subsequent reads will fail, so we can stop trying to read more pages.
|
||||
return Err("Failed to parse NFC page");
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user