From 796c7ca40b46d3a7e0e9d5de901508bad0d1c0b5 Mon Sep 17 00:00:00 2001 From: pulipakaa24 Date: Tue, 27 Jan 2026 21:46:12 -0600 Subject: [PATCH] bugfix --- EMG_Arm/src/core/model_weights.h | 49 +++++++++++++++++++++---------- emg_gui.py | 17 ++++++----- models/emg_lda_classifier.joblib | Bin 3413 -> 3413 bytes serial_stream.py | 21 +++++++++++++ 4 files changed, 65 insertions(+), 22 deletions(-) diff --git a/EMG_Arm/src/core/model_weights.h b/EMG_Arm/src/core/model_weights.h index 1dd5c94..94a35e2 100644 --- a/EMG_Arm/src/core/model_weights.h +++ b/EMG_Arm/src/core/model_weights.h @@ -1,8 +1,7 @@ /** * @file model_weights.h - * @brief Placeholder for trained model weights. - * - * This file should be generated by the Python script (Training Page -> Export for ESP32). + * @brief Trained LDA model weights exported from Python. + * @date 2026-01-27 21:35:17 */ #ifndef MODEL_WEIGHTS_H @@ -16,11 +15,11 @@ /* Class Names */ static const char* MODEL_CLASS_NAMES[MODEL_NUM_CLASSES] = { - "REST", - "OPEN", - "FIST", - "HOOK_EM", - "THUMBS_UP", + "fist", + "hook_em", + "open", + "rest", + "thumbs_up", }; /* Feature Extractor Parameters */ @@ -29,16 +28,36 @@ static const char* MODEL_CLASS_NAMES[MODEL_NUM_CLASSES] = { /* LDA Intercepts/Biases */ static const float LDA_INTERCEPTS[MODEL_NUM_CLASSES] = { - 0.0f, 0.0f, 0.0f, 0.0f, 0.0f + -14.097581f, -2.018629f, -4.478267f, 1.460458f, -5.562349f }; /* LDA Coefficients (Weights) */ static const float LDA_WEIGHTS[MODEL_NUM_CLASSES][MODEL_NUM_FEATURES] = { - {0.0f}, - {0.0f}, - {0.0f}, - {0.0f}, - {0.0f} + /* fist */ + { + 0.070110f, -0.002554f, 0.043924f, 0.020555f, -0.660305f, 0.010691f, -0.074429f, -0.037253f, + 0.057908f, -0.002655f, 0.042119f, -0.052956f, 0.063822f, 0.006184f, -0.025462f, 0.040815f, + }, + /* hook_em */ + { + -0.002511f, 0.001034f, 0.027889f, 0.026006f, 0.183681f, -0.000773f, 0.016791f, -0.027926f, + -0.023321f, 0.000770f, 0.059023f, -0.056021f, 0.237063f, -0.007423f, 0.082101f, -0.021472f, + }, + /* open */ + { + -0.006170f, 0.000208f, -0.041151f, 0.013271f, 0.054508f, -0.002356f, 0.000170f, 0.012941f, + -0.106180f, 0.003538f, -0.013656f, -0.017712f, 0.131131f, -0.002623f, -0.007022f, 0.024497f, + }, + /* rest */ + { + -0.011094f, 0.000160f, -0.012547f, -0.011058f, 0.130577f, -0.001942f, 0.020823f, -0.001961f, + 0.018021f, -0.000404f, -0.065598f, 0.039676f, 0.018679f, -0.001522f, 0.023302f, -0.008474f, + }, + /* thumbs_up */ + { + -0.016738f, 0.000488f, 0.024199f, -0.024643f, -0.044912f, 0.000153f, -0.011080f, 0.043487f, + 0.051828f, -0.001670f, 0.109633f, 0.004154f, -0.460694f, 0.008616f, -0.104097f, -0.020886f, + }, }; -#endif /* MODEL_WEIGHTS_H */ +#endif /* MODEL_WEIGHTS_H */ \ No newline at end of file diff --git a/emg_gui.py b/emg_gui.py index 59bc103..4849611 100644 --- a/emg_gui.py +++ b/emg_gui.py @@ -1918,14 +1918,17 @@ class PredictionPage(BasePage): print("[DEBUG] Starting Edge Prediction (On-Device)...") try: - # Send "start_predict" command to ESP32 - if hasattr(self.stream, 'ser'): - self.stream.ser.write(b'{"cmd": "start_predict"}\n') - self.stream.running = True + # Use the new interface method to start prediction + if hasattr(self.stream, 'start_predict'): + self.stream.start_predict() + self.stream.running = True else: - # Fallback - self.stream.ser.write(b'{"cmd": "start_predict"}\n') - self.stream.running = True + # Fallback for simulated stream or older interface + # Simulated stream doesn't need 'start_predict', just 'start' + if not self.using_real_hardware: + self.stream.start() + else: + raise RuntimeError("Stream object missing start_predict method") except Exception as e: messagebox.showerror("Start Error", f"Failed to start: {e}") diff --git a/models/emg_lda_classifier.joblib b/models/emg_lda_classifier.joblib index 5f2f6fcc5660b2391187d8cdfa5fa56d5b0ca0f8..6757e9ad18232b94e911c32e9debd45aeae27a65 100644 GIT binary patch delta 1292 zcmWO1i$4YccGhix-s%f56oz2axzOS z&78OEw?dSuLqkPU>(T@1JkojGegA{6L|dXQnBvSwpe1 zjFQioK9Fq|6eAoIu>N!`!&mP(3>X|Dwe@AA5TE&*<)%WT&7{W{|7_^oDo<=U!-d9{ zbE%O@-l+39(5urV13k#ParP~!1AQ#ymJebHt+UqrYZ>{7(P88S3>bf%=2wGAP1W%M3ITcbrW&s0C+CvLP zYaG%kGbpZNV+chcEttQWt!5cURvFg1#^-f6aDi}S`&xL)N1U2B?V|KSqWEX7AK@9&?`MQoumUmKSjp{P zm_9hB*^Mqy<7`)mfAi7uLPM^;! zCRgd$YQo#9u=)R3mC2w@>(t88@0azhUAr#BwmoIi3*%KF?GRh`9QDVf-iUoH4?|Gwc**Wb zx&)iv*nBEAZb8dRt-p;I!$2!s=q;YVjHA__3(H$_QTQgM_CuEl2uZssY@41{QLDHFb|(7 zp6&C&h&bDtTQ2#K)wgKdv&I)yjng8RqH=*ox>ucW?f}XT%F-0N1!%$dRHZp{LEvZ? z!_i@ZMqxmz>69m?k3Y%{7-mD%kaRm^;zTk=51KXg>M_xlbei8KLxx6bpGRhA0XW4+ z=#nf8ak*bySG&wZfxD~jtra~mb!++4xF#Lz*)Of$TJym740E$ZW+f1V?ZOniy|86K w@ZvqO6=s?POH}0|;GoNf23h(DI#>`k>?=FD*n!IK-rdiguC>5>x4+Tkn761SM delta 1292 zcmV~$2{hCR8~|WhN0MBrXm@II^ipc4mk{!Malj+IysRJx3Wlm(-RnZ zX-jg{Xa;rOaU};r>r--IBRd-_xmAiIYJcGF`>qx{VzOa!PO$ZBNIrVpRjU)U*+Q|J zoT~3z0~m5zR1SAi!l!4WiBtN=q21^(sjV*y6>z}MoSOo{_OqU!{IY<%Q&FQnSO_jn z7g8b;d~l(Q>D@s|Lkn|%rQ>%6u-d&KUha(}Ff)&THJ;H9jqa?bfOIiLjqbT2;?g0a zBSPmG=^+^YJ8w8)5Qs0k%~ZeG0R~e>NY^C{=syz{W1sAaVsR>zr02lNaibrMBQ67* z93<6K(1-1g7A_tiqu~4Dz;a`H6wu|5noh7n&@N{Fwl(7hru@0?lvf^61S$NG-{xus z*f8O|eEZfdphfft4#sn^>Th+^o2xmH8!0rlzUl(hfMsnac%jqq>7aXWE#WgKtld)C z7U#B?7#3FWkxi=}&s)gB`Cq!~l8XZ1zfFh3G{zo5>@aoWdr3N!pJ&A|6Z4_5Eq?1c z7ab)9zRY7h|nogy{XbAkEhba8*wW$+%l_Cj}F44Q9#tk&=-#|+=; zRmv3>=26OaDsAH+A)*Z0m7jpzu#$dW!P+Ba9S5<_SIQnq%AJ=jMn-ay0lh_O>jzDV zsq6wr#b*H$cxzOEnAx@JTlcvJjtp4@KcDi)R?T6)*~Kz&b@s^fTb~Ov=QhupliwjN zBkQQuyHseQQRO<-YSE{9&Dl<^60{LVCz+Jpf!OXtogxVnrX$5qI%xm(T9Ist$i?ef8|DeS zSnQg#5Iwfs4L0PBb9I`fNSxf<(rJYX(Sv&uNIy4I zIN#@sIWhJ%H(hf>XG#~a(8 xR}a0{XaT)uX3++PaG<(wsh4LBqm!k|mV>2<;63_{rBl{~reRjK&8qop{s$2>f1>~Z diff --git a/serial_stream.py b/serial_stream.py index d3b32a7..cfbd164 100644 --- a/serial_stream.py +++ b/serial_stream.py @@ -240,8 +240,29 @@ class RealSerialStream: start_cmd = {"cmd": "start"} self._send_json(start_cmd) self.state = ConnectionState.STREAMING + self.state = ConnectionState.STREAMING print(f"[SERIAL] Started streaming") + def start_predict(self) -> None: + """ + @brief Start on-device prediction (Edge Inference). + + Sends 'start_predict' command. Device enters PREDICTING state. + Stream receives JSON telemetry instead of raw CSV. + """ + if self.state != ConnectionState.CONNECTED: + raise RuntimeError( + f"Cannot start prediction from state {self.state.name}. " + "Must call connect() first." + ) + + self.serial.reset_input_buffer() + + cmd = {"cmd": "start_predict"} + self._send_json(cmd) + self.state = ConnectionState.STREAMING # Treat as streaming for readline purposes + print(f"[SERIAL] Started prediction mode") + def stop(self) -> None: """ @brief Stop streaming EMG data.