Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions src/filament/generic.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ def __init__(self,
drying_temp_c: float,
drying_time_hours: float,
manufacturing_date: str, # ISO 8601 date string
td: float = 0.0 # Transmission Distance in mm for HueForge/OrcaSlicer-FullSpectrum
td: float = 0.0, # Transmission Distance in mm for HueForge/OrcaSlicer-FullSpectrum
remaining_weight_grams: float = 0.0, # Measured remaining filament (e.g. written back by a smart scale), 0 = unknown
):
self.source_processor = source_processor
self.unique_id = unique_id
Expand All @@ -43,6 +44,7 @@ def __init__(self,
self.drying_time_hours = drying_time_hours
self.manufacturing_date = manufacturing_date
self.td = td
self.remaining_weight_grams = remaining_weight_grams

if "CF" in self.modifiers:
self.type += "-CF"
Expand Down Expand Up @@ -71,7 +73,7 @@ def pretty_text(self) -> str:
f"- Drying: {self.drying_temp_c:.1f}C for {self.drying_time_hours:.1f} hours",
f"- Manufactured on: {self.manufacturing_date}",
f"- TD: {self.td:.1f} mm"
])
] + ([f"- Remaining: {self.remaining_weight_grams} grams"] if self.remaining_weight_grams else []))

@property
def rgba(self) -> int:
Expand Down Expand Up @@ -102,7 +104,8 @@ def to_dict(self) -> dict:
"drying_temp_c": self.drying_temp_c,
"drying_time_hours": self.drying_time_hours,
"manufacturing_date": self.manufacturing_date,
"td": self.td
"td": self.td,
"remaining_weight_grams": self.remaining_weight_grams
}

@staticmethod
Expand Down
3 changes: 2 additions & 1 deletion src/tag/opentag3d/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ types, and scaling come from JSON; the mapping to `GenericFilament` is explicit.
- Missing print temperature bounds fall back to the target temperature.
- Primary color is retained even when transparent; transparent-black secondary
colors are omitted. Exported colors use ARGB.
- Weight is the nominal filament weight, not measured weight or remaining weight.
- Weight is the nominal filament weight. The measured filament weight is exported
as `remaining_weight_grams` (0 when not measured).
- The unique ID hashes the physical tag UID, since a serial may identify a batch.
- Material names still follow `GenericFilament`'s existing supported-material
validation; unrecognized materials fail cleanly rather than becoming PLA.
Expand Down
4 changes: 3 additions & 1 deletion src/tag/opentag3d/processor.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ def __to_filament(self, scan_result: ScanResult, data: dict) -> GenericFilament:
modifiers=[data["material_mod"]] if data["material_mod"] else [],
colors=colors,
diameter_mm=data["diameter"],
# Target Weight excludes the spool and is not measured/remaining weight.
# Target Weight excludes the spool; the measured weight is exported separately.
weight_grams=data["weight"],
hotend_min_temp_c=hotend_min_temp_c,
hotend_max_temp_c=hotend_max_temp_c,
Expand All @@ -105,4 +105,6 @@ def __to_filament(self, scan_result: ScanResult, data: dict) -> GenericFilament:
# Reuse the other processors' unknown-date sentinel for absent dates.
manufacturing_date=data["mfg_date"] or "0001-01-01",
td=data["td"],
# Measured Filament Weight: actual filament weight, 0 when not measured.
remaining_weight_grams=data["measured_weight"],
)
1 change: 1 addition & 0 deletions src/tag/tigertag/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
OFF_TIMESTAMP = 32
OFF_TD = 44
OFF_METADATA = 48
OFF_MEASURE_AVAILABLE = 76 # u24 BE, remaining quantity in the same unit as the measure
OFF_SIGNATURE = 80

