@@ -22,14 +22,45 @@ import { checkAndUpdateAlerts } from '@/lib/alerts';
2222 * }
2323 */
2424
25- /** Returns true if tray_uuid is a real Bambu spool serial (not empty, unknown, or all zeros) */
25+ /** Returns true if tray_uuid/rfid is a real spool identifier (not empty, unknown, or all zeros) */
2626function isValidTrayUuid ( tray_uuid : string | undefined | null ) : boolean {
2727 if ( ! tray_uuid || tray_uuid === 'unknown' || tray_uuid === '' ) return false ;
2828 // ha-bambulab reports all zeros for non-Bambu spools without RFID tags
2929 if ( tray_uuid . replace ( / 0 / g, '' ) === '' ) return false ;
3030 return true ;
3131}
3232
33+ /**
34+ * Material density lookup (g/cm³) for converting filament length to weight.
35+ * Used when Creality printers report usage in cm instead of grams.
36+ * Standard filament diameter: 1.75mm
37+ */
38+ const MATERIAL_DENSITY : Record < string , number > = {
39+ PLA : 1.24 ,
40+ 'PLA+' : 1.24 ,
41+ PETG : 1.27 ,
42+ ABS : 1.04 ,
43+ ASA : 1.07 ,
44+ TPU : 1.21 ,
45+ PC : 1.20 ,
46+ PA : 1.14 , // Nylon
47+ 'PA-CF' : 1.35 ,
48+ 'PA-GF' : 1.36 ,
49+ PVA : 1.23 ,
50+ HIPS : 1.04 ,
51+ } ;
52+
53+ /**
54+ * Convert filament length (cm) to weight (grams).
55+ * Uses filament diameter of 1.75mm and material-specific density.
56+ */
57+ function lengthToWeight ( lengthCm : number , material ?: string ) : number {
58+ const radiusCm = 0.0875 ; // 1.75mm / 2, converted to cm
59+ const volumeCm3 = Math . PI * radiusCm * radiusCm * lengthCm ;
60+ const density = ( material && MATERIAL_DENSITY [ material . toUpperCase ( ) ] ) || MATERIAL_DENSITY . PLA ;
61+ return volumeCm3 * density ;
62+ }
63+
3364export async function POST ( request : NextRequest ) {
3465 try {
3566 const body = await request . json ( ) ;
@@ -71,9 +102,18 @@ export async function POST(request: NextRequest) {
71102
72103 // Handle spool_usage event - deduct filament weight from spool
73104 if ( event === 'spool_usage' ) {
74- const { used_weight, active_tray_id, tray_uuid } = body ;
105+ const { used_weight, used_length, active_tray_id, tray_uuid, material } = body ;
106+
107+ // Determine weight to deduct: either directly provided (Bambu) or converted from length (Creality)
108+ let weightToDeduct = used_weight ;
109+ let lengthConverted = false ;
110+ if ( ( ! weightToDeduct || weightToDeduct <= 0 ) && used_length && used_length > 0 ) {
111+ weightToDeduct = lengthToWeight ( used_length , material ) ;
112+ lengthConverted = true ;
113+ console . log ( `Converted ${ used_length } cm to ${ weightToDeduct . toFixed ( 2 ) } g (material: ${ material || 'PLA default' } )` ) ;
114+ }
75115
76- if ( ! used_weight || used_weight <= 0 ) {
116+ if ( ! weightToDeduct || weightToDeduct <= 0 ) {
77117 return NextResponse . json ( { status : 'ignored' , reason : 'no weight to deduct' } ) ;
78118 }
79119
@@ -102,18 +142,29 @@ export async function POST(request: NextRequest) {
102142 } ) ;
103143 }
104144
145+ // If we have a matched spool and converted from length, try to use the spool's
146+ // actual filament density for a more accurate conversion
147+ if ( lengthConverted && matchedSpool . filament ?. material ) {
148+ const betterWeight = lengthToWeight ( used_length , matchedSpool . filament . material ) ;
149+ if ( betterWeight !== weightToDeduct ) {
150+ console . log ( `Refined conversion using spool material ${ matchedSpool . filament . material } : ${ weightToDeduct . toFixed ( 2 ) } g -> ${ betterWeight . toFixed ( 2 ) } g` ) ;
151+ weightToDeduct = betterWeight ;
152+ }
153+ }
154+
105155 // Deduct the used weight from the spool
106- await client . useWeight ( matchedSpool . id , used_weight ) ;
156+ await client . useWeight ( matchedSpool . id , weightToDeduct ) ;
107157
108158 // Check low filament alerts (fire-and-forget)
109159 checkAndUpdateAlerts ( ) . catch ( err => console . error ( 'Alert check failed:' , err ) ) ;
110160
111- console . log ( `Deducted ${ used_weight } g from spool #${ matchedSpool . id } (${ matchedSpool . filament . name } )` ) ;
161+ const deductionNote = lengthConverted ? ` (converted from ${ used_length } cm)` : '' ;
162+ console . log ( `Deducted ${ weightToDeduct . toFixed ( 2 ) } g${ deductionNote } from spool #${ matchedSpool . id } (${ matchedSpool . filament . name } )` ) ;
112163
113- // Store the spool serial number (tray_uuid) if we have a valid one
164+ // Store the spool serial/RFID if we have a valid one
114165 // This enables future auto-matching when the same spool is reinserted
115- // tray_uuid is the Bambu spool serial number, unique per physical spool
116- // (unlike tag_uid which differs per RFID tag - each spool has 2 tags)
166+ // For Bambu: tray_uuid is the spool serial ( unique per physical spool)
167+ // For Creality: rfid is a numeric RFID tag ID
117168 let tagStored = false ;
118169 if ( isValidTrayUuid ( tray_uuid ) ) {
119170 // Check if this spool already has this serial number stored
@@ -149,19 +200,20 @@ export async function POST(request: NextRequest) {
149200 type : 'usage' ,
150201 spoolId : matchedSpool . id ,
151202 spoolName : matchedSpool . filament . name ,
152- deducted : used_weight ,
153- newWeight : matchedSpool . remaining_weight - used_weight ,
203+ deducted : weightToDeduct ,
204+ newWeight : matchedSpool . remaining_weight - weightToDeduct ,
154205 trayId : active_tray_id ,
155206 timestamp : Date . now ( ) ,
156207 } ;
157208 spoolEvents . emit ( SPOOL_UPDATED , updateEvent ) ;
158209
159210 await createActivityLog ( {
160211 type : 'spool_usage' ,
161- message : `Deducted ${ used_weight } g from spool #${ matchedSpool . id } (${ matchedSpool . filament . name } )` ,
212+ message : `Deducted ${ weightToDeduct . toFixed ( 2 ) } g ${ deductionNote } from spool #${ matchedSpool . id } (${ matchedSpool . filament . name } )` ,
162213 details : {
163214 spoolId : matchedSpool . id ,
164- usedWeight : used_weight ,
215+ usedWeight : weightToDeduct ,
216+ ...( lengthConverted && { usedLengthCm : used_length } ) ,
165217 trayId : active_tray_id ,
166218 tagStored,
167219 } ,
@@ -170,8 +222,8 @@ export async function POST(request: NextRequest) {
170222 return NextResponse . json ( {
171223 status : 'success' ,
172224 spoolId : matchedSpool . id ,
173- deducted : used_weight ,
174- newRemainingWeight : matchedSpool . remaining_weight - used_weight ,
225+ deducted : weightToDeduct ,
226+ newRemainingWeight : matchedSpool . remaining_weight - weightToDeduct ,
175227 tagStored,
176228 } ) ;
177229 }
@@ -186,7 +238,8 @@ export async function POST(request: NextRequest) {
186238
187239 // Check if tray is now empty (no filament, or explicitly "Empty")
188240 // ha-bambulab reports name="Empty" when tray has no filament
189- const trayIsEmpty = ! name || name . toLowerCase ( ) === 'empty' || name === '' ;
241+ // ha_creality_ws reports empty string or no name when slot is empty
242+ const trayIsEmpty = ! name || name . toLowerCase ( ) === 'empty' || name === '' || name === 'unavailable' ;
190243
191244 if ( trayIsEmpty ) {
192245 // Auto-unassign any spool currently assigned to this tray
0 commit comments