diff --git a/extensions/go/ScannerHID/icon.ico b/extensions/go/ScannerHID/icon.ico new file mode 100644 index 0000000..58a95bd Binary files /dev/null and b/extensions/go/ScannerHID/icon.ico differ diff --git a/extensions/go/ScannerHID/main.go b/extensions/go/ScannerHID/main.go index bc74b24..093aa90 100644 --- a/extensions/go/ScannerHID/main.go +++ b/extensions/go/ScannerHID/main.go @@ -1,12 +1,15 @@ package main import ( + _ "embed" "encoding/json" "flag" "fmt" "image/color" "net/http" "os" + "strconv" + "strings" "time" "fyne.io/fyne/v2" @@ -17,10 +20,35 @@ import ( "github.com/google/gousb" ) +//go:embed icon.ico +var iconData []byte + +type HexUint16 uint16 + +func (h HexUint16) MarshalJSON() ([]byte, error) { + return json.Marshal(fmt.Sprintf("0x%04X", h)) +} + +func (h *HexUint16) UnmarshalJSON(data []byte) error { + var s string + if err := json.Unmarshal(data, &s); err != nil { + return err + } + s = strings.TrimPrefix(s, "0x") + val, err := strconv.ParseUint(s, 16, 16) + if err != nil { + return err + } + *h = HexUint16(val) + return nil +} + type Config struct { - TargetURL string `json:"target_url"` - VendorID uint16 `json:"vendor_id"` - ProductID uint16 `json:"product_id"` + TargetURL string `json:"target_url"` + VendorID HexUint16 `json:"vendor_id"` + ProductID HexUint16 `json:"product_id"` + FallbackVID HexUint16 `json:"fallback_vendor_id"` + FallbackPID HexUint16 `json:"fallback_product_id"` } var hidMap = map[byte]string{ @@ -49,15 +77,16 @@ func (b *BridgeApp) saveConfig() { func loadConfig() Config { conf := Config{ - TargetURL: "https://scanner.sekidesu.xyz/scan", - VendorID: 0xFFFF, - ProductID: 0x0035, + TargetURL: "https://scanner.sekidesu.xyz/scan", + VendorID: 0xFFFF, + ProductID: 0x0035, + FallbackVID: 0x04B3, + FallbackPID: 0x3107, } file, err := os.ReadFile("config.json") if err == nil { json.Unmarshal(file, &conf) } else { - // Create default config if missing data, _ := json.MarshalIndent(conf, "", " ") os.WriteFile("config.json", data, 0644) } @@ -82,6 +111,8 @@ func main() { a := app.New() w := a.NewWindow("POS Hardware Bridge (Go)") + w.SetIcon(fyne.NewStaticResource("icon.ico", iconData)) + bridge.window = w bridge.urlEntry = widget.NewEntry() bridge.urlEntry.SetText(conf.TargetURL) @@ -90,18 +121,18 @@ func main() { bridge.logList = widget.NewList( func() int { return len(bridge.logs) }, - func() fyne.CanvasObject { return widget.NewLabel("template") }, - func(i widget.ListItemID, o fyne.CanvasObject) { - o.(*widget.Label).SetText(bridge.logs[i]) - }, + func() fyne.CanvasObject { return widget.NewLabel("template") }, + func(i widget.ListItemID, o fyne.CanvasObject) { + o.(*widget.Label).SetText(bridge.logs[i]) + }, ) content := container.NewBorder( container.NewVBox( widget.NewLabel("Target POS Endpoint:"), - bridge.urlEntry, - bridge.status, - widget.NewLabel("Activity Log:"), + bridge.urlEntry, + bridge.status, + widget.NewLabel("Activity Log:"), ), nil, nil, nil, bridge.logList, @@ -129,18 +160,18 @@ func (b *BridgeApp) addLog(msg string) { ts := time.Now().Format("15:04:05") formatted := fmt.Sprintf("[%s] %s", ts, msg) - if b.isCLI { - fmt.Println(formatted) - return - } + if b.isCLI { + fmt.Println(formatted) + return + } - fyne.DoAndWait(func() { - b.logs = append([]string{formatted}, b.logs...) - if len(b.logs) > 15 { - b.logs = b.logs[:15] - } - b.logList.Refresh() - }) + fyne.DoAndWait(func() { + b.logs = append([]string{formatted}, b.logs...) + if len(b.logs) > 15 { + b.logs = b.logs[:15] + } + b.logList.Refresh() + }) } func (b *BridgeApp) updateStatus(msg string, col color.Color) { @@ -179,13 +210,17 @@ func (b *BridgeApp) usbListenLoop() { for { dev, err := ctx.OpenDeviceWithVIDPID(gousb.ID(b.config.VendorID), gousb.ID(b.config.ProductID)) + if (err != nil || dev == nil) && (b.config.FallbackVID != 0) { + dev, err = ctx.OpenDeviceWithVIDPID(gousb.ID(b.config.FallbackVID), gousb.ID(b.config.FallbackPID)) + } + if err != nil || dev == nil { b.updateStatus("Scanner unplugged. Waiting...", color.NRGBA{200, 0, 0, 255}) time.Sleep(2 * time.Second) continue } - b.updateStatus("Scanner Locked & Ready", color.NRGBA{0, 180, 0, 255}) + b.updateStatus(fmt.Sprintf("Scanner Ready (0x%04X)", dev.Desc.Vendor), color.NRGBA{0, 180, 0, 255}) intf, done, err := dev.DefaultInterface() if err != nil { diff --git a/extensions/platformio/DISPLAY_SEGMENTS.xcf b/extensions/platformio/DISPLAY_SEGMENTS.xcf new file mode 100644 index 0000000..7cc0895 Binary files /dev/null and b/extensions/platformio/DISPLAY_SEGMENTS.xcf differ diff --git a/extensions/platformio/WIRING.xcf b/extensions/platformio/WIRING.xcf new file mode 100644 index 0000000..0bd6fd3 Binary files /dev/null and b/extensions/platformio/WIRING.xcf differ diff --git a/extensions/platformio/keypad_read/src/main.cpp b/extensions/platformio/keypad_read/src/main.cpp index a98a5b6..577854e 100644 --- a/extensions/platformio/keypad_read/src/main.cpp +++ b/extensions/platformio/keypad_read/src/main.cpp @@ -1,48 +1,76 @@ #include const int BUZZER_PIN = 6; -const int allPins[] = {0, 1, 2, 3, 4, 5, 7}; +const int pins[] = {0, 1, 2, 3, 4, 5, 7}; const int numPins = 7; -unsigned long lastDebounceTime = 0; -const int debounceDelay = 250; +struct KeyMap { + int strobe; + int target; + char key; +}; + +// Your discovered mapping +KeyMap keypad[] = {{7, 1, '1'}, {7, 2, '2'}, {3, 7, '3'}, {5, 0, 'C'}, + {7, 0, 'T'}, {4, 1, 'm'}, {1, 2, 'Y'}, {5, 3, '6'}, + {1, 0, '+'}, {2, 0, 'Z'}, {1, 5, '7'}, {5, 2, 'U'}, + {2, 5, '8'}, {3, 5, '9'}, {2, 4, 'M'}, {3, 1, '4'}, + {0, 2, '5'}, {3, 4, 'P'}, {4, 0, '$'}, {0, 4, 'L'}}; void setup() { Serial.begin(115200); - for (int i = 0; i < numPins; i++) { - pinMode(allPins[i], INPUT_PULLUP); + for (int p : pins) { + pinMode(p, INPUT_PULLUP); } + // Keep buzzer pin high-impedance so it doesn't sink the matrix + pinMode(BUZZER_PIN, INPUT_PULLUP); } -void loop() { - // Only scan if we aren't in the middle of a "hit" lockout - if (millis() - lastDebounceTime < debounceDelay) return; +void playBeep() { + analogWrite(BUZZER_PIN, 10); + + delay(50); // Beep duration + + analogWrite(BUZZER_PIN, 0); // Turn it off + pinMode(BUZZER_PIN, INPUT_PULLUP); // Reset to input for the matrix +} +char getKeyPressed() { for (int s = 0; s < numPins; s++) { - int strobe = allPins[s]; + int strobe = pins[s]; pinMode(strobe, OUTPUT); digitalWrite(strobe, LOW); - delayMicroseconds(100); // Slightly longer for stability + delayMicroseconds(50); for (int i = 0; i < numPins; i++) { - int target = allPins[i]; - if (target == strobe) continue; + int target = pins[i]; + if (target == strobe) + continue; if (digitalRead(target) == LOW) { - // WE FOUND ONE! - Serial.print("SINK:"); - Serial.print(strobe); - Serial.print(" READ:"); - Serial.println(target); + for (int k = 0; k < 20; k++) { + if (keypad[k].strobe == strobe && keypad[k].target == target) { - //tone(BUZZER_PIN, 2000, 50); - lastDebounceTime = millis(); - - pinMode(strobe, INPUT_PULLUP); - return; // Jump out of the whole loop to reset the lockout + // Visual and Audio feedback + Serial.print("Pressed: "); + Serial.println(keypad[k].key); + playBeep(); + + while (digitalRead(target) == LOW) + ; // Wait for release + pinMode(strobe, INPUT_PULLUP); + return keypad[k].key; + } + } } } pinMode(strobe, INPUT_PULLUP); } -} \ No newline at end of file + return '\0'; +} + +void loop() { + getKeyPressed(); + delay(10); +}