MIN_DATA_LENGTH = 96
Expand Down
10 changes: 10 additions & 0 deletions src/tag/tigertag/processor.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,13 @@ def __parse_tigertag(self, scan_result: ScanResult, user_data: bytes, tag_id: in

timestamp_raw = struct.unpack_from('>I', user_data, Constants.OFF_TIMESTAMP)[0]

# Measure Available: remaining quantity, written back by a smart scale
# (e.g. Tiger Scale). Factory-written tags leave it at 0 (not measured).
available_value = 0
if len(user_data) >= Constants.OFF_MEASURE_AVAILABLE + 3:
available_bytes = user_data[Constants.OFF_MEASURE_AVAILABLE:Constants.OFF_MEASURE_AVAILABLE + 3]
available_value = (available_bytes[0] << 16) | (available_bytes[1] << 8) | available_bytes[2]

material_label = self.registry.material_ids.get(material_id, f"Unknown({material_id})")
material_type = self.registry.material_type_ids.get(material_id, material_label)
filled_type = self.registry.filled_type_ids.get(material_id, "")
Expand All @@ -80,6 +87,7 @@ def __parse_tigertag(self, scan_result: ScanResult, user_data: bytes, tag_id: in

weight_grams = self.__convert_to_grams(measure_value, unit_id)
manufacturing_date = self.__timestamp_to_date(timestamp_raw)
remaining_weight_grams = self.__convert_to_grams(available_value, unit_id) if available_value else 0.0

modifiers = []
for aspect_label in (aspect1_label, aspect2_label):
Expand All @@ -95,6 +103,7 @@ def __parse_tigertag(self, scan_result: ScanResult, user_data: bytes, tag_id: in
self.logger.debug(" Aspect1: %s, Aspect2: %s", aspect1_label, aspect2_label)
self.logger.debug(" Color (ARGB): 0x%08X", argb_color)
self.logger.debug(" Measure: %d %s (%.1f g)", measure_value, unit_label, weight_grams)
self.logger.debug(" Measure Available: %d %s (%.1f g)", available_value, unit_label, remaining_weight_grams)
self.logger.debug(" Nozzle Temp: %d-%d °C", temp_min, temp_max)
self.logger.debug(" Dry: %d °C for %d hours", dry_temp, dry_time)
self.logger.debug(" Bed Temp: %d-%d °C", bed_temp_min, bed_temp_max)
Expand All @@ -120,6 +129,7 @@ def __parse_tigertag(self, scan_result: ScanResult, user_data: bytes, tag_id: in
drying_time_hours=float(dry_time),
manufacturing_date=manufacturing_date,
td=td_mm,
remaining_weight_grams=remaining_weight_grams,
)

except Exception as e:
Expand Down
1 change: 1 addition & 0 deletions test/tags/OpenTag3D/Polar Filament PLA Pure Light Blue.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ drying_temp_c: 65
drying_time_hours: 0
manufacturing_date: '2026-04-03'
td: 0.0
remaining_weight_grams: 0
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
bed_temp_c: 0.0
colors:
- 0xFFDDE5ED
diameter_mm: 1.75
drying_temp_c: 50.0
drying_time_hours: 8.0
hotend_max_temp_c: 230.0
hotend_min_temp_c: 210.0
manufacturer: eSun
manufacturing_date: '2025-11-12'
modifiers:
- Basic
source_processor: TigerTagProcessor
td: 0.0
type: PLA
unique_id: c97d79c4ec83c89e5063834556625468b602e492996bfd674fbbeb238469d031
weight_grams: 1000.0
remaining_weight_grams: 742.0
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@ td: 0.0
type: PLA
unique_id: c97d79c4ec83c89e5063834556625468b602e492996bfd674fbbeb238469d031
weight_grams: 1000.0
remaining_weight_grams: 0.0
9 changes: 9 additions & 0 deletions test/test_opentag3d.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,15 @@ def test_long_payload_ignores_reserved_extension_bytes(processor, scan, payload)
assert result.weight_grams == 1000


def test_measured_weight_is_exported_as_remaining_weight(processor, scan, payload):
# The captured spool was never weighed (measured weight 0 = unknown).
assert processor.process_tag(scan, tag(record(payload))).remaining_weight_grams == 0
payload[164:166] = (742).to_bytes(2, "big")
result = processor.process_tag(scan, tag(record(payload)))
assert result.weight_grams == 1000
assert result.remaining_weight_grams == 742


def test_configuration_runtime_and_webhook(scan, payload, monkeypatch):
# Import the application's real configuration factory without Linux-only
# GPIO/SPI modules; this test never constructs or accesses physical hardware.
Expand Down