Skip to content

Commit f3e5563

Browse files
committed
feat: implement modular stylus solver components and device runtime handlers
1 parent b5c4644 commit f3e5563

22 files changed

Lines changed: 642 additions & 4152 deletions

EGoTouchService/Device/btmcu/PenUsbTypes.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,9 @@ struct PenSemanticState {
8484

8585
bool hasEraserToggle = false;
8686
uint8_t eraserToggle = 0;
87+
88+
bool hasCurrentFunc = false;
89+
uint8_t currentFunc = 0;
8790
};
8891

8992
struct PenUsbHeader {

EGoTouchService/Device/penevt/PenEventBridge.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -474,6 +474,10 @@ void PenEventBridge::OnPacketReceived(const std::vector<uint8_t>& packet) {
474474
ev.semantic.hasEraserToggle = true;
475475
ev.semantic.eraserToggle = ev.payload[0];
476476
break;
477+
case PenUsbEventCode::PenCurrentFunc:
478+
ev.semantic.hasCurrentFunc = true;
479+
ev.semantic.currentFunc = ev.payload[0];
480+
break;
477481
default:
478482
break;
479483
}

EGoTouchService/Device/runtime/DeviceRuntime.cpp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -483,6 +483,22 @@ void DeviceRuntime::IngestPenEvent(const Himax::Pen::PenEvent& ev) {
483483
break;
484484
}
485485

486+
case EC::PenCurrentFunc: {
487+
uint8_t func = 0;
488+
{
489+
std::lock_guard<std::mutex> lk(m_penStateMu);
490+
m_penState.hasCurrentFunc = ev.semantic.hasCurrentFunc;
491+
m_penState.currentFunc = ev.semantic.hasCurrentFunc ? ev.semantic.currentFunc : 0;
492+
func = m_penState.currentFunc;
493+
}
494+
495+
// 笔双击触控按键 → 触发 Barrel Button (momentary, 下一帧自动复位)
496+
m_vhfReporter.SetBarrelButtonState(true);
497+
LOG_INFO("Runtime", __func__, "MCU",
498+
"PenCurrentFunc: func={} → barrel button pressed (momentary).", func);
499+
break;
500+
}
501+
486502
case EC::EraserToggle: {
487503
uint8_t eraserState = 0;
488504
{

EGoTouchService/Device/runtime/DeviceRuntime.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,9 @@ struct RuntimePenState {
108108

109109
bool hasEraserToggle = false;
110110
uint8_t eraserToggle = 0;
111+
112+
bool hasCurrentFunc = false;
113+
uint8_t currentFunc = 0;
111114
};
112115

113116
// --------------- DeviceRuntime ---------------

EGoTouchService/Device/vhf/VhfReporter.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,8 @@ VhfReporter::StylusDispatchPacket VhfReporter::BuildStylusPacket(
207207
config.sensorCols = m_stylusSensorCols;
208208
config.emitWhenInvalid = m_emitStylusPacketWhenInvalid;
209209
}
210+
config.barrelButton = m_barrelButtonState.exchange(
211+
false, std::memory_order_relaxed);
210212

211213
StylusDispatchPacket built{};
212214
built.packet = VhfStylusPacket::Build(stylus, config);

EGoTouchService/Device/vhf/VhfReporter.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,10 @@ class VhfReporter {
5151
m_eraserState.store(v, std::memory_order_relaxed);
5252
}
5353

54+
void SetBarrelButtonState(bool pressed) {
55+
m_barrelButtonState.store(pressed, std::memory_order_relaxed);
56+
}
57+
5458
bool IsDeviceOpen() const;
5559
void Close();
5660

@@ -84,6 +88,7 @@ class VhfReporter {
8488
std::atomic<bool> m_transpose{false};
8589
std::atomic<bool> m_hadTouchLastFrame{false};
8690
std::atomic<uint8_t> m_eraserState{0};
91+
std::atomic<bool> m_barrelButtonState{false};
8792

8893
int m_stylusSensorRows = 40;
8994
int m_stylusSensorCols = 60;

EGoTouchService/Device/vhf/VhfReporterStylusPacketHelper.h

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ struct Config {
1414
int sensorRows = 40;
1515
int sensorCols = 60;
1616
bool emitWhenInvalid = true;
17+
bool barrelButton = false;
1718
};
1819

1920
namespace detail {
@@ -78,10 +79,13 @@ inline Solvers::StylusPacket Build(const Solvers::StylusFrameData& stylus,
7879

7980
uint8_t penState = 0;
8081
if (stylus.output.tipDown) {
81-
penState |= 0x01u;
82+
penState |= 0x01u; // Tip Switch
83+
}
84+
if (config.barrelButton) {
85+
penState |= 0x02u; // Barrel Switch
8286
}
8387
if (stylus.output.inRange) {
84-
penState |= 0x20u;
88+
penState |= 0x20u; // In Range
8589
}
8690
packet.bytes[1] = penState;
8791
packet.bytes[2] = 0x00u;

EGoTouchService/Solvers/StylusFrameTypes.h

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,18 @@ struct StylusRuntimePost {
213213
bool linearFilterActive = false;
214214
int32_t linearFilterDeltaDim1 = 0;
215215
int32_t linearFilterDeltaDim2 = 0;
216+
217+
// ── CoorSpeedProcess outputs ──
218+
int32_t speedValue = 0;
219+
int32_t speedAvgDx = 0;
220+
int32_t speedAvgDy = 0;
221+
int32_t speedShortAvgDist = 0;
222+
int32_t speedFullAvgDist = 0;
223+
224+
// ── CoorIIRProcess output ──
225+
uint16_t iirCoef = 0;
226+
bool iirFilterActive = false;
227+
216228
#if EGOTOUCH_DIAG
217229
float lfLineFitSlopeA = 0.f;
218230
float lfLineFitInterceptB = 0.f;
@@ -223,6 +235,14 @@ struct StylusRuntimePost {
223235
bool coorReviseActive = false;
224236
int16_t coorReviseCorrectionDim1 = 0;
225237
int16_t coorReviseCorrectionDim2 = 0;
238+
239+
// ── AftCoorProcess diagnostics ──
240+
bool lockActiveX = false;
241+
bool lockActiveY = false;
242+
int32_t lockOffsetX = 0;
243+
int32_t lockOffsetY = 0;
244+
int32_t lockThresholdX = 0;
245+
int32_t lockThresholdY = 0;
226246
#endif
227247
};
228248

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
#pragma once
2+
3+
#include "AsaTypes.hpp"
4+
#include "SolverTypes.h"
5+
6+
#include <algorithm>
7+
#include <cstdint>
8+
9+
namespace Solvers::Stylus {
10+
11+
class AftCoorProcess {
12+
public:
13+
bool m_enabled = true;
14+
15+
// ── Lock thresholds (flash params equivalent) ──
16+
// In-band thresholds (coordinate within sensor interior)
17+
uint8_t m_lockFlashInBandX = 10; // flash[0xA5A]
18+
uint8_t m_lockFlashInBandY = 10; // flash[0xA5B]
19+
20+
// Edge thresholds (coordinate near sensor boundary)
21+
uint8_t m_lockFlashEdgeX = 20; // flash[0xA58]
22+
uint8_t m_lockFlashEdgeY = 20; // flash[0xA59]
23+
24+
// Sensor dimensions (from asaPrmt)
25+
int m_sensorTxCount = 60; // bTxCount
26+
int m_sensorRxCount = 40; // bRxCount
27+
int m_sensorDim1 = 60; // wField00
28+
int m_sensorDim2 = 40; // wField02
29+
30+
// Bypass gate: skip lock when this ASA-style flag is active
31+
bool m_bypassLock = false;
32+
33+
inline void Reset() {
34+
m_startX = 0;
35+
m_startY = 0;
36+
m_lockOffsetX = 0;
37+
m_lockOffsetY = 0;
38+
m_flagLockX = false;
39+
m_flagLockY = false;
40+
m_prevPressure = 0;
41+
}
42+
43+
inline void Process(HeatmapFrame& frame) {
44+
auto& runtime = frame.stylus.runtime;
45+
auto& coor = runtime.post.finalCoor;
46+
const uint16_t curPressure = runtime.pressure.outputPressure;
47+
48+
if (!m_enabled || m_bypassLock) {
49+
// Pass-through: just clamp to sensor bounds
50+
if (coor.valid) {
51+
ClampToSensor(coor);
52+
}
53+
m_prevPressure = curPressure;
54+
return;
55+
}
56+
57+
if (!coor.valid) {
58+
Reset();
59+
m_prevPressure = curPressure;
60+
return;
61+
}
62+
63+
// ── Select thresholds based on coordinate position ──
64+
// TSACore: if coor < 0x401 or coor >= (count-1)*0x400 → edge threshold
65+
const int32_t minBound = 0x401;
66+
const int32_t maxBoundX = (m_sensorTxCount - 1) * Asa::kCoorUnit;
67+
const int32_t maxBoundY = (m_sensorRxCount - 1) * Asa::kCoorUnit;
68+
69+
uint32_t thresholdX;
70+
uint32_t thresholdY;
71+
72+
if (coor.dim1 < minBound || coor.dim2 < minBound ||
73+
coor.dim1 >= maxBoundX || coor.dim2 >= maxBoundY) {
74+
// Near boundary → use edge thresholds (more tolerant)
75+
thresholdX = (static_cast<uint32_t>(m_lockFlashEdgeX) *
76+
static_cast<uint32_t>(m_sensorTxCount) * Asa::kCoorUnit) /
77+
static_cast<uint32_t>(std::max(1, m_sensorDim1));
78+
thresholdY = (static_cast<uint32_t>(m_lockFlashEdgeY) *
79+
static_cast<uint32_t>(m_sensorRxCount) * Asa::kCoorUnit) /
80+
static_cast<uint32_t>(std::max(1, m_sensorDim2));
81+
} else {
82+
// Interior → use in-band thresholds
83+
thresholdX = (static_cast<uint32_t>(m_lockFlashInBandX) *
84+
static_cast<uint32_t>(m_sensorTxCount) * Asa::kCoorUnit) /
85+
static_cast<uint32_t>(std::max(1, m_sensorDim1));
86+
thresholdY = (static_cast<uint32_t>(m_lockFlashInBandY) *
87+
static_cast<uint32_t>(m_sensorRxCount) * Asa::kCoorUnit) /
88+
static_cast<uint32_t>(std::max(1, m_sensorDim2));
89+
}
90+
91+
// ── Pen-down transition: lock to start position ──
92+
if (curPressure != 0 && m_prevPressure == 0) {
93+
m_startX = coor.dim1;
94+
m_startY = coor.dim2;
95+
m_flagLockX = true;
96+
m_flagLockY = true;
97+
m_lockOffsetX = 0;
98+
m_lockOffsetY = 0;
99+
}
100+
101+
// ── X-axis lock ──
102+
if (m_flagLockX) {
103+
const int32_t diff = coor.dim1 - m_startX;
104+
const uint32_t absDiff = static_cast<uint32_t>(std::abs(diff));
105+
if (absDiff > thresholdX) {
106+
m_flagLockX = false; // movement exceeded threshold → release lock
107+
}
108+
if (m_flagLockX) {
109+
m_lockOffsetX = diff; // track offset while locked
110+
}
111+
}
112+
113+
// ── Y-axis lock ──
114+
if (m_flagLockY) {
115+
const int32_t diff = coor.dim2 - m_startY;
116+
const uint32_t absDiff = static_cast<uint32_t>(std::abs(diff));
117+
if (absDiff > thresholdY) {
118+
m_flagLockY = false;
119+
}
120+
if (m_flagLockY) {
121+
m_lockOffsetY = diff;
122+
}
123+
}
124+
125+
// ── Apply offset and clamp ──
126+
int32_t finalX = coor.dim1 - m_lockOffsetX;
127+
int32_t finalY = coor.dim2 - m_lockOffsetY;
128+
129+
finalX = std::clamp(finalX, 0, m_sensorTxCount * Asa::kCoorUnit);
130+
finalY = std::clamp(finalY, 0, m_sensorRxCount * Asa::kCoorUnit);
131+
132+
coor.dim1 = finalX;
133+
coor.dim2 = finalY;
134+
135+
#if EGOTOUCH_DIAG
136+
runtime.post.lockActiveX = m_flagLockX;
137+
runtime.post.lockActiveY = m_flagLockY;
138+
runtime.post.lockOffsetX = m_lockOffsetX;
139+
runtime.post.lockOffsetY = m_lockOffsetY;
140+
runtime.post.lockThresholdX = static_cast<int32_t>(thresholdX);
141+
runtime.post.lockThresholdY = static_cast<int32_t>(thresholdY);
142+
#endif
143+
144+
m_prevPressure = curPressure;
145+
}
146+
147+
private:
148+
int32_t m_startX = 0;
149+
int32_t m_startY = 0;
150+
int32_t m_lockOffsetX = 0;
151+
int32_t m_lockOffsetY = 0;
152+
bool m_flagLockX = false;
153+
bool m_flagLockY = false;
154+
uint16_t m_prevPressure = 0;
155+
156+
inline void ClampToSensor(Asa::AsaCoorResult& coor) const {
157+
coor.dim1 = std::clamp(coor.dim1, 0, m_sensorTxCount * Asa::kCoorUnit);
158+
coor.dim2 = std::clamp(coor.dim2, 0, m_sensorRxCount * Asa::kCoorUnit);
159+
}
160+
};
161+
162+
} // namespace Solvers::Stylus

0 commit comments

Comments
 (0)