moved stuff + scanner com/hid edits
This commit is contained in:
2
extensions/golang/DataToolsGO/.gitignore
vendored
Normal file
2
extensions/golang/DataToolsGO/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
config.json
|
||||
imageTools-*
|
||||
42
extensions/golang/DataToolsGO/build.sh
Executable file
42
extensions/golang/DataToolsGO/build.sh
Executable file
@@ -0,0 +1,42 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Define binary names
|
||||
LINUX_BIN="imageTools-linux"
|
||||
LINUX_ARM_BIN="imageTools-linuxARMv7"
|
||||
WINDOWS_BIN="imageTools-windows.exe"
|
||||
|
||||
echo "Starting build process..."
|
||||
|
||||
# Build for Linux (64-bit)
|
||||
echo "Building for Linux..."
|
||||
GOOS=linux GOARCH=amd64 go build -o "$LINUX_BIN" main.go
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "Successfully built: $LINUX_BIN"
|
||||
else
|
||||
echo "Failed to build Linux binary"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Build for Windows (64-bit)
|
||||
echo "Building for Windows..."
|
||||
GOOS=windows GOARCH=amd64 go build -o "$WINDOWS_BIN" main.go
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "Successfully built: $WINDOWS_BIN"
|
||||
else
|
||||
echo "Failed to build Windows binary"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Build for Linux ARM (ARMv7)
|
||||
echo "Building for Linux ARMv7..."
|
||||
GOOS=linux GOARCH=arm GOARM=7 go build -o "$LINUX_ARM_BIN" main.go
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "Successfully built: $LINUX_ARM_BIN"
|
||||
else
|
||||
echo "Failed to build Linux ARMv7 binary"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Build complete."
|
||||
5
extensions/golang/DataToolsGO/go.mod
Normal file
5
extensions/golang/DataToolsGO/go.mod
Normal file
@@ -0,0 +1,5 @@
|
||||
module dataTools
|
||||
|
||||
go 1.25.7
|
||||
|
||||
require github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646
|
||||
2
extensions/golang/DataToolsGO/go.sum
Normal file
2
extensions/golang/DataToolsGO/go.sum
Normal file
@@ -0,0 +1,2 @@
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
|
||||
84
extensions/golang/DataToolsGO/main.go
Normal file
84
extensions/golang/DataToolsGO/main.go
Normal file
@@ -0,0 +1,84 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"image"
|
||||
"image/jpeg"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/nfnt/resize"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Command line arguments
|
||||
dirPath := flag.String("dir", "./", "Directory containing images")
|
||||
maxWidth := flag.Uint("width", 1000, "Maximum width for resizing")
|
||||
quality := flag.Int("quality", 75, "JPEG quality (1-100)")
|
||||
flag.Parse()
|
||||
|
||||
files, err := os.ReadDir(*dirPath)
|
||||
if err != nil {
|
||||
fmt.Printf("Error reading directory: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("Processing images in %s (Max Width: %d, Quality: %d)\n", *dirPath, *maxWidth, *quality)
|
||||
|
||||
for _, file := range files {
|
||||
if file.IsDir() {
|
||||
continue
|
||||
}
|
||||
|
||||
ext := strings.ToLower(filepath.Ext(file.Name()))
|
||||
if ext != ".jpg" && ext != ".jpeg" && ext != ".png" {
|
||||
continue
|
||||
}
|
||||
|
||||
filePath := filepath.Join(*dirPath, file.Name())
|
||||
processImage(filePath, *maxWidth, *quality)
|
||||
}
|
||||
|
||||
fmt.Println("Done. Your storage can finally breathe again.")
|
||||
}
|
||||
|
||||
func processImage(path string, maxWidth uint, quality int) {
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
fmt.Printf("Failed to open %s: %v\n", path, err)
|
||||
return
|
||||
}
|
||||
|
||||
img, _, err := image.Decode(file)
|
||||
file.Close()
|
||||
if err != nil {
|
||||
fmt.Printf("Failed to decode %s: %v\n", path, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Only resize if original is wider than maxWidth
|
||||
bounds := img.Bounds()
|
||||
var finalImg image.Image
|
||||
if uint(bounds.Dx()) > maxWidth {
|
||||
finalImg = resize.Resize(maxWidth, 0, img, resize.Lanczos3)
|
||||
fmt.Printf("Resized and compressed: %s\n", filepath.Base(path))
|
||||
} else {
|
||||
finalImg = img
|
||||
fmt.Printf("Compressed (no resize needed): %s\n", filepath.Base(path))
|
||||
}
|
||||
|
||||
// Overwrite the original file
|
||||
out, err := os.Create(path)
|
||||
if err != nil {
|
||||
fmt.Printf("Failed to create output file %s: %v\n", path, err)
|
||||
return
|
||||
}
|
||||
defer out.Close()
|
||||
|
||||
err = jpeg.Encode(out, finalImg, &jpeg.Options{Quality: quality})
|
||||
if err != nil {
|
||||
fmt.Printf("Failed to encode %s: %v\n", path, err)
|
||||
}
|
||||
}
|
||||
8
extensions/golang/MPStudyGO/go.mod
Normal file
8
extensions/golang/MPStudyGO/go.mod
Normal file
@@ -0,0 +1,8 @@
|
||||
module MPPOS
|
||||
|
||||
go 1.25.7
|
||||
|
||||
require (
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/joho/godotenv v1.5.1
|
||||
)
|
||||
4
extensions/golang/MPStudyGO/go.sum
Normal file
4
extensions/golang/MPStudyGO/go.sum
Normal file
@@ -0,0 +1,4 @@
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||
143
extensions/golang/MPStudyGO/main.go
Normal file
143
extensions/golang/MPStudyGO/main.go
Normal file
@@ -0,0 +1,143 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/joho/godotenv"
|
||||
)
|
||||
|
||||
// Request structures based on MP documentation
|
||||
type PrintRequest struct {
|
||||
Type string `json:"type"`
|
||||
ExternalReference string `json:"external_reference"`
|
||||
Config PrintConfig `json:"config"`
|
||||
Content string `json:"content"`
|
||||
}
|
||||
|
||||
type PrintConfig struct {
|
||||
Point PointSettings `json:"point"`
|
||||
}
|
||||
|
||||
type PointSettings struct {
|
||||
TerminalID string `json:"terminal_id"`
|
||||
Subtype string `json:"subtype"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
err := godotenv.Load("../.env")
|
||||
if err != nil {
|
||||
fmt.Println("Error loading .env file")
|
||||
}
|
||||
|
||||
// Example receipt using supported tags: {b}, {center}, {br}, {s}
|
||||
receiptContent := "{center}{b}SEKIPOS VENTA{/b}{br}" +
|
||||
"--------------------------------{br}" +
|
||||
"{left}Producto: Choripan Premium{br}" +
|
||||
"{left}Total: $5.500{br}" +
|
||||
"--------------------------------{br}" +
|
||||
"{center}{s}Gracias por su compra{/s}{br}"
|
||||
|
||||
resp, err := SendPrintAction(receiptContent)
|
||||
if err != nil {
|
||||
fmt.Printf("Error: %v\n", err)
|
||||
return
|
||||
}
|
||||
fmt.Printf("Response: %s\n", resp)
|
||||
}
|
||||
|
||||
func SendPrintAction(content string) (string, error) {
|
||||
apiURL := "https://api.mercadopago.com/terminals/v1/actions"
|
||||
accessToken := os.Getenv("MP_ACCESS_TOKEN")
|
||||
terminalID := os.Getenv("MP_TERMINAL_ID")
|
||||
|
||||
payload := PrintRequest{
|
||||
Type: "print", // Required
|
||||
ExternalReference: fmt.Sprintf("ref_%d", time.Now().Unix()),
|
||||
Config: PrintConfig{
|
||||
Point: PointSettings{
|
||||
TerminalID: terminalID,
|
||||
Subtype: "custom", // For text with tags
|
||||
},
|
||||
},
|
||||
Content: content, // Must be 100-4096 chars
|
||||
}
|
||||
|
||||
jsonData, _ := json.Marshal(payload)
|
||||
|
||||
req, _ := http.NewRequest("POST", apiURL, bytes.NewBuffer(jsonData))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("Authorization", "Bearer "+accessToken)
|
||||
// Mandatory Unique UUID V4
|
||||
req.Header.Set("X-Idempotency-Key", uuid.New().String())
|
||||
|
||||
client := &http.Client{Timeout: 10 * time.Second}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
|
||||
if resp.StatusCode != http.StatusCreated && resp.StatusCode != http.StatusOK {
|
||||
return "", fmt.Errorf("MP Error %d: %s", resp.StatusCode, string(body))
|
||||
}
|
||||
|
||||
return string(body), nil
|
||||
}
|
||||
|
||||
func PartialRefund(orderID string, paymentID string, amount string) (string, error) {
|
||||
// Endpoint para reembolsos según la referencia de API
|
||||
apiURL := fmt.Sprintf("https://api.mercadopago.com/v1/orders/%s/refund", orderID)
|
||||
token := os.Getenv("MP_ACCESS_TOKEN")
|
||||
|
||||
payload := map[string]interface{}{
|
||||
"transactions": []map[string]string{
|
||||
{
|
||||
"id": paymentID,
|
||||
"amount": amount, // Debe ser un string sin decimales
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
body, _ := json.Marshal(payload)
|
||||
req, _ := http.NewRequest("POST", apiURL, bytes.NewBuffer(body))
|
||||
req.Header.Set("Authorization", "Bearer "+token)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("X-Idempotency-Key", uuid.New().String())
|
||||
|
||||
client := &http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
resBody, _ := io.ReadAll(resp.Body)
|
||||
return string(resBody), nil
|
||||
}
|
||||
|
||||
func GetOrderStatus(orderID string) (string, error) {
|
||||
apiURL := fmt.Sprintf("https://api.mercadopago.com/v1/orders/%s", orderID)
|
||||
token := os.Getenv("MP_ACCESS_TOKEN")
|
||||
|
||||
req, _ := http.NewRequest("GET", apiURL, nil)
|
||||
req.Header.Set("Authorization", "Bearer "+token)
|
||||
|
||||
client := &http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
resBody, _ := io.ReadAll(resp.Body)
|
||||
return string(resBody), nil
|
||||
}
|
||||
2
extensions/golang/ScannerCOM/.gitignore
vendored
Normal file
2
extensions/golang/ScannerCOM/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
config.json
|
||||
COMScannerGO-*
|
||||
42
extensions/golang/ScannerCOM/build.sh
Executable file
42
extensions/golang/ScannerCOM/build.sh
Executable file
@@ -0,0 +1,42 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Define binary names
|
||||
LINUX_BIN="COMScannerGO-linux"
|
||||
LINUX_ARM_BIN="COMScannerGO-linuxARMv7"
|
||||
WINDOWS_BIN="COMScannerGO-windows.exe"
|
||||
|
||||
echo "Starting build process..."
|
||||
|
||||
# Build for Linux (64-bit)
|
||||
echo "Building for Linux..."
|
||||
GOOS=linux GOARCH=amd64 go build -o "$LINUX_BIN" main.go
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "Successfully built: $LINUX_BIN"
|
||||
else
|
||||
echo "Failed to build Linux binary"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Build for Windows (64-bit)
|
||||
echo "Building for Windows..."
|
||||
GOOS=windows GOARCH=amd64 go build -o "$WINDOWS_BIN" main.go
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "Successfully built: $WINDOWS_BIN"
|
||||
else
|
||||
echo "Failed to build Windows binary"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Build for Linux ARM (ARMv7)
|
||||
echo "Building for Linux ARMv7..."
|
||||
GOOS=linux GOARCH=arm GOARM=7 go build -o "$LINUX_ARM_BIN" main.go
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "Successfully built: $LINUX_ARM_BIN"
|
||||
else
|
||||
echo "Failed to build Linux ARMv7 binary"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Build complete."
|
||||
7
extensions/golang/ScannerCOM/go.mod
Normal file
7
extensions/golang/ScannerCOM/go.mod
Normal file
@@ -0,0 +1,7 @@
|
||||
module ScannerGO
|
||||
|
||||
go 1.24.0
|
||||
|
||||
require github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07
|
||||
|
||||
require golang.org/x/sys v0.41.0 // indirect
|
||||
4
extensions/golang/ScannerCOM/go.sum
Normal file
4
extensions/golang/ScannerCOM/go.sum
Normal file
@@ -0,0 +1,4 @@
|
||||
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07 h1:UyzmZLoiDWMRywV4DUYb9Fbt8uiOSooupjTq10vpvnU=
|
||||
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
|
||||
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
|
||||
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
175
extensions/golang/ScannerCOM/main.go
Normal file
175
extensions/golang/ScannerCOM/main.go
Normal file
@@ -0,0 +1,175 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/tarm/serial"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Port string `json:"port"`
|
||||
URL string `json:"url"`
|
||||
BaudRate int `json:"baud"`
|
||||
Delimiter string `json:"delimiter"`
|
||||
FlowControl string `json:"flow_control"`
|
||||
}
|
||||
|
||||
var defaultConfig = Config{
|
||||
Port: "/dev/ttyACM0",
|
||||
URL: "https://scanner.sekidesu.xyz/scan",
|
||||
BaudRate: 115200,
|
||||
Delimiter: "\n",
|
||||
FlowControl: "none",
|
||||
}
|
||||
|
||||
const configPath = "config.json"
|
||||
|
||||
func main() {
|
||||
cfg := loadConfig()
|
||||
|
||||
portName := flag.String("port", cfg.Port, "Serial port name")
|
||||
endpoint := flag.String("url", cfg.URL, "Target URL endpoint")
|
||||
baudRate := flag.Int("baud", cfg.BaudRate, "Baud rate")
|
||||
delim := flag.String("delim", cfg.Delimiter, "Line delimiter")
|
||||
flow := flag.String("flow", cfg.FlowControl, "Flow control: none, hardware, software")
|
||||
save := flag.Bool("save", false, "Save current parameters to config.json")
|
||||
flag.Parse()
|
||||
|
||||
cfg.Port = *portName
|
||||
cfg.URL = *endpoint
|
||||
cfg.BaudRate = *baudRate
|
||||
cfg.Delimiter = *delim
|
||||
cfg.FlowControl = *flow
|
||||
|
||||
if *save {
|
||||
saveConfig(cfg)
|
||||
fmt.Println("Settings saved to", configPath)
|
||||
}
|
||||
|
||||
serialConfig := &serial.Config{
|
||||
Name: cfg.Port,
|
||||
Baud: cfg.BaudRate,
|
||||
ReadTimeout: time.Millisecond * 500,
|
||||
}
|
||||
|
||||
// tarm/serial uses boolean flags for flow control if available in the version used
|
||||
// If your version doesn't support these fields, you may need to update the package
|
||||
// or manage the lines manually via the file descriptor.
|
||||
/* Note: tarm/serial usually requires specific fork or version
|
||||
for full RTS/CTS hardware flow control support.
|
||||
*/
|
||||
|
||||
port, err := serial.OpenPort(serialConfig)
|
||||
if err != nil {
|
||||
fmt.Printf("Error opening port %s: %v\n", cfg.Port, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer port.Close()
|
||||
|
||||
fmt.Printf("Listening on %s (Baud: %d, Flow: %s)...\n", cfg.Port, cfg.BaudRate, cfg.FlowControl)
|
||||
|
||||
scanner := bufio.NewScanner(port)
|
||||
|
||||
scanner.Split(func(data []byte, atEOF bool) (advance int, token []byte, err error) {
|
||||
if atEOF && len(data) == 0 {
|
||||
return 0, nil, nil
|
||||
}
|
||||
if i := bytes.Index(data, []byte(cfg.Delimiter)); i >= 0 {
|
||||
return i + len(cfg.Delimiter), data[0:i], nil
|
||||
}
|
||||
if atEOF {
|
||||
return len(data), data, nil
|
||||
}
|
||||
return 0, nil, nil
|
||||
})
|
||||
|
||||
for scanner.Scan() {
|
||||
rawContent := scanner.Text()
|
||||
content := strings.TrimFunc(rawContent, func(r rune) bool {
|
||||
return r < 32 || r > 126
|
||||
})
|
||||
|
||||
if content != "" {
|
||||
sendToEndpoint(cfg.URL, content)
|
||||
}
|
||||
}
|
||||
|
||||
if err := scanner.Err(); err != nil {
|
||||
fmt.Printf("Scanner error: %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
func loadConfig() Config {
|
||||
if _, err := os.Stat(configPath); os.IsNotExist(err) {
|
||||
saveConfig(defaultConfig)
|
||||
return defaultConfig
|
||||
}
|
||||
|
||||
file, err := os.Open(configPath)
|
||||
if err != nil {
|
||||
return defaultConfig
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
var cfg Config
|
||||
decoder := json.NewDecoder(file)
|
||||
if err := decoder.Decode(&cfg); err != nil {
|
||||
return defaultConfig
|
||||
}
|
||||
if cfg.Delimiter == "" {
|
||||
cfg.Delimiter = "\n"
|
||||
}
|
||||
if cfg.FlowControl == "" {
|
||||
cfg.FlowControl = "none"
|
||||
}
|
||||
return cfg
|
||||
}
|
||||
|
||||
func saveConfig(cfg Config) {
|
||||
file, err := os.Create(configPath)
|
||||
if err != nil {
|
||||
fmt.Printf("Failed to create/save config: %v\n", err)
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
encoder := json.NewEncoder(file)
|
||||
encoder.SetIndent("", " ")
|
||||
encoder.Encode(cfg)
|
||||
}
|
||||
|
||||
func sendToEndpoint(baseURL, content string) {
|
||||
client := &http.Client{
|
||||
Timeout: 5 * time.Second,
|
||||
}
|
||||
|
||||
fullURL := fmt.Sprintf("%s?content=%s", baseURL, url.QueryEscape(content))
|
||||
resp, err := client.Get(fullURL)
|
||||
if err != nil {
|
||||
fmt.Printf("Network Error: %v\n", err)
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
fmt.Printf("Error reading response: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("Data: [%s] | Status: %s\n", content, resp.Status)
|
||||
if len(body) > 0 {
|
||||
fmt.Printf("Response: %s\n", string(body))
|
||||
}
|
||||
fmt.Println(strings.Repeat("-", 30))
|
||||
}
|
||||
2
extensions/golang/ScannerHID/.gitignore
vendored
Normal file
2
extensions/golang/ScannerHID/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
config.json
|
||||
HIDScannerGO-*
|
||||
18
extensions/golang/ScannerHID/build.sh
Executable file
18
extensions/golang/ScannerHID/build.sh
Executable file
@@ -0,0 +1,18 @@
|
||||
#!/bin/bash
|
||||
|
||||
LINUX_BIN="HIDScannerGO-linux"
|
||||
WINDOWS_BIN="HIDScannerGO-windows.exe"
|
||||
|
||||
echo "Starting build process..."
|
||||
|
||||
# 1. Build for Linux (Host)
|
||||
echo "Building for Linux (64-bit)..."
|
||||
CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -o "$LINUX_BIN" .
|
||||
|
||||
# 2. Build for Windows (Needs MinGW)
|
||||
echo "Building for Windows (64-bit)..."
|
||||
# We must point to the mingw gcc and enable CGO
|
||||
CGO_ENABLED=1 GOOS=windows GOARCH=amd64 CC=x86_64-w64-mingw32-gcc \
|
||||
go build -ldflags="-H=windowsgui" -o "$WINDOWS_BIN" .
|
||||
|
||||
echo "Build complete."
|
||||
46
extensions/golang/ScannerHID/go.mod
Normal file
46
extensions/golang/ScannerHID/go.mod
Normal file
@@ -0,0 +1,46 @@
|
||||
module ScannerGO
|
||||
|
||||
go 1.24.0
|
||||
|
||||
require (
|
||||
fyne.io/fyne/v2 v2.7.3
|
||||
gioui.org v0.9.0
|
||||
github.com/google/gousb v1.1.3
|
||||
)
|
||||
|
||||
require (
|
||||
fyne.io/systray v1.12.0 // indirect
|
||||
gioui.org/shader v1.0.8 // indirect
|
||||
github.com/BurntSushi/toml v1.5.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/fredbi/uri v1.1.1 // indirect
|
||||
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||
github.com/fyne-io/gl-js v0.2.0 // indirect
|
||||
github.com/fyne-io/glfw-js v0.3.0 // indirect
|
||||
github.com/fyne-io/image v0.1.1 // indirect
|
||||
github.com/fyne-io/oksvg v0.2.0 // indirect
|
||||
github.com/go-gl/gl v0.0.0-20231021071112-07e5d0ea2e71 // indirect
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20240506104042-037f3cc74f2a // indirect
|
||||
github.com/go-text/render v0.2.0 // indirect
|
||||
github.com/go-text/typesetting v0.3.3 // indirect
|
||||
github.com/godbus/dbus/v5 v5.1.0 // indirect
|
||||
github.com/hack-pad/go-indexeddb v0.3.2 // indirect
|
||||
github.com/hack-pad/safejs v0.1.0 // indirect
|
||||
github.com/jeandeaual/go-locale v0.0.0-20250612000132-0ef82f21eade // indirect
|
||||
github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25 // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect
|
||||
github.com/nicksnyder/go-i18n/v2 v2.5.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/rymdport/portal v0.4.2 // indirect
|
||||
github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c // indirect
|
||||
github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef // indirect
|
||||
github.com/stretchr/testify v1.11.1 // indirect
|
||||
github.com/yuin/goldmark v1.7.8 // indirect
|
||||
golang.org/x/exp/shiny v0.0.0-20250408133849-7e4ce0ab07d0 // indirect
|
||||
golang.org/x/image v0.26.0 // indirect
|
||||
golang.org/x/net v0.35.0 // indirect
|
||||
golang.org/x/sys v0.41.0 // indirect
|
||||
golang.org/x/text v0.24.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
93
extensions/golang/ScannerHID/go.sum
Normal file
93
extensions/golang/ScannerHID/go.sum
Normal file
@@ -0,0 +1,93 @@
|
||||
eliasnaur.com/font v0.0.0-20230308162249-dd43949cb42d h1:ARo7NCVvN2NdhLlJE9xAbKweuI9L6UgfTbYb0YwPacY=
|
||||
eliasnaur.com/font v0.0.0-20230308162249-dd43949cb42d/go.mod h1:OYVuxibdk9OSLX8vAqydtRPP87PyTFcT9uH3MlEGBQA=
|
||||
fyne.io/fyne/v2 v2.7.3 h1:xBT/iYbdnNHONWO38fZMBrVBiJG8rV/Jypmy4tVfRWE=
|
||||
fyne.io/fyne/v2 v2.7.3/go.mod h1:gu+dlIcZWSzKZmnrY8Fbnj2Hirabv2ek+AKsfQ2bBlw=
|
||||
fyne.io/systray v1.12.0 h1:CA1Kk0e2zwFlxtc02L3QFSiIbxJ/P0n582YrZHT7aTM=
|
||||
fyne.io/systray v1.12.0/go.mod h1:RVwqP9nYMo7h5zViCBHri2FgjXF7H2cub7MAq4NSoLs=
|
||||
gioui.org v0.9.0 h1:4u7XZwnb5kzQW91Nz/vR0wKD6LdW9CaVF96r3rfy4kc=
|
||||
gioui.org v0.9.0/go.mod h1:CjNig0wAhLt9WZxOPAusgFD8x8IRvqt26LdDBa3Jvao=
|
||||
gioui.org/cpu v0.0.0-20210808092351-bfe733dd3334/go.mod h1:A8M0Cn5o+vY5LTMlnRoK3O5kG+rH0kWfJjeKd9QpBmQ=
|
||||
gioui.org/shader v1.0.8 h1:6ks0o/A+b0ne7RzEqRZK5f4Gboz2CfG+mVliciy6+qA=
|
||||
gioui.org/shader v1.0.8/go.mod h1:mWdiME581d/kV7/iEhLmUgUK5iZ09XR5XpduXzbePVM=
|
||||
github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
|
||||
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/felixge/fgprof v0.9.3 h1:VvyZxILNuCiUCSXtPtYmmtGvb65nqXh2QFWc0Wpf2/g=
|
||||
github.com/felixge/fgprof v0.9.3/go.mod h1:RdbpDgzqYVh/T9fPELJyV7EYJuHB55UTEULNun8eiPw=
|
||||
github.com/fredbi/uri v1.1.1 h1:xZHJC08GZNIUhbP5ImTHnt5Ya0T8FI2VAwI/37kh2Ko=
|
||||
github.com/fredbi/uri v1.1.1/go.mod h1:4+DZQ5zBjEwQCDmXW5JdIjz0PUA+yJbvtBv+u+adr5o=
|
||||
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/fyne-io/gl-js v0.2.0 h1:+EXMLVEa18EfkXBVKhifYB6OGs3HwKO3lUElA0LlAjs=
|
||||
github.com/fyne-io/gl-js v0.2.0/go.mod h1:ZcepK8vmOYLu96JoxbCKJy2ybr+g1pTnaBDdl7c3ajI=
|
||||
github.com/fyne-io/glfw-js v0.3.0 h1:d8k2+Y7l+zy2pc7wlGRyPfTgZoqDf3AI4G+2zOWhWUk=
|
||||
github.com/fyne-io/glfw-js v0.3.0/go.mod h1:Ri6te7rdZtBgBpxLW19uBpp3Dl6K9K/bRaYdJ22G8Jk=
|
||||
github.com/fyne-io/image v0.1.1 h1:WH0z4H7qfvNUw5l4p3bC1q70sa5+YWVt6HCj7y4VNyA=
|
||||
github.com/fyne-io/image v0.1.1/go.mod h1:xrfYBh6yspc+KjkgdZU/ifUC9sPA5Iv7WYUBzQKK7JM=
|
||||
github.com/fyne-io/oksvg v0.2.0 h1:mxcGU2dx6nwjJsSA9PCYZDuoAcsZ/OuJlvg/Q9Njfo8=
|
||||
github.com/fyne-io/oksvg v0.2.0/go.mod h1:dJ9oEkPiWhnTFNCmRgEze+YNprJF7YRbpjgpWS4kzoI=
|
||||
github.com/go-gl/gl v0.0.0-20231021071112-07e5d0ea2e71 h1:5BVwOaUSBTlVZowGO6VZGw2H/zl9nrd3eCZfYV+NfQA=
|
||||
github.com/go-gl/gl v0.0.0-20231021071112-07e5d0ea2e71/go.mod h1:9YTyiznxEY1fVinfM7RvRcjRHbw2xLBJ3AAGIT0I4Nw=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20240506104042-037f3cc74f2a h1:vxnBhFDDT+xzxf1jTJKMKZw3H0swfWk9RpWbBbDK5+0=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20240506104042-037f3cc74f2a/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-text/render v0.2.0 h1:LBYoTmp5jYiJ4NPqDc2pz17MLmA3wHw1dZSVGcOdeAc=
|
||||
github.com/go-text/render v0.2.0/go.mod h1:CkiqfukRGKJA5vZZISkjSYrcdtgKQWRa2HIzvwNN5SU=
|
||||
github.com/go-text/typesetting v0.3.3 h1:ihGNJU9KzdK2QRDy1Bm7FT5RFQoYb+3n3EIhI/4eaQc=
|
||||
github.com/go-text/typesetting v0.3.3/go.mod h1:vIRUT25mLQaSh4C8H/lIsKppQz/Gdb8Pu/tNwpi52ts=
|
||||
github.com/go-text/typesetting-utils v0.0.0-20250618110550-c820a94c77b8 h1:4KCscI9qYWMGTuz6BpJtbUSRzcBrUSSE0ENMJbNSrFs=
|
||||
github.com/go-text/typesetting-utils v0.0.0-20250618110550-c820a94c77b8/go.mod h1:3/62I4La/HBRX9TcTpBj4eipLiwzf+vhI+7whTc9V7o=
|
||||
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
|
||||
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/google/gousb v1.1.3 h1:xt6M5TDsGSZ+rlomz5Si5Hmd/Fvbmo2YCJHN+yGaK4o=
|
||||
github.com/google/gousb v1.1.3/go.mod h1:GGWUkK0gAXDzxhwrzetW592aOmkkqSGcj5KLEgmCVUg=
|
||||
github.com/google/pprof v0.0.0-20211214055906-6f57359322fd h1:1FjCyPC+syAzJ5/2S8fqdZK1R22vvA0J7JZKcuOIQ7Y=
|
||||
github.com/google/pprof v0.0.0-20211214055906-6f57359322fd/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg=
|
||||
github.com/hack-pad/go-indexeddb v0.3.2 h1:DTqeJJYc1usa45Q5r52t01KhvlSN02+Oq+tQbSBI91A=
|
||||
github.com/hack-pad/go-indexeddb v0.3.2/go.mod h1:QvfTevpDVlkfomY498LhstjwbPW6QC4VC/lxYb0Kom0=
|
||||
github.com/hack-pad/safejs v0.1.0 h1:qPS6vjreAqh2amUqj4WNG1zIw7qlRQJ9K10eDKMCnE8=
|
||||
github.com/hack-pad/safejs v0.1.0/go.mod h1:HdS+bKF1NrE72VoXZeWzxFOVQVUSqZJAG0xNCnb+Tio=
|
||||
github.com/jeandeaual/go-locale v0.0.0-20250612000132-0ef82f21eade h1:FmusiCI1wHw+XQbvL9M+1r/C3SPqKrmBaIOYwVfQoDE=
|
||||
github.com/jeandeaual/go-locale v0.0.0-20250612000132-0ef82f21eade/go.mod h1:ZDXo8KHryOWSIqnsb/CiDq7hQUYryCgdVnxbj8tDG7o=
|
||||
github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25 h1:YLvr1eE6cdCqjOe972w/cYF+FjW34v27+9Vo5106B4M=
|
||||
github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25/go.mod h1:kLgvv7o6UM+0QSf0QjAse3wReFDsb9qbZJdfexWlrQw=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
|
||||
github.com/nicksnyder/go-i18n/v2 v2.5.1 h1:IxtPxYsR9Gp60cGXjfuR/llTqV8aYMsC472zD0D1vHk=
|
||||
github.com/nicksnyder/go-i18n/v2 v2.5.1/go.mod h1:DrhgsSDZxoAfvVrBVLXoxZn/pN5TXqaDbq7ju94viiQ=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/pkg/profile v1.7.0 h1:hnbDkaNWPCLMO9wGLdBFTIZvzDrDfBM2072E1S9gJkA=
|
||||
github.com/pkg/profile v1.7.0/go.mod h1:8Uer0jas47ZQMJ7VD+OHknK4YDY07LPUC6dEvqDjvNo=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rymdport/portal v0.4.2 h1:7jKRSemwlTyVHHrTGgQg7gmNPJs88xkbKcIL3NlcmSU=
|
||||
github.com/rymdport/portal v0.4.2/go.mod h1:kFF4jslnJ8pD5uCi17brj/ODlfIidOxlgUDTO5ncnC4=
|
||||
github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c h1:km8GpoQut05eY3GiYWEedbTT0qnSxrCjsVbb7yKY1KE=
|
||||
github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c/go.mod h1:cNQ3dwVJtS5Hmnjxy6AgTPd0Inb3pW05ftPSX7NZO7Q=
|
||||
github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef h1:Ch6Q+AZUxDBCVqdkI8FSpFyZDtCVBc2VmejdNrm5rRQ=
|
||||
github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef/go.mod h1:nXTWP6+gD5+LUJ8krVhhoeHjvHTutPxMYl5SvkcnJNE=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/yuin/goldmark v1.7.8 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic=
|
||||
github.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
|
||||
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 h1:R84qjqJb5nVJMxqWYb3np9L5ZsaDtB+a39EqjV0JSUM=
|
||||
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:S9Xr4PYopiDyqSyp5NjCrhFrqg6A5zA2E/iPHPhqnS8=
|
||||
golang.org/x/exp/shiny v0.0.0-20250408133849-7e4ce0ab07d0 h1:tMSqXTK+AQdW3LpCbfatHSRPHeW6+2WuxaVQuHftn80=
|
||||
golang.org/x/exp/shiny v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:ygj7T6vSGhhm/9yTpOQQNvuAUFziTH7RUiH74EoE2C8=
|
||||
golang.org/x/image v0.26.0 h1:4XjIFEZWQmCZi6Wv8BoxsDhRU3RVnLX04dToTDAEPlY=
|
||||
golang.org/x/image v0.26.0/go.mod h1:lcxbMFAovzpnJxzXS3nyL83K27tmqtKzIJpctK8YO5c=
|
||||
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
|
||||
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
|
||||
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
|
||||
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
|
||||
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
246
extensions/golang/ScannerHID/main.go
Normal file
246
extensions/golang/ScannerHID/main.go
Normal file
@@ -0,0 +1,246 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"image/color"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/app"
|
||||
"fyne.io/fyne/v2/canvas"
|
||||
"fyne.io/fyne/v2/container"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
"github.com/google/gousb"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
TargetURL string `json:"target_url"`
|
||||
VendorID uint16 `json:"vendor_id"`
|
||||
ProductID uint16 `json:"product_id"`
|
||||
}
|
||||
|
||||
var hidMap = map[byte]string{
|
||||
4: "a", 5: "b", 6: "c", 7: "d", 8: "e", 9: "f", 10: "g", 11: "h", 12: "i",
|
||||
13: "j", 14: "k", 15: "l", 16: "m", 17: "n", 18: "o", 19: "p", 20: "q",
|
||||
21: "r", 22: "s", 23: "t", 24: "u", 25: "v", 26: "w", 27: "x", 28: "y", 29: "z",
|
||||
30: "1", 31: "2", 32: "3", 33: "4", 34: "5", 35: "6", 36: "7", 37: "8", 38: "9", 39: "0",
|
||||
44: " ", 45: "-", 46: "=", 55: ".", 56: "/",
|
||||
}
|
||||
|
||||
type BridgeApp struct {
|
||||
urlEntry *widget.Entry
|
||||
status *canvas.Text
|
||||
logList *widget.List
|
||||
logs []string
|
||||
window fyne.Window
|
||||
config Config
|
||||
isCLI bool
|
||||
}
|
||||
|
||||
func (b *BridgeApp) saveConfig() {
|
||||
b.config.TargetURL = b.urlEntry.Text
|
||||
data, _ := json.MarshalIndent(b.config, "", " ")
|
||||
_ = os.WriteFile("config.json", data, 0644)
|
||||
}
|
||||
|
||||
func loadConfig() Config {
|
||||
conf := Config{
|
||||
TargetURL: "https://scanner.sekidesu.xyz/scan",
|
||||
VendorID: 0xFFFF,
|
||||
ProductID: 0x0035,
|
||||
}
|
||||
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)
|
||||
}
|
||||
return conf
|
||||
}
|
||||
|
||||
func main() {
|
||||
cliMode := flag.Bool("cli", false, "Run in CLI mode without GUI")
|
||||
flag.Parse()
|
||||
|
||||
conf := loadConfig()
|
||||
bridge := &BridgeApp{
|
||||
config: conf,
|
||||
isCLI: *cliMode,
|
||||
}
|
||||
|
||||
if *cliMode {
|
||||
fmt.Println("Running in CLI mode...")
|
||||
bridge.usbListenLoop()
|
||||
return
|
||||
}
|
||||
|
||||
a := app.New()
|
||||
w := a.NewWindow("POS Hardware Bridge (Go)")
|
||||
bridge.window = w
|
||||
bridge.urlEntry = widget.NewEntry()
|
||||
bridge.urlEntry.SetText(conf.TargetURL)
|
||||
bridge.status = canvas.NewText("Status: Booting...", color.Black)
|
||||
bridge.status.TextSize = 14
|
||||
|
||||
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])
|
||||
},
|
||||
)
|
||||
|
||||
content := container.NewBorder(
|
||||
container.NewVBox(
|
||||
widget.NewLabel("Target POS Endpoint:"),
|
||||
bridge.urlEntry,
|
||||
bridge.status,
|
||||
widget.NewLabel("Activity Log:"),
|
||||
),
|
||||
nil, nil, nil,
|
||||
bridge.logList,
|
||||
)
|
||||
|
||||
w.SetContent(content)
|
||||
w.Resize(fyne.NewSize(500, 400))
|
||||
|
||||
w.SetOnClosed(func() {
|
||||
if !bridge.isCLI {
|
||||
bridge.config.TargetURL = bridge.urlEntry.Text
|
||||
data, err := json.MarshalIndent(bridge.config, "", " ")
|
||||
if err == nil {
|
||||
_ = os.WriteFile("config.json", data, 0644)
|
||||
fmt.Println("Configuration saved.")
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
go bridge.usbListenLoop()
|
||||
w.ShowAndRun()
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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) {
|
||||
if b.isCLI {
|
||||
fmt.Printf("STATUS: %s\n", msg)
|
||||
return
|
||||
}
|
||||
fyne.DoAndWait(func() {
|
||||
b.status.Text = msg
|
||||
b.status.Color = col
|
||||
b.status.Refresh()
|
||||
})
|
||||
}
|
||||
|
||||
func (b *BridgeApp) sendToPos(barcode string) {
|
||||
url := b.config.TargetURL
|
||||
if !b.isCLI {
|
||||
url = b.urlEntry.Text
|
||||
}
|
||||
|
||||
b.addLog(fmt.Sprintf("Captured: %s. Sending to %s", barcode, url))
|
||||
client := http.Client{Timeout: 3 * time.Second}
|
||||
resp, err := client.Get(url + "?content=" + barcode)
|
||||
if err != nil {
|
||||
b.addLog("HTTP Error: Backend unreachable")
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
b.addLog(fmt.Sprintf("Success: POS returned %d", resp.StatusCode))
|
||||
}
|
||||
|
||||
func (b *BridgeApp) usbListenLoop() {
|
||||
ctx := gousb.NewContext()
|
||||
defer ctx.Close()
|
||||
|
||||
for {
|
||||
dev, err := ctx.OpenDeviceWithVIDPID(gousb.ID(b.config.VendorID), gousb.ID(b.config.ProductID))
|
||||
|
||||
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})
|
||||
|
||||
intf, done, err := dev.DefaultInterface()
|
||||
if err != nil {
|
||||
b.addLog("Error claiming interface")
|
||||
dev.Close()
|
||||
time.Sleep(2 * time.Second)
|
||||
continue
|
||||
}
|
||||
|
||||
var inEp *gousb.InEndpoint
|
||||
for _, epDesc := range intf.Setting.Endpoints {
|
||||
if epDesc.Direction == gousb.EndpointDirectionIn {
|
||||
inEp, _ = intf.InEndpoint(epDesc.Number)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if inEp == nil {
|
||||
b.addLog("No IN endpoint found")
|
||||
done()
|
||||
dev.Close()
|
||||
continue
|
||||
}
|
||||
|
||||
currentBarcode := ""
|
||||
buf := make([]byte, inEp.Desc.MaxPacketSize)
|
||||
|
||||
for {
|
||||
n, err := inEp.Read(buf)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
if n < 3 || buf[2] == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
modifier := buf[0]
|
||||
keycode := buf[2]
|
||||
isShift := (modifier == 2 || modifier == 32)
|
||||
|
||||
if keycode == 40 {
|
||||
if currentBarcode != "" {
|
||||
go b.sendToPos(currentBarcode)
|
||||
currentBarcode = ""
|
||||
}
|
||||
} else if val, ok := hidMap[keycode]; ok {
|
||||
if isShift && len(val) == 1 && val[0] >= 'a' && val[0] <= 'z' {
|
||||
val = string(val[0] - 32)
|
||||
}
|
||||
currentBarcode += val
|
||||
}
|
||||
}
|
||||
|
||||
done()
|
||||
dev.Close()
|
||||
b.addLog("Hardware connection lost. Reconnecting...")
|
||||
}
|
||||
}
|
||||
5
extensions/platformio/IOT_Scale/.gitignore
vendored
Normal file
5
extensions/platformio/IOT_Scale/.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
.pio
|
||||
.vscode/.browse.c_cpp.db*
|
||||
.vscode/c_cpp_properties.json
|
||||
.vscode/launch.json
|
||||
.vscode/ipch
|
||||
10
extensions/platformio/IOT_Scale/.vscode/extensions.json
vendored
Normal file
10
extensions/platformio/IOT_Scale/.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
// See http://go.microsoft.com/fwlink/?LinkId=827846
|
||||
// for the documentation about the extensions.json format
|
||||
"recommendations": [
|
||||
"platformio.platformio-ide"
|
||||
],
|
||||
"unwantedRecommendations": [
|
||||
"ms-vscode.cpptools-extension-pack"
|
||||
]
|
||||
}
|
||||
37
extensions/platformio/IOT_Scale/include/README
Normal file
37
extensions/platformio/IOT_Scale/include/README
Normal file
@@ -0,0 +1,37 @@
|
||||
|
||||
This directory is intended for project header files.
|
||||
|
||||
A header file is a file containing C declarations and macro definitions
|
||||
to be shared between several project source files. You request the use of a
|
||||
header file in your project source file (C, C++, etc) located in `src` folder
|
||||
by including it, with the C preprocessing directive `#include'.
|
||||
|
||||
```src/main.c
|
||||
|
||||
#include "header.h"
|
||||
|
||||
int main (void)
|
||||
{
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
Including a header file produces the same results as copying the header file
|
||||
into each source file that needs it. Such copying would be time-consuming
|
||||
and error-prone. With a header file, the related declarations appear
|
||||
in only one place. If they need to be changed, they can be changed in one
|
||||
place, and programs that include the header file will automatically use the
|
||||
new version when next recompiled. The header file eliminates the labor of
|
||||
finding and changing all the copies as well as the risk that a failure to
|
||||
find one copy will result in inconsistencies within a program.
|
||||
|
||||
In C, the convention is to give header files names that end with `.h'.
|
||||
|
||||
Read more about using header files in official GCC documentation:
|
||||
|
||||
* Include Syntax
|
||||
* Include Operation
|
||||
* Once-Only Headers
|
||||
* Computed Includes
|
||||
|
||||
https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html
|
||||
46
extensions/platformio/IOT_Scale/lib/README
Normal file
46
extensions/platformio/IOT_Scale/lib/README
Normal file
@@ -0,0 +1,46 @@
|
||||
|
||||
This directory is intended for project specific (private) libraries.
|
||||
PlatformIO will compile them to static libraries and link into the executable file.
|
||||
|
||||
The source code of each library should be placed in a separate directory
|
||||
("lib/your_library_name/[Code]").
|
||||
|
||||
For example, see the structure of the following example libraries `Foo` and `Bar`:
|
||||
|
||||
|--lib
|
||||
| |
|
||||
| |--Bar
|
||||
| | |--docs
|
||||
| | |--examples
|
||||
| | |--src
|
||||
| | |- Bar.c
|
||||
| | |- Bar.h
|
||||
| | |- library.json (optional. for custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html
|
||||
| |
|
||||
| |--Foo
|
||||
| | |- Foo.c
|
||||
| | |- Foo.h
|
||||
| |
|
||||
| |- README --> THIS FILE
|
||||
|
|
||||
|- platformio.ini
|
||||
|--src
|
||||
|- main.c
|
||||
|
||||
Example contents of `src/main.c` using Foo and Bar:
|
||||
```
|
||||
#include <Foo.h>
|
||||
#include <Bar.h>
|
||||
|
||||
int main (void)
|
||||
{
|
||||
...
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
The PlatformIO Library Dependency Finder will find automatically dependent
|
||||
libraries by scanning project source files.
|
||||
|
||||
More information about PlatformIO Library Dependency Finder
|
||||
- https://docs.platformio.org/page/librarymanager/ldf.html
|
||||
17
extensions/platformio/IOT_Scale/platformio.ini
Normal file
17
extensions/platformio/IOT_Scale/platformio.ini
Normal file
@@ -0,0 +1,17 @@
|
||||
; PlatformIO Project Configuration File
|
||||
;
|
||||
; Build options: build flags, source filter
|
||||
; Upload options: custom upload port, speed and extra flags
|
||||
; Library options: dependencies, extra library storages
|
||||
; Advanced options: extra scripting
|
||||
;
|
||||
; Please visit documentation for the other options and examples
|
||||
; https://docs.platformio.org/page/projectconf.html
|
||||
|
||||
[env:waveshare_rp2040_zero]
|
||||
platform = https://github.com/maxgerhardt/platform-raspberrypi.git
|
||||
board = waveshare_rp2040_zero
|
||||
framework = arduino
|
||||
monitor_speed = 115200
|
||||
lib_archive = no
|
||||
lib_deps = bogde/HX711@^0.7.5
|
||||
32
extensions/platformio/IOT_Scale/src/main.cpp
Normal file
32
extensions/platformio/IOT_Scale/src/main.cpp
Normal file
@@ -0,0 +1,32 @@
|
||||
#include <Arduino.h>
|
||||
#include "HX711.h"
|
||||
|
||||
const int LOADCELL_SCK_PIN = 3;
|
||||
const int LOADCELL_DOUT_PIN = 2;
|
||||
|
||||
const long LOADCELL_OFFSET = 0;
|
||||
const long LOADCELL_DIVIDER = 0;
|
||||
|
||||
HX711 scale;
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
while(!Serial);
|
||||
|
||||
Serial.println("Remove all weight from the scale.");
|
||||
delay(2000);
|
||||
scale.set_scale();
|
||||
scale.tare();
|
||||
Serial.println("Tare complete. Place a known weight on the scale.");
|
||||
}
|
||||
|
||||
void loop() {
|
||||
if (scale.is_ready()) {
|
||||
long reading = scale.get_units(10);
|
||||
Serial.print("Raw Value: ");
|
||||
Serial.println(reading);
|
||||
} else {
|
||||
Serial.println("HX711 not found.");
|
||||
}
|
||||
delay(1000);
|
||||
}
|
||||
11
extensions/platformio/IOT_Scale/test/README
Normal file
11
extensions/platformio/IOT_Scale/test/README
Normal file
@@ -0,0 +1,11 @@
|
||||
|
||||
This directory is intended for PlatformIO Test Runner and project tests.
|
||||
|
||||
Unit Testing is a software testing method by which individual units of
|
||||
source code, sets of one or more MCU program modules together with associated
|
||||
control data, usage procedures, and operating procedures, are tested to
|
||||
determine whether they are fit for use. Unit testing finds problems early
|
||||
in the development cycle.
|
||||
|
||||
More information about PlatformIO Unit Testing:
|
||||
- https://docs.platformio.org/en/latest/advanced/unit-testing/index.html
|
||||
5
extensions/platformio/display_driver/.gitignore
vendored
Normal file
5
extensions/platformio/display_driver/.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
.pio
|
||||
.vscode/.browse.c_cpp.db*
|
||||
.vscode/c_cpp_properties.json
|
||||
.vscode/launch.json
|
||||
.vscode/ipch
|
||||
10
extensions/platformio/display_driver/.vscode/extensions.json
vendored
Normal file
10
extensions/platformio/display_driver/.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
// See http://go.microsoft.com/fwlink/?LinkId=827846
|
||||
// for the documentation about the extensions.json format
|
||||
"recommendations": [
|
||||
"platformio.platformio-ide"
|
||||
],
|
||||
"unwantedRecommendations": [
|
||||
"ms-vscode.cpptools-extension-pack"
|
||||
]
|
||||
}
|
||||
37
extensions/platformio/display_driver/include/README
Normal file
37
extensions/platformio/display_driver/include/README
Normal file
@@ -0,0 +1,37 @@
|
||||
|
||||
This directory is intended for project header files.
|
||||
|
||||
A header file is a file containing C declarations and macro definitions
|
||||
to be shared between several project source files. You request the use of a
|
||||
header file in your project source file (C, C++, etc) located in `src` folder
|
||||
by including it, with the C preprocessing directive `#include'.
|
||||
|
||||
```src/main.c
|
||||
|
||||
#include "header.h"
|
||||
|
||||
int main (void)
|
||||
{
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
Including a header file produces the same results as copying the header file
|
||||
into each source file that needs it. Such copying would be time-consuming
|
||||
and error-prone. With a header file, the related declarations appear
|
||||
in only one place. If they need to be changed, they can be changed in one
|
||||
place, and programs that include the header file will automatically use the
|
||||
new version when next recompiled. The header file eliminates the labor of
|
||||
finding and changing all the copies as well as the risk that a failure to
|
||||
find one copy will result in inconsistencies within a program.
|
||||
|
||||
In C, the convention is to give header files names that end with `.h'.
|
||||
|
||||
Read more about using header files in official GCC documentation:
|
||||
|
||||
* Include Syntax
|
||||
* Include Operation
|
||||
* Once-Only Headers
|
||||
* Computed Includes
|
||||
|
||||
https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html
|
||||
46
extensions/platformio/display_driver/lib/README
Normal file
46
extensions/platformio/display_driver/lib/README
Normal file
@@ -0,0 +1,46 @@
|
||||
|
||||
This directory is intended for project specific (private) libraries.
|
||||
PlatformIO will compile them to static libraries and link into the executable file.
|
||||
|
||||
The source code of each library should be placed in a separate directory
|
||||
("lib/your_library_name/[Code]").
|
||||
|
||||
For example, see the structure of the following example libraries `Foo` and `Bar`:
|
||||
|
||||
|--lib
|
||||
| |
|
||||
| |--Bar
|
||||
| | |--docs
|
||||
| | |--examples
|
||||
| | |--src
|
||||
| | |- Bar.c
|
||||
| | |- Bar.h
|
||||
| | |- library.json (optional. for custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html
|
||||
| |
|
||||
| |--Foo
|
||||
| | |- Foo.c
|
||||
| | |- Foo.h
|
||||
| |
|
||||
| |- README --> THIS FILE
|
||||
|
|
||||
|- platformio.ini
|
||||
|--src
|
||||
|- main.c
|
||||
|
||||
Example contents of `src/main.c` using Foo and Bar:
|
||||
```
|
||||
#include <Foo.h>
|
||||
#include <Bar.h>
|
||||
|
||||
int main (void)
|
||||
{
|
||||
...
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
The PlatformIO Library Dependency Finder will find automatically dependent
|
||||
libraries by scanning project source files.
|
||||
|
||||
More information about PlatformIO Library Dependency Finder
|
||||
- https://docs.platformio.org/page/librarymanager/ldf.html
|
||||
17
extensions/platformio/display_driver/platformio.ini
Normal file
17
extensions/platformio/display_driver/platformio.ini
Normal file
@@ -0,0 +1,17 @@
|
||||
; PlatformIO Project Configuration File
|
||||
;
|
||||
; Build options: build flags, source filter
|
||||
; Upload options: custom upload port, speed and extra flags
|
||||
; Library options: dependencies, extra library storages
|
||||
; Advanced options: extra scripting
|
||||
;
|
||||
; Please visit documentation for the other options and examples
|
||||
; https://docs.platformio.org/page/projectconf.html
|
||||
|
||||
[env:waveshare_rp2040_zero]
|
||||
platform = https://github.com/maxgerhardt/platform-raspberrypi.git
|
||||
board = waveshare_rp2040_zero
|
||||
framework = arduino
|
||||
monitor_speed = 115200
|
||||
lib_archive = no
|
||||
lib_deps = bogde/HX711@^0.7.5
|
||||
50
extensions/platformio/display_driver/src/TM1621_Config.h
Normal file
50
extensions/platformio/display_driver/src/TM1621_Config.h
Normal file
@@ -0,0 +1,50 @@
|
||||
#ifndef TM1621_CONFIG_H
|
||||
#define TM1621_CONFIG_H
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
const uint8_t digitMap[] = {
|
||||
0b11111100, // 0
|
||||
0b00000110, // 1
|
||||
0b01011011, // 2
|
||||
0b01001111, // 3
|
||||
0b01100110, // 4
|
||||
0b01101101, // 5
|
||||
0b01111101, // 6
|
||||
0b00000111, // 7
|
||||
0b01111111, // 8
|
||||
0b01101111 // 9
|
||||
};
|
||||
|
||||
const int arrows[] = {283,363,183,203}; //tare,?,?,Zero
|
||||
const int battery[] = {63,83,103}; //LOW / MED / FULL
|
||||
|
||||
// Following your pattern: {A, B, C, D, E, F, G}
|
||||
const int row1_d1[] = {300, 301, 302, 313, 312, 310, 311}; // Addresses 30 & 31
|
||||
const int row1_d2[] = {280, 281, 282, 293, 292, 290, 291}; // Addresses 28 & 29
|
||||
const int row1_d3[] = {260, 261, 262, 273, 272, 270, 271}; // Addresses 26 & 27
|
||||
const int row1_d4[] = {240, 241, 242, 253, 252, 250, 251}; // Addresses 24 & 25
|
||||
const int row1_d5[] = {220, 221, 222, 233, 232, 230, 231}; // Addresses 22 & 23
|
||||
const int row1_decimal[] = {263,232,223}; //XX.X.X.X
|
||||
|
||||
const int row2_d1[] = {200, 201, 202, 213, 212, 210, 211}; // Addresses 20 & 21
|
||||
const int row2_d2[] = {180, 181, 182, 193, 192, 190, 191}; // Addresses 18 & 19
|
||||
const int row2_d3[] = {160, 161, 162, 173, 172, 170, 171}; // Addresses 16 & 17
|
||||
const int row2_d4[] = {140, 141, 142, 153, 152, 150, 151}; // Addresses 14 & 15
|
||||
const int row2_d5[] = {120, 121, 122, 133, 132, 130, 131}; // Addresses 12 & 13
|
||||
const int row2_decimal[] = {163,143,123}; //XX.X.X.X
|
||||
|
||||
const int row3_d1[] = {100, 101, 102, 113, 112, 110, 111}; // Addresses 10 & 11
|
||||
const int row3_d2[] = {80, 81, 82, 93, 92, 90, 91}; // Addresses 8 & 9
|
||||
const int row3_d3[] = {60, 61, 62, 73, 72, 70, 71}; // Addresses 6 & 7
|
||||
const int row3_d4[] = {40, 41, 42, 53, 52, 50, 51}; // Addresses 4 & 5
|
||||
const int row3_d5[] = {20, 21, 22, 33, 32, 30, 31}; // Addresses 2 & 3
|
||||
const int row3_d6[] = {0, 1, 2, 13, 12, 10, 11}; // Addresses 0 & 1
|
||||
const int row3_decimal[] = {43,23,3}; //XX.X.X.X
|
||||
|
||||
const int* digitsRow1[] = {row1_d1, row1_d2, row1_d3, row1_d4, row1_d5};
|
||||
const int* digitsRow2[] = {row2_d1, row2_d2, row2_d3, row2_d4, row2_d5};
|
||||
const int* digitsRow3[] = {row3_d1, row3_d2, row3_d3, row3_d4, row3_d5, row3_d6};
|
||||
|
||||
const int* decimals[] = {row1_decimal, row2_decimal, row3_decimal};
|
||||
#endif
|
||||
180
extensions/platformio/display_driver/src/main.cpp
Normal file
180
extensions/platformio/display_driver/src/main.cpp
Normal file
@@ -0,0 +1,180 @@
|
||||
#include <Arduino.h>
|
||||
#include "TM1621_Config.h"
|
||||
|
||||
#define LCD_DATA 29
|
||||
#define LCD_WR 28
|
||||
#define LCD_CS 27
|
||||
|
||||
// Tracking variables
|
||||
int currentAddr = 0;
|
||||
int currentBit = 0;
|
||||
|
||||
void writeBits(uint32_t data, uint8_t count) {
|
||||
for (int8_t i = count - 1; i >= 0; i--) {
|
||||
digitalWrite(LCD_WR, LOW);
|
||||
digitalWrite(LCD_DATA, (data >> i) & 0x01);
|
||||
digitalWrite(LCD_WR, HIGH);
|
||||
}
|
||||
}
|
||||
|
||||
void sendCmd(uint8_t cmd) {
|
||||
digitalWrite(LCD_CS, LOW);
|
||||
writeBits(0x04, 3); // Binary 100 [cite: 432]
|
||||
writeBits(cmd, 8); // Command [cite: 413, 534]
|
||||
writeBits(0, 1); // X bit [cite: 416]
|
||||
digitalWrite(LCD_CS, HIGH);
|
||||
}
|
||||
|
||||
void writeAddr(uint8_t addr, uint8_t data) {
|
||||
digitalWrite(LCD_CS, LOW);
|
||||
uint16_t header = (0x05 << 6) | (addr & 0x3F); // Mode 101 [cite: 475]
|
||||
writeBits(header, 9);
|
||||
writeBits(data & 0x0F, 4); // 4 bits of data [cite: 475]
|
||||
digitalWrite(LCD_CS, HIGH);
|
||||
}
|
||||
|
||||
void updateDisplay() {
|
||||
// Clear all segments first
|
||||
for (int i = 0; i < 32; i++) {
|
||||
writeAddr(i, 0x00);
|
||||
}
|
||||
// Light up only the current bit
|
||||
writeAddr(currentAddr, (1 << currentBit));
|
||||
|
||||
Serial.print(">>> CURRENT - Address: ");
|
||||
Serial.print(currentAddr);
|
||||
Serial.print(" | Bit (COM): ");
|
||||
Serial.println(currentBit);
|
||||
Serial.println("Enter 'n' for Next, 'p' for Prev:");
|
||||
}
|
||||
|
||||
void setup() {
|
||||
pinMode(LCD_DATA, OUTPUT);
|
||||
pinMode(LCD_WR, OUTPUT);
|
||||
pinMode(LCD_CS, OUTPUT);
|
||||
digitalWrite(LCD_CS, HIGH); // Initialize serial interface [cite: 443]
|
||||
|
||||
Serial.begin(9600);
|
||||
// while (!Serial);
|
||||
|
||||
Serial.println("--- TM1621 Interactive Mapper ---");
|
||||
|
||||
sendCmd(0x01); // SYS EN [cite: 534]
|
||||
sendCmd(0x29); // BIAS 1/3, 4 COM [cite: 420, 544]
|
||||
sendCmd(0x03); // LCD ON [cite: 534]
|
||||
|
||||
updateDisplay();
|
||||
}
|
||||
|
||||
uint8_t shadowRAM[32] = {0};
|
||||
|
||||
void writeMappedSegment(int aab, bool state) {
|
||||
int addr = aab / 10;
|
||||
int bit = aab % 10;
|
||||
|
||||
if (state) shadowRAM[addr] |= (1 << bit);
|
||||
else shadowRAM[addr] &= ~(1 << bit);
|
||||
|
||||
writeAddr(addr, shadowRAM[addr]);
|
||||
}
|
||||
|
||||
void displayDigit(const int segments[], int number) {
|
||||
uint8_t bits = digitMap[number % 10];
|
||||
for (int i = 0; i < 7; i++) {
|
||||
// We check Bit 0, then Bit 1, etc.
|
||||
// This maps i=0 to Segment A, i=1 to Segment B...
|
||||
bool state = (bits >> i) & 0x01;
|
||||
writeMappedSegment(segments[i], state);
|
||||
}
|
||||
}
|
||||
|
||||
void printToRow(int row, long value, int decimalPos = 0) {
|
||||
const int** currentRow;
|
||||
int numDigits;
|
||||
|
||||
// Select row configuration
|
||||
switch(row) {
|
||||
case 1: currentRow = digitsRow1; numDigits = 5; break;
|
||||
case 2: currentRow = digitsRow2; numDigits = 5; break;
|
||||
case 3: currentRow = digitsRow3; numDigits = 6; break;
|
||||
default: return;
|
||||
}
|
||||
|
||||
// Display the number right-aligned
|
||||
long tempValue = value;
|
||||
for (int i = numDigits - 1; i >= 0; i--) {
|
||||
if (tempValue > 0 || i == numDigits - 1) { // Show at least one digit
|
||||
displayDigit(currentRow[i], tempValue % 10);
|
||||
tempValue /= 10;
|
||||
} else {
|
||||
// Clear leading zeros (all segments off)
|
||||
for (int s = 0; s < 7; s++) writeMappedSegment(currentRow[i][s], false);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle Decimal Points (if applicable for that row)
|
||||
if (decimalPos > 0 && decimalPos <= 3) {
|
||||
writeMappedSegment(decimals[row-1][decimalPos-1], true);
|
||||
}
|
||||
}
|
||||
|
||||
int counter1 = 0;
|
||||
int counter2 = 0;
|
||||
int counter3 = 0;
|
||||
|
||||
void loop() {
|
||||
printToRow(1, counter1);
|
||||
printToRow(2, counter2);
|
||||
printToRow(3, counter3);
|
||||
|
||||
counter1 = (counter1 + 1) % 1000;
|
||||
counter2 = (counter2 + 2) % 1000;
|
||||
counter3 = (counter3 + 3) % 1000;
|
||||
delay(100);
|
||||
}
|
||||
|
||||
// void loop() {
|
||||
// writeAddr(0, 0x00);
|
||||
// writeAddr(1, 0x00);
|
||||
// writeAddr(2, 0x00);
|
||||
// writeAddr(3, 0x00);
|
||||
|
||||
// displayDigit(row1_d1, counter);
|
||||
|
||||
// Serial.println(counter);
|
||||
|
||||
// counter++;
|
||||
// if (counter > 9) {
|
||||
// counter = 0;
|
||||
// }
|
||||
|
||||
// delay(1000);
|
||||
// }
|
||||
|
||||
// void loop() {
|
||||
// if (Serial.available() > 0) {
|
||||
// char input = Serial.read();
|
||||
|
||||
// // Ignore newline/carriage return characters
|
||||
// if (input == '\n' || input == '\r') return;
|
||||
|
||||
// if (input == 'n' || input == 'N') {
|
||||
// currentBit++;
|
||||
// if (currentBit > 3) {
|
||||
// currentBit = 0;
|
||||
// currentAddr++;
|
||||
// }
|
||||
// if (currentAddr > 31) currentAddr = 0;
|
||||
// updateDisplay();
|
||||
// }
|
||||
// else if (input == 'p' || input == 'P') {
|
||||
// currentBit--;
|
||||
// if (currentBit < 0) {
|
||||
// currentBit = 3;
|
||||
// currentAddr--;
|
||||
// }
|
||||
// if (currentAddr < 0) currentAddr = 31;
|
||||
// updateDisplay();
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
11
extensions/platformio/display_driver/test/README
Normal file
11
extensions/platformio/display_driver/test/README
Normal file
@@ -0,0 +1,11 @@
|
||||
|
||||
This directory is intended for PlatformIO Test Runner and project tests.
|
||||
|
||||
Unit Testing is a software testing method by which individual units of
|
||||
source code, sets of one or more MCU program modules together with associated
|
||||
control data, usage procedures, and operating procedures, are tested to
|
||||
determine whether they are fit for use. Unit testing finds problems early
|
||||
in the development cycle.
|
||||
|
||||
More information about PlatformIO Unit Testing:
|
||||
- https://docs.platformio.org/en/latest/advanced/unit-testing/index.html
|
||||
BIN
extensions/python/KeyGenerator/PLU+FSMA+list+v1.0.xlsx
Normal file
BIN
extensions/python/KeyGenerator/PLU+FSMA+list+v1.0.xlsx
Normal file
Binary file not shown.
164
extensions/python/KeyGenerator/createPDF.py
Normal file
164
extensions/python/KeyGenerator/createPDF.py
Normal file
@@ -0,0 +1,164 @@
|
||||
import os
|
||||
import json
|
||||
import requests
|
||||
import barcode
|
||||
import urllib3
|
||||
import re
|
||||
from barcode.writer import ImageWriter
|
||||
from PIL import Image, ImageDraw, ImageFont
|
||||
from io import BytesIO
|
||||
|
||||
# --- SETTINGS ---
|
||||
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||
|
||||
JSON_FILE = os.path.join(os.getcwd(), 'curated_list.json')
|
||||
CARD_DIR = os.path.join(os.getcwd(), 'keychain_cards')
|
||||
IMG_CACHE_DIR = os.path.join(os.getcwd(), 'image_cache')
|
||||
OUTPUT_PDF = os.path.join(os.getcwd(), 'keychain_3x3_perfect.pdf')
|
||||
|
||||
# A4 at 300 DPI
|
||||
PAGE_W, PAGE_H = 2480, 3508
|
||||
COLS, ROWS = 3, 3
|
||||
PAGE_MARGIN = 150
|
||||
|
||||
# Ensure directories exist
|
||||
for d in [CARD_DIR, IMG_CACHE_DIR]:
|
||||
os.makedirs(d, exist_ok=True)
|
||||
|
||||
def clean_filename(name):
|
||||
return re.sub(r'[\\/*?:"<>|]', "_", name)
|
||||
|
||||
def get_ean_from_plu(plu):
|
||||
return f"000000{str(plu).zfill(4)}00"
|
||||
|
||||
def get_cached_image(url, plu):
|
||||
cache_path = os.path.join(IMG_CACHE_DIR, f"{plu}.jpg")
|
||||
|
||||
# Si el archivo ya existe en el cache, lo usamos sin importar la URL
|
||||
if os.path.exists(cache_path):
|
||||
return cache_path
|
||||
|
||||
# Si no existe y la URL es un placeholder, no podemos descargar nada
|
||||
if url == "URL_PLACEHOLDER":
|
||||
print(f"⚠️ {plu} tiene placeholder y no se encontró en {IMG_CACHE_DIR}")
|
||||
return None
|
||||
|
||||
# Lógica de descarga original para URLs reales
|
||||
try:
|
||||
headers = {'User-Agent': 'Mozilla/5.0'}
|
||||
res = requests.get(url, headers=headers, timeout=10, verify=False)
|
||||
if res.status_code == 200:
|
||||
with open(cache_path, 'wb') as f:
|
||||
f.write(res.content)
|
||||
return cache_path
|
||||
except Exception as e:
|
||||
print(f"❌ Error descargando {plu}: {e}")
|
||||
return None
|
||||
|
||||
def generate_card(item):
|
||||
name = item['name']
|
||||
plu = item['plu']
|
||||
img_url = item['image']
|
||||
|
||||
safe_name = clean_filename(name).replace(' ', '_')
|
||||
final_path = os.path.join(CARD_DIR, f"PLU_{plu}_{safe_name}.png")
|
||||
|
||||
if os.path.exists(final_path):
|
||||
return final_path
|
||||
|
||||
local_img_path = get_cached_image(img_url, plu)
|
||||
card = Image.new('RGB', (300, 450), color='white')
|
||||
draw = ImageDraw.Draw(card)
|
||||
draw.rectangle([0, 0, 299, 449], outline="black", width=3)
|
||||
|
||||
if local_img_path:
|
||||
try:
|
||||
img = Image.open(local_img_path).convert("RGB")
|
||||
w, h = img.size
|
||||
size = min(w, h)
|
||||
img = img.crop(((w-size)//2, (h-size)//2, (w+size)//2, (h+size)//2))
|
||||
img = img.resize((200, 200), Image.Resampling.LANCZOS)
|
||||
card.paste(img, (50, 40))
|
||||
except:
|
||||
draw.text((150, 140), "[IMG ERROR]", anchor="mm", fill="red")
|
||||
else:
|
||||
draw.text((150, 140), "[NOT FOUND]", anchor="mm", fill="red")
|
||||
|
||||
try:
|
||||
f_name = ImageFont.truetype("arialbd.ttf", 22)
|
||||
f_plu = ImageFont.truetype("arial.ttf", 18)
|
||||
except:
|
||||
f_name = f_plu = ImageFont.load_default()
|
||||
|
||||
draw.text((150, 260), name.upper(), fill="black", font=f_name, anchor="mm")
|
||||
draw.text((150, 295), f"PLU: {plu}", fill="#333333", font=f_plu, anchor="mm")
|
||||
|
||||
EAN = barcode.get_barcode_class('ean13')
|
||||
ean = EAN(get_ean_from_plu(plu), writer=ImageWriter())
|
||||
tmp = f"tmp_{plu}"
|
||||
ean.save(tmp, options={'module_height': 12.0, 'font_size': 10, 'text_distance': 4})
|
||||
|
||||
if os.path.exists(f"{tmp}.png"):
|
||||
b_img = Image.open(f"{tmp}.png")
|
||||
b_img = b_img.resize((280, 120))
|
||||
card.paste(b_img, (10, 320))
|
||||
os.remove(f"{tmp}.png")
|
||||
|
||||
card.save(final_path)
|
||||
print(f" - Card created: {name} ({plu})")
|
||||
return final_path
|
||||
|
||||
def create_pdf():
|
||||
all_files = sorted([f for f in os.listdir(CARD_DIR) if f.endswith('.png')])
|
||||
if not all_files:
|
||||
print("❌ No cards found to put in PDF.")
|
||||
return
|
||||
|
||||
available_w = PAGE_W - (PAGE_MARGIN * 2)
|
||||
available_h = PAGE_H - (PAGE_MARGIN * 2)
|
||||
slot_w, slot_h = available_w // COLS, available_h // ROWS
|
||||
|
||||
target_w = int(slot_w * 0.9)
|
||||
target_h = int(target_w * (450 / 300))
|
||||
|
||||
if target_h > (slot_h * 0.9):
|
||||
target_h = int(slot_h * 0.9)
|
||||
target_w = int(target_h * (300 / 450))
|
||||
|
||||
pages = []
|
||||
current_page = Image.new('RGB', (PAGE_W, PAGE_H), 'white')
|
||||
|
||||
print(f"📄 Organizing {len(all_files)} cards into {COLS}x{ROWS} grid...")
|
||||
|
||||
for i, filename in enumerate(all_files):
|
||||
item_idx = i % (COLS * ROWS)
|
||||
if item_idx == 0 and i > 0:
|
||||
pages.append(current_page)
|
||||
current_page = Image.new('RGB', (PAGE_W, PAGE_H), 'white')
|
||||
|
||||
row, col = item_idx // COLS, item_idx % COLS
|
||||
img_path = os.path.join(CARD_DIR, filename)
|
||||
card_img = Image.open(img_path).convert('RGB')
|
||||
card_img = card_img.resize((target_w, target_h), Image.Resampling.LANCZOS)
|
||||
|
||||
x = PAGE_MARGIN + (col * slot_w) + (slot_w - target_w) // 2
|
||||
y = PAGE_MARGIN + (row * slot_h) + (slot_h - target_h) // 2
|
||||
current_page.paste(card_img, (x, y))
|
||||
|
||||
pages.append(current_page)
|
||||
pages[0].save(OUTPUT_PDF, save_all=True, append_images=pages[1:], resolution=300.0, quality=100)
|
||||
print(f"✅ Created {OUTPUT_PDF}. Now go print it and stop crying.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
if not os.path.exists(JSON_FILE):
|
||||
print(f"❌ Missing {JSON_FILE}")
|
||||
else:
|
||||
with open(JSON_FILE, 'r', encoding='utf-8') as f:
|
||||
data = json.load(f)
|
||||
|
||||
print(f"Step 1: Processing {len(data)} cards...")
|
||||
for entry in data:
|
||||
generate_card(entry)
|
||||
|
||||
print("\nStep 2: Generating PDF...")
|
||||
create_pdf()
|
||||
51
extensions/python/KeyGenerator/excel_parser.py
Normal file
51
extensions/python/KeyGenerator/excel_parser.py
Normal file
@@ -0,0 +1,51 @@
|
||||
import pandas as pd
|
||||
import json
|
||||
import os
|
||||
|
||||
file_path = os.path.join(os.getcwd(), 'PLU+FSMA+list+v1.0.xlsx')
|
||||
sheet_name = 'Non FTL'
|
||||
new_url_base = "https://server-ifps.accurateig.com/assets/commodities/"
|
||||
|
||||
def get_one_of_each():
|
||||
if not os.path.exists(file_path):
|
||||
print("❌ Excel file not found.")
|
||||
return
|
||||
|
||||
# 1. Load Excel
|
||||
df = pd.read_excel(file_path, sheet_name=sheet_name)
|
||||
|
||||
# 2. Drop rows missing the essentials
|
||||
df = df.dropna(subset=['IMAGE', 'PLU', 'COMMODITY'])
|
||||
|
||||
# 3. CRITICAL: Drop duplicates by COMMODITY only
|
||||
# This ignores Variety and Size, giving us exactly one row per fruit type.
|
||||
df_unique = df.drop_duplicates(subset=['COMMODITY'], keep='first')
|
||||
|
||||
data_output = []
|
||||
|
||||
for _, row in df_unique.iterrows():
|
||||
# Extract filename from the messy URL in Excel
|
||||
original_link = str(row['IMAGE'])
|
||||
filename = original_link.split('/')[-1]
|
||||
|
||||
# Build the final working URL
|
||||
image_url = f"{new_url_base}{filename}"
|
||||
|
||||
# Get the clean Commodity name
|
||||
commodity = str(row['COMMODITY']).title()
|
||||
plu_code = str(row['PLU'])
|
||||
|
||||
data_output.append({
|
||||
"name": commodity,
|
||||
"plu": plu_code,
|
||||
"image": image_url
|
||||
})
|
||||
|
||||
# 4. Save to JSON
|
||||
with open('one_of_each.json', 'w', encoding='utf-8') as f:
|
||||
json.dump(data_output, f, indent=4, ensure_ascii=False)
|
||||
|
||||
print(f"✅ Success! Generated 'one_of_each.json' with {len(data_output)} unique commodities.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
get_one_of_each()
|
||||
41
extensions/python/ScannerCOM/scanner.py
Normal file
41
extensions/python/ScannerCOM/scanner.py
Normal file
@@ -0,0 +1,41 @@
|
||||
import serial
|
||||
import requests
|
||||
import time
|
||||
|
||||
# --- CONFIGURATION ---
|
||||
COM_PORT = 'COM5' # Change to /dev/ttyUSB0 on Linux
|
||||
BAUD_RATE = 115200
|
||||
# The IP of the PC running your Flask WebUI
|
||||
SERVER_URL = "https://scanner.sekidesu.xyz/scan" # Change to your server's URL
|
||||
|
||||
def run_bridge():
|
||||
try:
|
||||
# Initialize serial connection
|
||||
ser = serial.Serial(COM_PORT, BAUD_RATE, timeout=0.1)
|
||||
print(f"Connected to {COM_PORT} at {BAUD_RATE} bauds.")
|
||||
print("Ready to scan. Try not to break it.")
|
||||
|
||||
while True:
|
||||
# Read line from scanner (most scanners send \r or \n at the end)
|
||||
if ser.in_waiting > 0:
|
||||
barcode = ser.readline().decode('utf-8').strip()
|
||||
|
||||
if barcode:
|
||||
print(f"Scanned: {barcode}")
|
||||
try:
|
||||
# Send to your existing Flask server
|
||||
# We use the same parameter 'content' so your server doesn't know the difference
|
||||
resp = requests.get(SERVER_URL, params={'content': barcode})
|
||||
print(f"Server responded: {resp.status_code}")
|
||||
except Exception as e:
|
||||
print(f"Failed to send to server: {e}")
|
||||
|
||||
time.sleep(0.01) # Don't melt your CPU
|
||||
|
||||
except serial.SerialException as e:
|
||||
print(f"Error opening {COM_PORT}: {e}")
|
||||
except KeyboardInterrupt:
|
||||
print("\nBridge stopped by user. Quitter.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
run_bridge()
|
||||
36
extensions/python/ScannerCOM/scannerV2.py
Normal file
36
extensions/python/ScannerCOM/scannerV2.py
Normal file
@@ -0,0 +1,36 @@
|
||||
import serial
|
||||
import requests
|
||||
import time
|
||||
import argparse
|
||||
import sys
|
||||
|
||||
def run_bridge():
|
||||
parser = argparse.ArgumentParser(description="Scanner Bridge for the technically impaired")
|
||||
parser.add_argument('--port', default='COM5', help='Serial port (default: COM5)')
|
||||
parser.add_argument('--baud', type=int, default=115200, help='Baud rate (default: 115200)')
|
||||
parser.add_argument('--url', default='https://scanner.sekidesu.xyz/scan', help='Server URL')
|
||||
args = parser.parse_args()
|
||||
|
||||
try:
|
||||
ser = serial.Serial(args.port, args.baud, timeout=0.1)
|
||||
print(f"Connected to {args.port} at {args.baud} bauds.")
|
||||
|
||||
while True:
|
||||
if ser.in_waiting > 0:
|
||||
barcode = ser.readline().decode('utf-8', errors='ignore').strip()
|
||||
if barcode:
|
||||
print(f"Scanned: {barcode}")
|
||||
try:
|
||||
resp = requests.get(args.url, params={'content': barcode}, timeout=5)
|
||||
print(f"Server responded: {resp.status_code}")
|
||||
except Exception as e:
|
||||
print(f"Failed to send to server: {e}")
|
||||
time.sleep(0.01)
|
||||
|
||||
except serial.SerialException as e:
|
||||
print(f"Error opening {args.port}: {e}")
|
||||
except KeyboardInterrupt:
|
||||
print("\nBridge stopped. Finally.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
run_bridge()
|
||||
155
extensions/python/ScannerHID/gui_scanner.py
Normal file
155
extensions/python/ScannerHID/gui_scanner.py
Normal file
@@ -0,0 +1,155 @@
|
||||
import tkinter as tk
|
||||
from tkinter import ttk
|
||||
import threading
|
||||
import requests
|
||||
import usb.core
|
||||
import usb.util
|
||||
import usb.backend.libusb1
|
||||
import os
|
||||
import time
|
||||
|
||||
VENDOR_ID = 0xFFFF
|
||||
PRODUCT_ID = 0x0035
|
||||
|
||||
HID_MAP = {
|
||||
4: 'a', 5: 'b', 6: 'c', 7: 'd', 8: 'e', 9: 'f', 10: 'g', 11: 'h', 12: 'i',
|
||||
13: 'j', 14: 'k', 15: 'l', 16: 'm', 17: 'n', 18: 'o', 19: 'p', 20: 'q',
|
||||
21: 'r', 22: 's', 23: 't', 24: 'u', 25: 'v', 26: 'w', 27: 'x', 28: 'y', 29: 'z',
|
||||
30: '1', 31: '2', 32: '3', 33: '4', 34: '5', 35: '6', 36: '7', 37: '8', 38: '9', 39: '0',
|
||||
44: ' ', 45: '-', 46: '=', 55: '.', 56: '/'
|
||||
}
|
||||
|
||||
class POSBridgeApp:
|
||||
def __init__(self, root):
|
||||
self.root = root
|
||||
self.root.title("POS Hardware Bridge")
|
||||
self.root.geometry("500x320")
|
||||
self.running = True
|
||||
|
||||
# UI Setup
|
||||
ttk.Label(root, text="Target POS Endpoint:").pack(pady=(15, 2))
|
||||
self.url_var = tk.StringVar(value="https://scanner.sekidesu.xyz/scan")
|
||||
self.url_entry = ttk.Entry(root, textvariable=self.url_var, width=60)
|
||||
self.url_entry.pack(pady=5)
|
||||
|
||||
self.status_var = tk.StringVar(value="Status: Booting...")
|
||||
self.status_label = ttk.Label(root, textvariable=self.status_var, font=("Segoe UI", 10, "bold"))
|
||||
self.status_label.pack(pady=10)
|
||||
|
||||
ttk.Label(root, text="Activity Log:").pack()
|
||||
self.log_listbox = tk.Listbox(root, width=70, height=8, bg="#1e1e1e", fg="#00ff00", font=("Consolas", 9))
|
||||
self.log_listbox.pack(pady=5, padx=10)
|
||||
|
||||
# Bind the close button to kill threads cleanly
|
||||
self.root.protocol("WM_DELETE_WINDOW", self.on_close)
|
||||
|
||||
# Fire up the USB listener in a background thread
|
||||
self.usb_thread = threading.Thread(target=self.usb_listen_loop, daemon=True)
|
||||
self.usb_thread.start()
|
||||
|
||||
def log(self, message):
|
||||
# Tkinter requires GUI updates to happen on the main thread
|
||||
self.root.after(0, self._append_log, message)
|
||||
|
||||
def _append_log(self, message):
|
||||
self.log_listbox.insert(0, time.strftime("[%H:%M:%S] ") + message)
|
||||
if self.log_listbox.size() > 15:
|
||||
self.log_listbox.delete(15)
|
||||
|
||||
def update_status(self, text, color="black"):
|
||||
self.root.after(0, self._set_status, text, color)
|
||||
|
||||
def _set_status(self, text, color):
|
||||
self.status_var.set(f"Status: {text}")
|
||||
self.status_label.config(foreground=color)
|
||||
|
||||
def on_close(self):
|
||||
self.running = False
|
||||
self.root.destroy()
|
||||
|
||||
def send_to_pos(self, barcode):
|
||||
url = self.url_var.get()
|
||||
self.log(f"Captured: {barcode}. Sending...")
|
||||
try:
|
||||
resp = requests.get(url, params={'content': barcode}, timeout=3)
|
||||
self.log(f"Success: POS returned {resp.status_code}")
|
||||
except requests.RequestException as e:
|
||||
self.log(f"HTTP Error: Backend unreachable")
|
||||
|
||||
def usb_listen_loop(self):
|
||||
import sys
|
||||
# PyInstaller extracts files to a temp _MEIPASS folder at runtime
|
||||
if getattr(sys, 'frozen', False):
|
||||
base_path = sys._MEIPASS
|
||||
else:
|
||||
base_path = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
dll_path = os.path.join(base_path, "libusb-1.0.dll")
|
||||
|
||||
if not os.path.exists(dll_path):
|
||||
self.update_status(f"CRITICAL: DLL missing at {dll_path}", "red")
|
||||
return
|
||||
|
||||
backend = usb.backend.libusb1.get_backend(find_library=lambda x: dll_path)
|
||||
|
||||
while self.running:
|
||||
# Reconnect loop
|
||||
dev = usb.core.find(idVendor=VENDOR_ID, idProduct=PRODUCT_ID, backend=backend)
|
||||
|
||||
if dev is None:
|
||||
self.update_status("Scanner unplugged. Waiting...", "red")
|
||||
time.sleep(2)
|
||||
continue
|
||||
|
||||
try:
|
||||
dev.set_configuration()
|
||||
cfg = dev.get_active_configuration()
|
||||
intf = cfg[(0,0)]
|
||||
endpoint = usb.util.find_descriptor(
|
||||
intf,
|
||||
custom_match=lambda e: usb.util.endpoint_direction(e.bEndpointAddress) == usb.util.ENDPOINT_IN
|
||||
)
|
||||
|
||||
self.update_status("Scanner Locked & Ready", "green")
|
||||
current_barcode = ""
|
||||
|
||||
# Active reading loop
|
||||
while self.running:
|
||||
try:
|
||||
data = dev.read(endpoint.bEndpointAddress, endpoint.wMaxPacketSize, timeout=1000)
|
||||
|
||||
keycode = data[2]
|
||||
modifier = data[0]
|
||||
is_shift = modifier == 2 or modifier == 32
|
||||
|
||||
if keycode == 0:
|
||||
continue
|
||||
|
||||
if keycode == 40: # Enter key signifies end of scan
|
||||
if current_barcode:
|
||||
# Spawn a micro-thread for the HTTP request so we don't block the next scan
|
||||
threading.Thread(target=self.send_to_pos, args=(current_barcode,), daemon=True).start()
|
||||
current_barcode = ""
|
||||
elif keycode in HID_MAP:
|
||||
char = HID_MAP[keycode]
|
||||
if is_shift and char.isalpha():
|
||||
char = char.upper()
|
||||
current_barcode += char
|
||||
|
||||
except usb.core.USBError as e:
|
||||
# 10060/110 are normal timeouts when no barcode is being actively scanned
|
||||
if e.args[0] in (10060, 110):
|
||||
continue
|
||||
else:
|
||||
self.log(f"Hardware interrupt lost. Reconnecting...")
|
||||
break # Breaks inner loop to trigger outer reconnect loop
|
||||
|
||||
except Exception as e:
|
||||
self.log(f"USB Error: {e}")
|
||||
time.sleep(2) # Prevent rapid crash loops
|
||||
|
||||
if __name__ == '__main__':
|
||||
# You must run pip install requests if you haven't already
|
||||
root = tk.Tk()
|
||||
app = POSBridgeApp(root)
|
||||
root.mainloop()
|
||||
BIN
extensions/python/ScannerHID/libusb-1.0.dll
Normal file
BIN
extensions/python/ScannerHID/libusb-1.0.dll
Normal file
Binary file not shown.
25
extensions/python/checkHIDDevices.py
Normal file
25
extensions/python/checkHIDDevices.py
Normal file
@@ -0,0 +1,25 @@
|
||||
import usb.core
|
||||
import usb.backend.libusb1
|
||||
import os
|
||||
|
||||
# Grab the exact path to the DLL in your current folder
|
||||
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
dll_path = os.path.join(current_dir, "libusb-1.0.dll")
|
||||
|
||||
if not os.path.exists(dll_path):
|
||||
print(f"I don't see the DLL at: {dll_path}")
|
||||
exit(1)
|
||||
|
||||
# Force pyusb to use this specific file
|
||||
backend = usb.backend.libusb1.get_backend(find_library=lambda x: dll_path)
|
||||
|
||||
print("Scanning with forced local DLL backend...")
|
||||
devices = usb.core.find(find_all=True, backend=backend)
|
||||
|
||||
found = False
|
||||
for d in devices:
|
||||
found = True
|
||||
print(f"Found Device -> VID: {hex(d.idVendor)} PID: {hex(d.idProduct)}")
|
||||
|
||||
if not found:
|
||||
print("Python is still blind. The DLL might be the wrong architecture (32-bit vs 64-bit).")
|
||||
85
extensions/python/hidScanner.py
Normal file
85
extensions/python/hidScanner.py
Normal file
@@ -0,0 +1,85 @@
|
||||
import usb.core
|
||||
import usb.util
|
||||
import usb.backend.libusb1
|
||||
import os
|
||||
import sys
|
||||
|
||||
# Your exact scanner IDs
|
||||
VENDOR_ID = 0xFFFF
|
||||
PRODUCT_ID = 0x0035
|
||||
|
||||
# Basic HID to ASCII translation dictionary
|
||||
HID_MAP = {
|
||||
4: 'a', 5: 'b', 6: 'c', 7: 'd', 8: 'e', 9: 'f', 10: 'g', 11: 'h', 12: 'i',
|
||||
13: 'j', 14: 'k', 15: 'l', 16: 'm', 17: 'n', 18: 'o', 19: 'p', 20: 'q',
|
||||
21: 'r', 22: 's', 23: 't', 24: 'u', 25: 'v', 26: 'w', 27: 'x', 28: 'y', 29: 'z',
|
||||
30: '1', 31: '2', 32: '3', 33: '4', 34: '5', 35: '6', 36: '7', 37: '8', 38: '9', 39: '0',
|
||||
44: ' ', 45: '-', 46: '=', 55: '.', 56: '/'
|
||||
}
|
||||
|
||||
def main():
|
||||
# Force the local DLL backend
|
||||
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
dll_path = os.path.join(current_dir, "libusb-1.0.dll")
|
||||
|
||||
if not os.path.exists(dll_path):
|
||||
print(f"Error: Missing {dll_path}")
|
||||
sys.exit(1)
|
||||
|
||||
backend = usb.backend.libusb1.get_backend(find_library=lambda x: dll_path)
|
||||
|
||||
# Find the scanner using the forced backend
|
||||
dev = usb.core.find(idVendor=VENDOR_ID, idProduct=PRODUCT_ID, backend=backend)
|
||||
|
||||
if dev is None:
|
||||
print("Scanner not found. Check Zadig driver again.")
|
||||
sys.exit(1)
|
||||
|
||||
# Claim device
|
||||
dev.set_configuration()
|
||||
cfg = dev.get_active_configuration()
|
||||
intf = cfg[(0,0)]
|
||||
|
||||
endpoint = usb.util.find_descriptor(
|
||||
intf,
|
||||
custom_match=lambda e: usb.util.endpoint_direction(e.bEndpointAddress) == usb.util.ENDPOINT_IN
|
||||
)
|
||||
|
||||
print("Scanner locked. Waiting for barcodes...")
|
||||
|
||||
current_barcode = ""
|
||||
|
||||
while True:
|
||||
try:
|
||||
# Read 8 bytes from the scanner
|
||||
data = dev.read(endpoint.bEndpointAddress, endpoint.wMaxPacketSize, timeout=1000)
|
||||
|
||||
keycode = data[2]
|
||||
modifier = data[0]
|
||||
is_shift = modifier == 2 or modifier == 32
|
||||
|
||||
if keycode == 0:
|
||||
continue
|
||||
|
||||
if keycode == 40: # Enter key
|
||||
print(f"Captured Barcode: {current_barcode}")
|
||||
current_barcode = ""
|
||||
elif keycode in HID_MAP:
|
||||
char = HID_MAP[keycode]
|
||||
if is_shift and char.isalpha():
|
||||
char = char.upper()
|
||||
current_barcode += char
|
||||
|
||||
except usb.core.USBError as e:
|
||||
if e.args[0] == 10060 or e.args[0] == 110:
|
||||
continue
|
||||
else:
|
||||
print(f"USB Error: {e}")
|
||||
break
|
||||
except KeyboardInterrupt:
|
||||
print("\nExiting and releasing scanner...")
|
||||
usb.util.dispose_resources(dev)
|
||||
break
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
29
extensions/python/migrateLegacyDB.py
Normal file
29
extensions/python/migrateLegacyDB.py
Normal file
@@ -0,0 +1,29 @@
|
||||
import sqlite3
|
||||
|
||||
DB_FILE = 'db/pos_database.db'
|
||||
|
||||
def upgrade_db():
|
||||
try:
|
||||
with sqlite3.connect(DB_FILE) as conn:
|
||||
<<<<<<< HEAD
|
||||
=======
|
||||
# Add stock column
|
||||
# conn.execute("ALTER TABLE products ADD COLUMN stock REAL DEFAULT 0")
|
||||
# print("Successfully added 'stock' column.")
|
||||
|
||||
# # App.py also expects unit_type, adding it to prevent future headaches
|
||||
# conn.execute("ALTER TABLE products ADD COLUMN unit_type TEXT DEFAULT 'unit'")
|
||||
# print("Successfully added 'unit_type' column.")
|
||||
>>>>>>> 1a048a0e074ee26bd45dda9731c78c2ecef42fba
|
||||
|
||||
conn.execute("ALTER TABLE dicom ADD COLUMN image_url TEXT;")
|
||||
print("Successfully added 'image_url' column.")
|
||||
|
||||
conn.commit()
|
||||
print("Migration complete. Your data is intact.")
|
||||
|
||||
except sqlite3.OperationalError as e:
|
||||
print(f"Skipped: {e}. (This usually means the columns already exist, so you're fine).")
|
||||
|
||||
if __name__ == '__main__':
|
||||
upgrade_db()
|
||||
Reference in New Issue
Block a user