43 Commits
v2 ... v3

Author SHA1 Message Date
9cf0792866 cli mode 2026-03-17 16:15:18 -03:00
719e227ba4 golang scanner imp 2026-03-17 16:13:57 -03:00
ace45b1cc9 Merge branch 'main' of https://gitea.sekidesu.xyz/SekiDesu01/SekiPOS 2026-03-17 16:06:53 -03:00
0bd47658e9 hid scanner support 2026-03-17 16:06:22 -03:00
97a592b0c9 goscan fixes 2026-03-17 14:53:41 -03:00
4bbbb0334c WIP scale fw 2026-03-16 18:27:02 -03:00
27d5fb26d9 reorder meh 2026-03-13 23:39:38 -03:00
5119d4bb31 Merge branch 'main' of https://gitea.sekidesu.xyz/SekiDesu01/SekiPOS 2026-03-13 21:55:23 -03:00
acaf537f11 reverese engeneering of TM1621 display 2026-03-13 21:54:45 -03:00
55ff314163 Merge branch 'main' of https://gitea.sekidesu.xyz/SekiDesu01/SekiPOS 2026-03-13 18:07:54 -03:00
376b8c54a6 Version bump to v2.0, updated checkout and dicom 2026-03-13 18:07:09 -03:00
1a048a0e07 Fixed recepit lenght 2026-03-11 16:52:17 -03:00
77fc5920a2 image export like db 2026-03-11 15:59:50 -03:00
78d48db9ea added image_url column to dicom table 2026-03-10 23:39:44 -03:00
92e3a3f0f9 dicom evidence update 2026-03-10 23:22:50 -03:00
57cb27f6cf db dump tool 2026-03-10 22:47:40 -03:00
c24dae9694 actions button position fix 2026-03-10 22:41:34 -03:00
e0ac23a8e0 re-print old receipt from sales page 2026-03-10 22:37:47 -03:00
c1a06dc44c allow for inmediate confimation of checkout 2026-03-10 22:32:37 -03:00
72f6e0c822 receipt print fix 2026-03-10 22:25:09 -03:00
b2418d8c7e style fixes 2026-03-10 22:15:18 -03:00
9cb057668b login style fix 2026-03-10 21:41:15 -03:00
cb2aa89b16 WIP rewrite with macros 2026-03-10 20:15:58 -03:00
ef9a9296dd initial modularization + templates 2026-03-10 18:40:47 -03:00
3c4b2e148d Implemented Dicom 2026-03-09 18:25:49 -03:00
bf1bc84cd0 Checkout UI fixes 2026-03-09 16:54:55 -03:00
8e37f9e776 Implemented change calculator and various UI fixes 2026-03-09 16:32:23 -03:00
216abc8ad2 Fixed receipt design 2026-03-09 13:02:00 -03:00
cffa3d789b modified: templates/checkout.html 2026-03-09 11:34:49 -03:00
d7ef1573e5 modified: templates/checkout.html 2026-03-09 10:20:59 -03:00
e101833c7d modified: templates/checkout.html 2026-03-09 09:53:30 -03:00
6c98919c80 Added option forcustom product during checkout 2026-03-09 07:00:47 -03:00
cae35a266f Implemented receipt generation and printing 2026-03-09 06:20:24 -03:00
c57e8ab6db Implemented sales dashboard 2026-03-09 05:58:24 -03:00
135b14adcf Added persistent cart for chechout 2026-03-09 05:06:25 -03:00
9f59e122ef Various chechout improvements: subtotal rounding, placeholder color, and weight input now in grams for better UX. 2026-03-09 03:53:28 -03:00
43cc2a3caa Fixed UI inconsistencies and added logic to handle unit types in the product list and form. Also added a call to toggle the stock input visibility on page load based on the default unit type. 2026-03-09 02:55:07 -03:00
shironeko
2f2998b0fd esp attempt, stock + unit type 2026-03-07 19:21:14 -03:00
788b67804e oops 2026-03-07 03:35:22 -03:00
2bb38570f9 deactivated MP stuff 2026-03-07 03:23:00 -03:00
aacbce2557 oops 2026-03-07 03:07:55 -03:00
6c5085093d chekout update, mp study v1 2026-03-07 03:07:45 -03:00
423d563cc0 image compression, initial checkout(? 2026-03-04 22:47:02 -03:00
1201 changed files with 155335 additions and 4009 deletions

2
.gitignore vendored
View File

@@ -1,3 +1,5 @@
pos_database.db
ScannerGO/ScannerGO-*
ScannerGO/config.json
DataToolsGO/imageTools-*
.env

11
.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,11 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Build SekiPOS (F5)",
"type": "node",
"request": "launch",
"preLaunchTask": "build-sekipos"
}
]
}

3
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,3 @@
{
"python-envs.defaultEnvManager": "ms-python.python:pyenv"
}

15
.vscode/tasks.json vendored Normal file
View File

@@ -0,0 +1,15 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "build-sekipos",
"type": "shell",
"command": "docker build -t sekipos:latest .",
"group": "build",
"presentation": {
"reveal": "always",
"panel": "new"
}
}
]
}

42
DataToolsGO/build.sh Executable file
View 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
DataToolsGO/go.mod Normal file
View File

@@ -0,0 +1,5 @@
module dataTools
go 1.25.7
require github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646

2
DataToolsGO/go.sum Normal file
View 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
DataToolsGO/main.go Normal file
View 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)
}
}

View File

@@ -10,6 +10,7 @@ RUN pip install --no-cache-dir -r requirements.txt
COPY app.py .
COPY templates/ ./templates/
COPY static/ ./static/
#COPY .env .
# Create the folder structure for the volume mounts
RUN mkdir -p /app/static/cache

File diff suppressed because it is too large Load Diff

162
KeyGenerator/frutas2.json Normal file
View File

@@ -0,0 +1,162 @@
[
{
"name": "Paltas",
"plu": "3509",
"image": "https://server-ifps.accurateig.com/assets/commodities/3509-gem-avocado_1625011346.JPG"
},
{
"name": "Papas",
"plu": "3414",
"image": "https://server-ifps.accurateig.com/assets/commodities/3414-baking-potato-white_1635179333.jpg"
},
{
"name": "Limones",
"plu": "3626",
"image": "https://server-ifps.accurateig.com/assets/commodities/3626-meyer-lemons_1460404763.jpg"
},
{
"name": "Cebollines / Cebollas",
"plu": "4068",
"image": "https://server-ifps.accurateig.com/assets/commodities/4068-onions-green-2_1625063600.jpg"
},
{
"name": "Ajo",
"plu": "4608",
"image": "https://server-ifps.accurateig.com/assets/commodities/4608-garlic-regular_1637184640.jpg"
},
{
"name": "Zanahorias",
"plu": "4560",
"image": "https://server-ifps.accurateig.com/assets/commodities/4560-carrots-baby_1625673556.jpg"
},
{
"name": "Plátanos",
"plu": "4235",
"image": "https://server-ifps.accurateig.com/assets/commodities/4235-plantains-01_1625076376.jpg"
},
{
"name": "Manzanas",
"plu": "4099",
"image": "https://server-ifps.accurateig.com/assets/commodities/apples-akane_1629314651.png"
},
{
"name": "Naranjas",
"plu": "4381",
"image": "https://server-ifps.accurateig.com/assets/commodities/4381-oranges-blood-01_1625082045.jpg"
},
{
"name": "Choclo",
"plu": "3087",
"image": "https://server-ifps.accurateig.com/assets/commodities/3087-corn-04_1614633780.jpg"
},
{
"name": "Zapallo",
"plu": "4734",
"image": "https://server-ifps.accurateig.com/assets/commodities/4734-mini-pumpkin_1633964765.jpg"
},
{
"name": "Zapallo italiano",
"plu": "4750",
"image": "https://server-ifps.accurateig.com/assets/commodities/4750-squash-acorn-01_1625751871.jpg"
},
{
"name": "Repollo",
"plu": "4069",
"image": "https://server-ifps.accurateig.com/assets/commodities/4069-green-cabbage_1633958066.jpg"
},
{
"name": "Betarragas",
"plu": "4537",
"image": "https://server-ifps.accurateig.com/assets/commodities/4537-baby-golden-beets_1635173500.jpg"
},
{
"name": "Porotos verdes",
"plu": "4527",
"image": "https://server-ifps.accurateig.com/assets/commodities/4527-beans-chinese-long-07_1625671743.jpg"
},
{
"name": "Mandarinas",
"plu": "3524",
"image": "https://server-ifps.accurateig.com/assets/commodities/noble-juicycrunch-productisolated-900x900-rgb-copy_1627662136.png"
},
{
"name": "Uvas",
"plu": "3491",
"image": "https://server-ifps.accurateig.com/assets/commodities/arra-15-grape_1518122851.JPG"
},
{
"name": "Duraznos",
"plu": "3113",
"image": "https://server-ifps.accurateig.com/assets/commodities/3113-peaches-donut-04_1614707155.jpg"
},
{
"name": "Cerezas",
"plu": "3549",
"image": "https://server-ifps.accurateig.com/assets/commodities/screenshot-2023-05-02-100428_1683036305.png"
},
{
"name": "Peras",
"plu": "3317",
"image": "https://server-ifps.accurateig.com/assets/commodities/3317-angelys-pear_1460402454.jpg"
},
{
"name": "Ciruelas",
"plu": "4435",
"image": "https://server-ifps.accurateig.com/assets/commodities/4435-plums-greengage-01_1625667846.jpg"
},
{
"name": "Kiwi",
"plu": "3279",
"image": "https://server-ifps.accurateig.com/assets/commodities/3279-kiwi-gold-03_1614718637.jpg"
},
{
"name": "Piña",
"plu": "4430",
"image": "https://server-ifps.accurateig.com/assets/commodities/4430-pineapple-05_1625667649.jpg"
},
{
"name": "Puerros",
"plu": "4629",
"image": "https://server-ifps.accurateig.com/assets/commodities/4629-leeks-02_1625680225.jpg"
},
{
"name": "Alcachofas",
"plu": "4519",
"image": "https://server-ifps.accurateig.com/assets/commodities/4519-artichokes-baby-02_1625671366.jpg"
},
{
"name": "Espárragos",
"plu": "4521",
"image": "https://server-ifps.accurateig.com/assets/commodities/4521-asparagus-01_1625671446.jpg"
},
{
"name": "Berenjena",
"plu": "4599",
"image": "https://server-ifps.accurateig.com/assets/commodities/4599-baby-eggplant-aubergine_1633372314.jpg"
},
{
"name": "Durazno Nectarin",
"plu": "3437",
"image": "https://server-ifps.accurateig.com/assets/commodities/yellow-fleshed-flat-nectarine_1629140965.jpg"
},
{
"name": "Damascos",
"plu": "3044",
"image": "https://server-ifps.accurateig.com/assets/commodities/3044-apricots-black-velvet-03_1614619576.jpg"
},
{
"name": "Membrillo",
"plu": "4447",
"image": "https://server-ifps.accurateig.com/assets/commodities/4447-quince-1_1625668926.jpg"
},
{
"name": "Champiñones",
"plu": "4647",
"image": "https://server-ifps.accurateig.com/assets/commodities/4647-mushrooms-chanterelles-1_1625681503.jpg"
},
{
"name": "Castañas",
"plu": "4927",
"image": "https://server-ifps.accurateig.com/assets/commodities/4927-chestnuts-italian-02_1625756943.jpg"
}
]

View File

@@ -11,7 +11,7 @@ from io import BytesIO
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
# --- CONFIGURATION ---
JSON_FILE = os.path.join(os.getcwd(), 'frutas.json')
JSON_FILE = os.path.join(os.getcwd(), 'frutas2.json')
OUTPUT_DIR = os.path.join(os.getcwd(), 'keychain_cards')
IMG_CACHE_DIR = os.path.join(os.getcwd(), 'image_cache')
os.makedirs(OUTPUT_DIR, exist_ok=True)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 119 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

View File

@@ -1,437 +0,0 @@
[
{
"PLU": "4924",
"Name": "Almonds",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4924-almonds_1630598936.jpg"
},
{
"PLU": "3525",
"Name": "Apples",
"URL": "https://server-ifps.accurateig.com/assets/commodities/3486-cn121-apple-image_1627662287.JPG"
},
{
"PLU": "4218",
"Name": "Apricots",
"URL": "https://server-ifps.accurateig.com/assets/commodities/3302-apricots-1-1614718940_1633443369.jpg"
},
{
"PLU": "4516",
"Name": "Artichokes",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4516-artichokes_1630598556.jpg"
},
{
"PLU": "4080",
"Name": "Asparagus",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4521-asparagus-01-1625671446_1632939137.jpg"
},
{
"PLU": "4046",
"Name": "Avocados",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4770-hass-avocado_1635960530.jpg"
},
{
"PLU": "3303",
"Name": "Babaco",
"URL": "https://server-ifps.accurateig.com/assets/commodities/3303-babacoa_1614719281.JPG"
},
{
"PLU": "4186",
"Name": "Bananas",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4011-yellow-banana_1634142809.jpg"
},
{
"PLU": "4528",
"Name": "Beans",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4528-beans-fava-07_1625671831.jpg"
},
{
"PLU": "4537",
"Name": "Beets",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4537-baby-golden-beets_1635173500.jpg"
},
{
"PLU": "4247",
"Name": "Berries",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4028-berries-strawberries-06-1625013614_1633444773.jpg"
},
{
"PLU": "4926",
"Name": "Brazilnuts",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4926-brazil-nuts-02_1625756785.JPG"
},
{
"PLU": "4069",
"Name": "Cabbage",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4069-green-cabbage_1633958066.jpg"
},
{
"PLU": "4560",
"Name": "Carrots",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4560-carrots-baby_1625673556.jpg"
},
{
"PLU": "3105",
"Name": "Cashews",
"URL": "https://server-ifps.accurateig.com/assets/commodities/3105-cashews-03_1614635878.JPG"
},
{
"PLU": "3321",
"Name": "Celery Root/Celeriac",
"URL": "https://server-ifps.accurateig.com/assets/commodities/3321-celery-root-03_1614720213.jpg"
},
{
"PLU": "3357",
"Name": "Cherries",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4045-cherries-05-1625061853_1633445797.jpg"
},
{
"PLU": "4927",
"Name": "Chestnuts",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4927-chestnuts-italian-02_1625756943.jpg"
},
{
"PLU": "4261",
"Name": "Coconuts",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4261-coconuts-02_1625077777.jpg"
},
{
"PLU": "3087",
"Name": "Corn",
"URL": "https://server-ifps.accurateig.com/assets/commodities/3087-corn-04_1614633780.jpg"
},
{
"PLU": "4862",
"Name": "Dates",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4862-dates_1630598790.jpg"
},
{
"PLU": "4892",
"Name": "Dill",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4892-dill-baby-02_1625755376.jpg"
},
{
"PLU": "4599",
"Name": "Eggplant (Aubergine)",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4599-baby-eggplant-aubergine_1633372314.jpg"
},
{
"PLU": "4606",
"Name": "Fiddlehead Ferns",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4606-fiddlehead-ferns-07_1625679559.jpg"
},
{
"PLU": "4266",
"Name": "Figs",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4266-figs-03_1625077969.jpg"
},
{
"PLU": "4608",
"Name": "Garlic",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4608-garlic-regular_1637184640.jpg"
},
{
"PLU": "4612",
"Name": "Ginger Root",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4612-ginger-root-06_1625679690.jpg"
},
{
"PLU": "3091",
"Name": "Gobo Root/Burdock",
"URL": "https://server-ifps.accurateig.com/assets/commodities/3091gobo-root-02_1614634374.jpg"
},
{
"PLU": "4280",
"Name": "Grapefruit",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4492-grapefruit-ruby-red_1633960726.jpg"
},
{
"PLU": "3505",
"Name": "Grapes",
"URL": "https://server-ifps.accurateig.com/assets/commodities/arra-32-image-3_1629296403.PNG"
},
{
"PLU": "4614",
"Name": "Greens",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4614-collard-greens-trimmed_1625679839.JPG"
},
{
"PLU": "4625",
"Name": "Horseradish Root",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4625-horseradish-root-04_1625680026.jpg"
},
{
"PLU": "4626",
"Name": "Jicama/Yam Bean",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4626-jicama_1635179919.jpg"
},
{
"PLU": "3280",
"Name": "Kiwifruit",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4030-kiwi-fruit-04-1625013864_1633447661.jpg"
},
{
"PLU": "4303",
"Name": "Kumquat",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4303-kumquats-03_1625079229.jpg"
},
{
"PLU": "4629",
"Name": "Leeks",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4629-leeks-02_1625680225.jpg"
},
{
"PLU": "4033",
"Name": "Lemons",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4033-lemons_1630597866.jpg"
},
{
"PLU": "4328",
"Name": "Limequats",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4328-limequats-01_1625081231.jpg"
},
{
"PLU": "4048",
"Name": "Limes",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4048-limes-02_1625062144.jpg"
},
{
"PLU": "4308",
"Name": "Loquats",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4308-lychees-5_1625079574.jpg"
},
{
"PLU": "3099",
"Name": "Lotus Root",
"URL": "https://server-ifps.accurateig.com/assets/commodities/3099-lotus-root-03_1614635240.jpg"
},
{
"PLU": "3106",
"Name": "Macadamia",
"URL": "https://server-ifps.accurateig.com/assets/commodities/3106-macadamias_1614705972.JPG"
},
{
"PLU": "4644",
"Name": "Malanga",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4644-malanga_1633956612.jpg"
},
{
"PLU": "3475",
"Name": "Mint",
"URL": "https://server-ifps.accurateig.com/assets/commodities/peppermint-3475_1625790587.jpg"
},
{
"PLU": "4645",
"Name": "Mushrooms",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4645-mushrooms-white-button-02_1625681263.jpg"
},
{
"PLU": "3276",
"Name": "Name",
"URL": "https://server-ifps.accurateig.com/assets/commodities/3276-name-white_1634146226.jpg"
},
{
"PLU": "4377",
"Name": "Nectarine",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4378-nectarines-1-1625081931_1633448513.jpg"
},
{
"PLU": "4665",
"Name": "Onions",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4093-onions-yellow-01-1625064621_1632941716.jpg"
},
{
"PLU": "4013",
"Name": "Oranges",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4012-navel-orange_1634141444.jpg"
},
{
"PLU": "4672",
"Name": "Parsnip",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4672-parsnip_1633958885.jpg"
},
{
"PLU": "3311",
"Name": "Passion Fruit",
"URL": "https://server-ifps.accurateig.com/assets/commodities/3311-passion-fruit-02_1614719436.jpg"
},
{
"PLU": "4037",
"Name": "Peaches",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4403-peaches-1-1625664942_1633449692.jpg"
},
{
"PLU": "4931",
"Name": "Peanuts",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4931-peanuts_1625757234.JPG"
},
{
"PLU": "4025",
"Name": "Pears",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4025-anjou-pear_1629988897.PNG"
},
{
"PLU": "4673",
"Name": "Peas",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4673-peas-black-eyed-01_1625684339.jpg"
},
{
"PLU": "4936",
"Name": "Pecans",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4936-pecans-01_1625757366.JPG"
},
{
"PLU": "3459",
"Name": "Persimmon",
"URL": "https://server-ifps.accurateig.com/assets/commodities/3459-shiny-red-persimmon_1486570958.JPG"
},
{
"PLU": "3039",
"Name": "Physalis/Cape Gooseberry/Ground Cherry",
"URL": "https://server-ifps.accurateig.com/assets/commodities/3039-cape-gooseberry_1630596655.jpg"
},
{
"PLU": "4029",
"Name": "Pineapple",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4029-pineapple_1630597815.jpg"
},
{
"PLU": "4939",
"Name": "Pistachio",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4939-pistachios_1625757519.JPG"
},
{
"PLU": "3040",
"Name": "Pitahaya",
"URL": "https://server-ifps.accurateig.com/assets/commodities/3040-pitaya-dragon-fruit-01_1614282658.jpg"
},
{
"PLU": "3611",
"Name": "Plumcot (Interspecific Plum)",
"URL": "https://server-ifps.accurateig.com/assets/commodities/interspecific-plum-black-picture_1629142350.JPG"
},
{
"PLU": "4434",
"Name": "Plums",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4435-plums-greengage-01-1625667846_1633453839.jpg"
},
{
"PLU": "4445",
"Name": "Pomegranate",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4445-pomegranates-07_1625668628.jpg"
},
{
"PLU": "3128",
"Name": "Potato",
"URL": "https://server-ifps.accurateig.com/assets/commodities/3128-potatoes-purple-06_1614708225.jpg"
},
{
"PLU": "3631",
"Name": "Pumpkin",
"URL": "https://server-ifps.accurateig.com/assets/commodities/3631-pink-pumpkin_1460472104.jpg"
},
{
"PLU": "3480",
"Name": "Pumpkin Vine",
"URL": "https://server-ifps.accurateig.com/assets/commodities/pumpkin-vine-3480_1625790222.jpg"
},
{
"PLU": "3478",
"Name": "Quelites",
"URL": "https://server-ifps.accurateig.com/assets/commodities/quelite-3478_1625790359.jpg"
},
{
"PLU": "4447",
"Name": "Quince",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4447-quince-1_1625668926.jpg"
},
{
"PLU": "3168",
"Name": "Radicchio",
"URL": "https://server-ifps.accurateig.com/assets/commodities/3168-radicchio-castelfranco-02_1614718366.jpg"
},
{
"PLU": "4739",
"Name": "Radish",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4739-radish-black-03_1625751544.jpg"
},
{
"PLU": "4745",
"Name": "Rhubarb",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4745-rhubarb-01_1625751764.jpg"
},
{
"PLU": "4747",
"Name": "Rutabagas (Swede)",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4747-rutabaga_1635168852.jpg"
},
{
"PLU": "3137",
"Name": "Sapote",
"URL": "https://server-ifps.accurateig.com/assets/commodities/3137-sapote-white_1635264028.jpg"
},
{
"PLU": "4757",
"Name": "Squash",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4757-squash-banana_1625752222.JPG"
},
{
"PLU": "4790",
"Name": "Sugar Cane",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4790-sugar-cane_1630598686.jpg"
},
{
"PLU": "4791",
"Name": "Sunchokes (Jerusalem Artichokes)",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4791-sunchokes-03_1625753005.jpg"
},
{
"PLU": "4942",
"Name": "Sunflower Seeds",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4942-sunflower-seeds_1630599026.jpg"
},
{
"PLU": "4074",
"Name": "Sweet Potato/Yam/Kumara",
"URL": "https://server-ifps.accurateig.com/assets/commodities/sweet-potato-red-flesh-4074_1614281686.png"
},
{
"PLU": "4448",
"Name": "Tamarind",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4448-tamarindo-01_1625669100.jpg"
},
{
"PLU": "3428",
"Name": "Tangerines/Mandarins",
"URL": "https://server-ifps.accurateig.com/assets/commodities/murcott-mandarin-1629310772_1633456088.png"
},
{
"PLU": "4794",
"Name": "Taro Root (Dasheen)",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4795-taro-root-01-1625753179_1633374616.jpg"
},
{
"PLU": "4811",
"Name": "Turnip",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4811-turnip-purple-top_1635177577.jpg"
},
{
"PLU": "4943",
"Name": "Walnuts",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4943-walnuts_1625757652.JPG"
},
{
"PLU": "4814",
"Name": "Water Chestnuts",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4814-waterchestnuts-01_1625753939.jpg"
},
{
"PLU": "3481",
"Name": "Xpelon",
"URL": "https://server-ifps.accurateig.com/assets/commodities/expelon-3481_1625790150.jpg"
},
{
"PLU": "4819",
"Name": "Yuca Root/Cassava/Manioc",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4819-yucca-root-03_1625754507.jpg"
}
]

8
MPStudyGO/go.mod Normal file
View 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
MPStudyGO/go.sum Normal file
View 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
MPStudyGO/main.go Normal file
View 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
}

View File

@@ -1,4 +1,4 @@
# SekiPOS v1.6 🍫🥤
# SekiPOS v2.0 🍫🥤
A reactive POS inventory system for software engineers with a snack addiction. Features real-time UI updates, automatic product discovery via Open Food Facts, and local image caching.
@@ -84,6 +84,7 @@ python app.py
## 📋 TODOs?
- Some form of user registration(?)
- Major refactoring of the codebase
## 🥼 Food Datasets
- https://www.ifpsglobal.com/plu-codes-search

5
ScaleHardware/IOT_Scale/.gitignore vendored Normal file
View File

@@ -0,0 +1,5 @@
.pio
.vscode/.browse.c_cpp.db*
.vscode/c_cpp_properties.json
.vscode/launch.json
.vscode/ipch

View 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"
]
}

View 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

View 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

View File

@@ -0,0 +1,16 @@
; 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

View File

@@ -0,0 +1,89 @@
#include <Arduino.h>
const int SCLK_PIN = 18;
const int DOUT_PIN = 19;
const int TARE_BUTTON_PIN = 4;
long offset = 0;
float scaleFactor = 1.0; // Update this after your calibration test
void setup() {
Serial.begin(115200);
pinMode(SCLK_PIN, OUTPUT);
pinMode(DOUT_PIN, INPUT);
pinMode(TARE_BUTTON_PIN, INPUT_PULLUP);
digitalWrite(SCLK_PIN, LOW);
Serial.println("Initializing SD10819 ADC...");
delay(500); // Wait for chip stabilization
calibrateZero();
}
long readADC() {
// 1. Wait for DRDY to go LOW
// Timeout added to prevent infinite loop if ADC is disconnected
unsigned long startWait = millis();
while (digitalRead(DOUT_PIN) == HIGH) {
if (millis() - startWait > 500) return 0;
}
long value = 0;
// 2. Read 24 bits of data
for (int i = 0; i < 24; i++) {
digitalWrite(SCLK_PIN, HIGH);
delayMicroseconds(1); // pulse width t3 > 100ns
value = (value << 1) | digitalRead(DOUT_PIN);
digitalWrite(SCLK_PIN, LOW);
delayMicroseconds(1);
}
// 3. Configuration Pulses: 27 pulses = Channel A, 128x Gain
for (int i = 0; i < 3; i++) {
digitalWrite(SCLK_PIN, HIGH);
delayMicroseconds(1);
digitalWrite(SCLK_PIN, LOW);
delayMicroseconds(1);
}
// Handle 24-bit two's complement
if (value & 0x800000) {
value |= 0xFF000000;
}
return value;
}
void calibrateZero() {
Serial.println("Taring... stay still.");
long sum = 0;
for(int i = 0; i < 20; i++) {
sum += readADC();
delay(10);
}
offset = sum / 20;
Serial.print("New Offset: ");
Serial.println(offset);
}
void loop() {
// Check Tare Button
if (digitalRead(TARE_BUTTON_PIN) == LOW) {
calibrateZero();
while(digitalRead(TARE_BUTTON_PIN) == LOW); // Wait for release
}
// Read and Filter
long raw = readADC();
float weight = (float)(raw - offset) * scaleFactor;
// Serial Output for Debugging / Plotting
Serial.print("Raw:");
Serial.print(raw);
Serial.print("\tWeight:");
Serial.print(weight, 2);
Serial.println("g");
delay(100); // 10Hz sampling rate
}

View 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

View File

@@ -0,0 +1,5 @@
.pio
.vscode/.browse.c_cpp.db*
.vscode/c_cpp_properties.json
.vscode/launch.json
.vscode/ipch

View 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"
]
}

View 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

View 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

View 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

View 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

View 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();
// }
// }
// }

View 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

View File

@@ -2,6 +2,42 @@ module ScannerGO
go 1.25.7
require github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07
require (
fyne.io/fyne/v2 v2.7.3
github.com/google/gousb v1.1.3
)
require golang.org/x/sys v0.41.0 // indirect
require (
fyne.io/systray v1.12.0 // 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/image v0.24.0 // indirect
golang.org/x/net v0.35.0 // indirect
golang.org/x/sys v0.41.0 // indirect
golang.org/x/text v0.22.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

View File

@@ -1,4 +1,82 @@
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07 h1:UyzmZLoiDWMRywV4DUYb9Fbt8uiOSooupjTq10vpvnU=
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
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=
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/image v0.24.0 h1:AN7zRgVsbvmTfNyqIbbOraYL8mSwcKncEj8ofjgzcMQ=
golang.org/x/image v0.24.0/go.mod h1:4b/ITuLfqYq1hqZcjofwctIhi7sZh2WaCjvsBNjjya8=
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.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
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=

View File

@@ -1,159 +1,229 @@
package main
import (
"bufio"
"bytes"
"encoding/json"
"flag"
"fmt"
"io"
"image/color"
"net/http"
"net/url"
"os"
"strings"
"time"
"github.com/tarm/serial"
"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 {
Port string `json:"port"`
URL string `json:"url"`
BaudRate int `json:"baud"`
Delimiter string `json:"delimiter"`
TargetURL string `json:"target_url"`
VendorID uint16 `json:"vendor_id"`
ProductID uint16 `json:"product_id"`
}
var defaultConfig = Config{
Port: "/dev/ttyACM0",
URL: "https://scanner.sekidesu.xyz/scan",
BaudRate: 115200,
Delimiter: "\n",
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: "/",
}
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")
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
if *save {
saveConfig(cfg)
fmt.Println("Settings saved to", configPath)
}
serialConfig := &serial.Config{
Name: cfg.Port,
Baud: cfg.BaudRate,
ReadTimeout: 0,
}
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, Delim: %q)...\n", cfg.Port, cfg.BaudRate, cfg.Delimiter)
scanner := bufio.NewScanner(port)
// Custom split function to handle the configurable delimiter
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() {
content := strings.TrimSpace(scanner.Text())
if content != "" {
sendToEndpoint(cfg.URL, content)
}
}
if err := scanner.Err(); err != nil {
fmt.Printf("Scanner error: %v\n", err)
}
type BridgeApp struct {
urlEntry *widget.Entry
status *canvas.Text
logList *widget.List
logs []string
window fyne.Window
config Config
isCLI bool
}
func loadConfig() Config {
if _, err := os.Stat(configPath); os.IsNotExist(err) {
saveConfig(defaultConfig)
return defaultConfig
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
}
file, err := os.Open(configPath)
if err != nil {
return defaultConfig
}
defer file.Close()
func main() {
cliMode := flag.Bool("cli", false, "Run in CLI mode without GUI")
flag.Parse()
var cfg Config
decoder := json.NewDecoder(file)
if err := decoder.Decode(&cfg); err != nil {
return defaultConfig
}
// Handle case where JSON exists but field is missing
if cfg.Delimiter == "" {
cfg.Delimiter = "\n"
}
return cfg
conf := loadConfig()
bridge := &BridgeApp{
config: conf,
isCLI: *cliMode,
}
func saveConfig(cfg Config) {
file, err := os.Create(configPath)
if err != nil {
fmt.Printf("Failed to create/save config: %v\n", err)
if *cliMode {
fmt.Println("Running in CLI mode...")
bridge.usbListenLoop()
return
}
defer file.Close()
encoder := json.NewEncoder(file)
encoder.SetIndent("", " ")
encoder.Encode(cfg)
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))
go bridge.usbListenLoop()
w.ShowAndRun()
}
func sendToEndpoint(baseURL, content string) {
client := &http.Client{
Timeout: 5 * time.Second,
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
}
fullURL := fmt.Sprintf("%s?content=%s", baseURL, url.QueryEscape(content))
resp, err := client.Get(fullURL)
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 {
fmt.Printf("Network Error: %v\n", err)
b.addLog("HTTP Error: Backend unreachable")
return
}
defer resp.Body.Close()
b.addLog(fmt.Sprintf("Success: POS returned %d", resp.StatusCode))
}
body, err := io.ReadAll(resp.Body)
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 {
fmt.Printf("Error reading response: %v\n", err)
return
b.addLog("Error claiming interface")
dev.Close()
time.Sleep(2 * time.Second)
continue
}
fmt.Printf("Data: [%s] | Status: %s\n", content, resp.Status)
if len(body) > 0 {
fmt.Printf("Response: %s\n", string(body))
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...")
}
fmt.Println(strings.Repeat("-", 30))
}

402
app.py
View File

@@ -1,12 +1,24 @@
import os
import sqlite3
import requests
from flask import send_file
from flask import Flask, render_template, request, jsonify, redirect, url_for, flash, send_from_directory
from flask_socketio import SocketIO, emit
from flask_login import LoginManager, UserMixin, login_user, login_required, logout_user, current_user
from werkzeug.security import generate_password_hash, check_password_hash
import mimetypes
import time
import uuid
from datetime import datetime
import zipfile
import io
# from dotenv import load_dotenv
# load_dotenv()
# MP_ACCESS_TOKEN = os.getenv('MP_ACCESS_TOKEN')
# MP_TERMINAL_ID = os.getenv('MP_TERMINAL_ID')
app = Flask(__name__)
app.config['SECRET_KEY'] = 'seki_super_secret_key_99' # Change this if you have actual friends
@@ -31,10 +43,41 @@ def init_db():
with sqlite3.connect(DB_FILE) as conn:
conn.execute('''CREATE TABLE IF NOT EXISTS users
(id INTEGER PRIMARY KEY, username TEXT UNIQUE, password TEXT)''')
# Updated table definition
conn.execute('''CREATE TABLE IF NOT EXISTS products
(barcode TEXT PRIMARY KEY, name TEXT, price REAL, image_url TEXT)''')
(barcode TEXT PRIMARY KEY,
name TEXT,
price REAL,
image_url TEXT,
stock REAL DEFAULT 0,
unit_type TEXT DEFAULT 'unit')''')
# Default user: admin / Pass: choripan1234
# Add these two tables for sales history
conn.execute('''CREATE TABLE IF NOT EXISTS sales
(id INTEGER PRIMARY KEY AUTOINCREMENT,
date TEXT DEFAULT CURRENT_TIMESTAMP,
total REAL,
payment_method TEXT)''')
conn.execute('''CREATE TABLE IF NOT EXISTS sale_items
(id INTEGER PRIMARY KEY AUTOINCREMENT,
sale_id INTEGER,
barcode TEXT,
name TEXT,
price REAL,
quantity REAL,
subtotal REAL,
FOREIGN KEY(sale_id) REFERENCES sales(id))''')
conn.execute('''CREATE TABLE IF NOT EXISTS dicom
(id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT UNIQUE,
amount REAL DEFAULT 0,
notes TEXT,
image_url TEXT,
last_updated TEXT DEFAULT CURRENT_TIMESTAMP)''')
# Default user logic remains same...
user = conn.execute('SELECT * FROM users WHERE username = ?', ('admin',)).fetchone()
if not user:
hashed_pw = generate_password_hash('choripan1234')
@@ -105,23 +148,70 @@ def login():
user = conn.execute('SELECT * FROM users WHERE username = ?', (user_in,)).fetchone()
if user and check_password_hash(user[2], pass_in):
login_user(User(user[0], user[1]))
return redirect(url_for('index'))
return redirect(url_for('inventory'))
flash('Invalid credentials.')
return render_template('login.html')
@app.route('/logout')
@login_required
def logout():
logout_user(); return redirect(url_for('login'))
logout_user()
return redirect(url_for('login'))
@app.route('/')
@login_required
def index():
def defaultRoute():
return redirect(url_for('inventory'))
@app.route('/inventory')
@login_required
def inventory():
with sqlite3.connect(DB_FILE) as conn:
products = conn.execute('SELECT * FROM products').fetchall()
return render_template('index.html', products=products, user=current_user)
return render_template('inventory.html', active_page='inventory', products=products, user=current_user)
@app.route('/upsert', methods=['POST'])
@app.route("/checkout")
@login_required
def checkout():
with sqlite3.connect(DB_FILE) as conn:
# Fetching the same columns the scanner expects
products = conn.execute('SELECT barcode, name, price, image_url, stock, unit_type FROM products').fetchall()
return render_template("checkout.html", active_page='checkout', user=current_user, products=products)
@app.route('/dicom')
@login_required
def dicom():
with sqlite3.connect(DB_FILE) as conn:
debtors = conn.execute('SELECT id, name, amount, notes, datetime(last_updated, "localtime"), image_url FROM dicom ORDER BY amount DESC').fetchall()
return render_template('dicom.html', active_page='dicom', user=current_user, debtors=debtors)
@app.route('/sales')
@login_required
def sales():
selected_date = request.args.get('date')
with sqlite3.connect(DB_FILE) as conn:
cur = conn.cursor()
# Determine the target date for the "Daily" stat
target_date = selected_date if selected_date else cur.execute("SELECT date('now', 'localtime')").fetchone()[0]
stats = {
"daily": cur.execute("SELECT SUM(total) FROM sales WHERE date(date, 'localtime') = ?", (target_date,)).fetchone()[0] or 0,
"week": cur.execute("SELECT SUM(total) FROM sales WHERE date(date, 'localtime') >= date('now', 'localtime', '-7 days')").fetchone()[0] or 0,
"month": cur.execute("SELECT SUM(total) FROM sales WHERE strftime('%Y-%m', date, 'localtime') = strftime('%Y-%m', 'now', 'localtime')").fetchone()[0] or 0
}
if selected_date:
sales_data = cur.execute('''SELECT id, date, total, payment_method FROM sales
WHERE date(date, 'localtime') = ?
ORDER BY date DESC''', (selected_date,)).fetchall()
else:
sales_data = cur.execute('SELECT id, date, total, payment_method FROM sales ORDER BY date DESC LIMIT 100').fetchall()
return render_template('sales.html', active_page='sales', user=current_user, sales=sales_data, stats=stats, selected_date=selected_date)
@app.route("/upsert", methods=["POST"])
@login_required
def upsert():
d = request.form
@@ -129,18 +219,27 @@ def upsert():
try:
price = float(d['price'])
stock = float(d.get('stock', 0)) # New field
except (ValueError, TypeError):
price = 0.0
stock = 0.0
unit_type = d.get('unit_type', 'unit') # New field (unit or kg)
final_image_path = download_image(d['image_url'], barcode)
with sqlite3.connect(DB_FILE) as conn:
conn.execute('''INSERT INTO products (barcode, name, price, image_url) VALUES (?,?,?,?)
ON CONFLICT(barcode) DO UPDATE SET name=excluded.name,
price=excluded.price, image_url=excluded.image_url''',
(barcode, d['name'], price, final_image_path))
# Updated UPSERT query
conn.execute('''INSERT INTO products (barcode, name, price, image_url, stock, unit_type)
VALUES (?,?,?,?,?,?)
ON CONFLICT(barcode) DO UPDATE SET
name=excluded.name,
price=excluded.price,
image_url=excluded.image_url,
stock=excluded.stock,
unit_type=excluded.unit_type''',
(barcode, d['name'], price, final_image_path, stock, unit_type))
conn.commit()
return redirect(url_for('index'))
return redirect(url_for('inventory'))
@app.route('/delete/<barcode>', methods=['POST'])
@login_required
@@ -152,7 +251,7 @@ def delete(barcode):
img_p = os.path.join(CACHE_DIR, f"{barcode}.jpg")
if os.path.exists(img_p): os.remove(img_p)
socketio.emit('product_deleted', {"barcode": barcode})
return redirect(url_for('index'))
return redirect(url_for('inventory'))
@app.route('/scan', methods=['GET'])
def scan():
@@ -161,13 +260,13 @@ def scan():
return jsonify({"status": "error", "message": "empty barcode"}), 400
with sqlite3.connect(DB_FILE) as conn:
p = conn.execute('SELECT * FROM products WHERE barcode = ?', (barcode,)).fetchone()
# Fixed: Selecting all 6 necessary columns
p = conn.execute('SELECT barcode, name, price, image_url, stock, unit_type FROM products WHERE barcode = ?', (barcode,)).fetchone()
# 1. Product exists in local Database
if p:
barcode_val, name, price, image_path = p
# Now matches the 6 columns in the SELECT statement
barcode_val, name, price, image_path, stock, unit_type = p
# Image recovery logic for missing local files
if image_path and image_path.startswith('/static/'):
clean_path = image_path.split('?')[0].lstrip('/')
if not os.path.exists(clean_path):
@@ -182,7 +281,9 @@ def scan():
"barcode": barcode_val,
"name": name,
"price": int(price),
"image": image_path
"image": image_path,
"stock": stock,
"unit_type": unit_type
}
socketio.emit('new_scan', product_data)
@@ -266,13 +367,274 @@ def upload_image():
barcode = request.form['barcode']
if file.filename == '' or not barcode:
return jsonify({"error": "Invalid data"}), 400
ext = mimetypes.guess_extension(file.mimetype) or '.jpg'
filename = f"{barcode}{ext}"
filename = f"{barcode}.jpg"
filepath = os.path.join(CACHE_DIR, filename)
file.save(filepath)
timestamp = int(time.time())
return jsonify({"status": "success", "image_url": f"/static/cache/{filename}?t={timestamp}"}), 200
@app.route('/api/scale/weight', methods=['POST'])
def update_scale_weight():
data = request.get_json()
# Assuming the scale sends {"weight": 1250} (in grams)
weight_grams = data.get('weight', 0)
# Optional: Convert to kg if you prefer
weight_kg = round(weight_grams / 1000, 3)
# Broadcast to all connected clients via SocketIO
socketio.emit('scale_update', {
"grams": weight_grams,
"kilograms": weight_kg,
"timestamp": time.time()
})
return jsonify({"status": "received"}), 200
@app.route('/api/checkout', methods=['POST'])
@login_required
def process_checkout():
try:
data = request.get_json()
cart = data.get('cart', [])
payment_method = data.get('payment_method', 'efectivo')
if not cart:
return jsonify({"error": "Cart is empty"}), 400
# Recalculate total on the server because the frontend is a liar
total = sum(item.get('subtotal', 0) for item in cart)
with sqlite3.connect(DB_FILE) as conn:
cur = conn.cursor()
# Let SQLite handle the exact UTC timestamp internally
cur.execute('INSERT INTO sales (date, total, payment_method) VALUES (CURRENT_TIMESTAMP, ?, ?)', (total, payment_method))
sale_id = cur.lastrowid
# Record each item and deduct stock
for item in cart:
cur.execute('''INSERT INTO sale_items (sale_id, barcode, name, price, quantity, subtotal)
VALUES (?, ?, ?, ?, ?, ?)''',
(sale_id, item['barcode'], item['name'], item['price'], item['qty'], item['subtotal']))
# Deduct from inventory (Manual products will safely be ignored here)
cur.execute('UPDATE products SET stock = stock - ? WHERE barcode = ?', (item['qty'], item['barcode']))
conn.commit()
return jsonify({"status": "success", "sale_id": sale_id}), 200
except Exception as e:
print(f"Checkout Error: {e}")
return jsonify({"error": str(e)}), 500
@app.route('/api/sale/<int:sale_id>')
@login_required
def get_sale_details(sale_id):
with sqlite3.connect(DB_FILE) as conn:
items = conn.execute('SELECT barcode, name, price, quantity, subtotal FROM sale_items WHERE sale_id = ?', (sale_id,)).fetchall()
# Format it as a neat list of dictionaries for JavaScript to digest
item_list = [{"barcode": i[0], "name": i[1], "price": i[2], "qty": i[3], "subtotal": i[4]} for i in items]
return jsonify(item_list), 200
@app.route('/api/sale/<int:sale_id>', methods=['DELETE'])
@login_required
def reverse_sale(sale_id):
try:
with sqlite3.connect(DB_FILE) as conn:
cur = conn.cursor()
# 1. Get the items and quantities from the receipt
items = cur.execute('SELECT barcode, quantity FROM sale_items WHERE sale_id = ?', (sale_id,)).fetchall()
# 2. Add the stock back to the inventory
for barcode, qty in items:
# This safely ignores manual products since their fake barcodes won't match any row
cur.execute('UPDATE products SET stock = stock + ? WHERE barcode = ?', (qty, barcode))
# 3. Destroy the evidence
cur.execute('DELETE FROM sale_items WHERE sale_id = ?', (sale_id,))
cur.execute('DELETE FROM sales WHERE id = ?', (sale_id,))
conn.commit()
return jsonify({"status": "success"}), 200
except Exception as e:
print(f"Reverse Sale Error: {e}")
return jsonify({"error": str(e)}), 500
@app.route('/api/dicom/update', methods=['POST'])
@login_required
def update_dicom():
data = request.get_json()
name = data.get('name', '').strip()
amount = float(data.get('amount', 0))
notes = data.get('notes', '')
image_url = data.get('image_url', '')
action = data.get('action')
if not name or amount <= 0:
return jsonify({"error": "Nombre y monto válidos son requeridos"}), 400
if action == 'add':
amount = -amount
with sqlite3.connect(DB_FILE) as conn:
cur = conn.cursor()
cur.execute('''INSERT INTO dicom (name, amount, notes, image_url, last_updated)
VALUES (?, ?, ?, ?, CURRENT_TIMESTAMP)
ON CONFLICT(name) DO UPDATE SET
amount = amount + excluded.amount,
notes = excluded.notes,
image_url = CASE WHEN excluded.image_url != "" THEN excluded.image_url ELSE dicom.image_url END,
last_updated = CURRENT_TIMESTAMP''', (name, amount, notes, image_url))
conn.commit()
return jsonify({"status": "success"}), 200
@app.route('/api/dicom/<int:debtor_id>', methods=['DELETE'])
@login_required
def delete_dicom(debtor_id):
try:
with sqlite3.connect(DB_FILE) as conn:
conn.execute('DELETE FROM dicom WHERE id = ?', (debtor_id,))
conn.commit()
return jsonify({"status": "success"}), 200
except Exception as e:
return jsonify({"error": str(e)}), 500
@app.route('/settings/update', methods=['POST'])
@login_required
def update_settings():
new_password = request.form.get('password')
profile_pic = request.form.get('profile_pic')
with sqlite3.connect(DB_FILE) as conn:
if new_password and len(new_password) > 0:
hashed_pw = generate_password_hash(new_password)
conn.execute('UPDATE users SET password = ? WHERE id = ?', (hashed_pw, current_user.id))
if profile_pic:
conn.execute('UPDATE users SET profile_pic = ? WHERE id = ?', (profile_pic, current_user.id))
conn.commit()
flash('Configuración actualizada')
return redirect(request.referrer)
@app.route('/export/db')
@login_required
def export_db():
if os.path.exists(DB_FILE):
return send_file(DB_FILE, as_attachment=True, download_name=f"SekiPOS_Backup_{datetime.now().strftime('%Y%m%d')}.db", mimetype='application/x-sqlite3')
return "Error: Database file not found", 404
@app.route('/export/images')
@login_required
def export_images():
if not os.path.exists(CACHE_DIR) or not os.listdir(CACHE_DIR):
return "No images found to export", 404
# Create an in-memory byte stream to hold the zip data
memory_file = io.BytesIO()
with zipfile.ZipFile(memory_file, 'w', zipfile.ZIP_DEFLATED) as zf:
for root, dirs, files in os.walk(CACHE_DIR):
for file in files:
file_path = os.path.join(root, file)
# Store files using their names only to avoid nesting inside the zip
zf.write(file_path, arcname=file)
memory_file.seek(0)
return send_file(
memory_file,
mimetype='application/zip',
as_attachment=True,
download_name=f"SekiPOS_Images_{datetime.now().strftime('%Y%m%d')}.zip"
)
# @app.route('/process_payment', methods=['POST'])
# @login_required
# def process_payment():
# data = request.get_json()
# total_amount = data.get('total')
# if not total_amount or total_amount <= 0:
# return jsonify({"error": "Invalid amount"}), 400
# url = "https://api.mercadopago.com/v1/orders"
# headers = {
# "Authorization": f"Bearer {MP_ACCESS_TOKEN}",
# "Content-Type": "application/json",
# "X-Idempotency-Key": str(uuid.uuid4())
# }
# # MP Point API often prefers integer strings for CLP or exact strings
# # We use int() here if you are dealing with CLP (no cents)
# formatted_amount = str(int(float(total_amount)))
# payload = {
# "type": "point",
# "external_reference": f"ref_{int(time.time())}",
# "description": "Venta SekiPOS",
# "expiration_time": "PT16M",
# "transactions": {
# "payments": [
# {
# "amount": formatted_amount
# }
# ]
# },
# "config": {
# "point": {
# "terminal_id": MP_TERMINAL_ID,
# "print_on_terminal": "no_ticket"
# },
# "payment_method": {
# "default_type": "credit_card"
# }
# },
# "integration_data": {
# "platform_id": "dev_1234567890",
# "integrator_id": "dev_1234567890"
# },
# "taxes": [
# {
# "payer_condition": "payment_taxable_iva"
# }
# ]
# }
# try:
# # Verify the payload in your terminal if it fails again
# response = requests.post(url, json=payload, headers=headers)
# if response.status_code != 201 and response.status_code != 200:
# print(f"DEBUG MP ERROR: {response.text}")
# return jsonify(response.json()), response.status_code
# except Exception as e:
# return jsonify({"error": str(e)}), 500
# @app.route('/api/mp-webhook', methods=['POST'])
# def webhook_notify():
# data = request.get_json()
# action = data.get('action', 'unknown')
# # Emitimos a todos los clientes conectados
# socketio.emit('payment_update', {
# "status": action,
# "id": data.get('data', {}).get('id')
# })
# return jsonify({"status": "ok"}), 200
if __name__ == '__main__':
init_db()
socketio.run(app, host='0.0.0.0', port=5000, debug=True)

162
frutas2.json Normal file
View File

@@ -0,0 +1,162 @@
[
{
"name": "Paltas",
"plu": "3509",
"image": "https://server-ifps.accurateig.com/assets/commodities/3509-gem-avocado_1625011346.JPG"
},
{
"name": "Papas",
"plu": "3414",
"image": "https://server-ifps.accurateig.com/assets/commodities/3414-baking-potato-white_1635179333.jpg"
},
{
"name": "Limones",
"plu": "3626",
"image": "https://server-ifps.accurateig.com/assets/commodities/3626-meyer-lemons_1460404763.jpg"
},
{
"name": "Cebollines / Cebollas",
"plu": "4068",
"image": "https://server-ifps.accurateig.com/assets/commodities/4068-onions-green-2_1625063600.jpg"
},
{
"name": "Ajo",
"plu": "4608",
"image": "https://server-ifps.accurateig.com/assets/commodities/4608-garlic-regular_1637184640.jpg"
},
{
"name": "Zanahorias",
"plu": "4560",
"image": "https://server-ifps.accurateig.com/assets/commodities/4560-carrots-baby_1625673556.jpg"
},
{
"name": "Plátanos",
"plu": "4235",
"image": "https://server-ifps.accurateig.com/assets/commodities/4235-plantains-01_1625076376.jpg"
},
{
"name": "Manzanas",
"plu": "4099",
"image": "https://server-ifps.accurateig.com/assets/commodities/apples-akane_1629314651.png"
},
{
"name": "Naranjas",
"plu": "4381",
"image": "https://server-ifps.accurateig.com/assets/commodities/4381-oranges-blood-01_1625082045.jpg"
},
{
"name": "Choclo",
"plu": "3087",
"image": "https://server-ifps.accurateig.com/assets/commodities/3087-corn-04_1614633780.jpg"
},
{
"name": "Zapallo",
"plu": "4734",
"image": "https://server-ifps.accurateig.com/assets/commodities/4734-mini-pumpkin_1633964765.jpg"
},
{
"name": "Zapallo italiano",
"plu": "4750",
"image": "https://server-ifps.accurateig.com/assets/commodities/4750-squash-acorn-01_1625751871.jpg"
},
{
"name": "Repollo",
"plu": "4069",
"image": "https://server-ifps.accurateig.com/assets/commodities/4069-green-cabbage_1633958066.jpg"
},
{
"name": "Betarragas",
"plu": "4537",
"image": "https://server-ifps.accurateig.com/assets/commodities/4537-baby-golden-beets_1635173500.jpg"
},
{
"name": "Porotos verdes",
"plu": "4527",
"image": "https://server-ifps.accurateig.com/assets/commodities/4527-beans-chinese-long-07_1625671743.jpg"
},
{
"name": "Mandarinas",
"plu": "3524",
"image": "https://server-ifps.accurateig.com/assets/commodities/noble-juicycrunch-productisolated-900x900-rgb-copy_1627662136.png"
},
{
"name": "Uvas",
"plu": "3491",
"image": "https://server-ifps.accurateig.com/assets/commodities/arra-15-grape_1518122851.JPG"
},
{
"name": "Duraznos",
"plu": "3113",
"image": "https://server-ifps.accurateig.com/assets/commodities/3113-peaches-donut-04_1614707155.jpg"
},
{
"name": "Cerezas",
"plu": "3549",
"image": "https://server-ifps.accurateig.com/assets/commodities/screenshot-2023-05-02-100428_1683036305.png"
},
{
"name": "Peras",
"plu": "3317",
"image": "https://server-ifps.accurateig.com/assets/commodities/3317-angelys-pear_1460402454.jpg"
},
{
"name": "Ciruelas",
"plu": "4435",
"image": "https://server-ifps.accurateig.com/assets/commodities/4435-plums-greengage-01_1625667846.jpg"
},
{
"name": "Kiwi",
"plu": "3279",
"image": "https://server-ifps.accurateig.com/assets/commodities/3279-kiwi-gold-03_1614718637.jpg"
},
{
"name": "Piña",
"plu": "4430",
"image": "https://server-ifps.accurateig.com/assets/commodities/4430-pineapple-05_1625667649.jpg"
},
{
"name": "Puerros",
"plu": "4629",
"image": "https://server-ifps.accurateig.com/assets/commodities/4629-leeks-02_1625680225.jpg"
},
{
"name": "Alcachofas",
"plu": "4519",
"image": "https://server-ifps.accurateig.com/assets/commodities/4519-artichokes-baby-02_1625671366.jpg"
},
{
"name": "Espárragos",
"plu": "4521",
"image": "https://server-ifps.accurateig.com/assets/commodities/4521-asparagus-01_1625671446.jpg"
},
{
"name": "Berenjena",
"plu": "4599",
"image": "https://server-ifps.accurateig.com/assets/commodities/4599-baby-eggplant-aubergine_1633372314.jpg"
},
{
"name": "Durazno Nectarin",
"plu": "3437",
"image": "https://server-ifps.accurateig.com/assets/commodities/yellow-fleshed-flat-nectarine_1629140965.jpg"
},
{
"name": "Damascos",
"plu": "3044",
"image": "https://server-ifps.accurateig.com/assets/commodities/3044-apricots-black-velvet-03_1614619576.jpg"
},
{
"name": "Membrillo",
"plu": "4447",
"image": "https://server-ifps.accurateig.com/assets/commodities/4447-quince-1_1625668926.jpg"
},
{
"name": "Champiñones",
"plu": "4647",
"image": "https://server-ifps.accurateig.com/assets/commodities/4647-mushrooms-chanterelles-1_1625681503.jpg"
},
{
"name": "Castañas",
"plu": "4927",
"image": "https://server-ifps.accurateig.com/assets/commodities/4927-chestnuts-italian-02_1625756943.jpg"
}
]

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,83 @@
('C:\\Users\\Pepitho\\Desktop\\hid-scanner\\build\\gui_scanner\\gui_scanner.exe',
False,
False,
True,
'C:\\Users\\Pepitho\\AppData\\Roaming\\Python\\Python314\\site-packages\\PyInstaller\\bootloader\\images\\icon-windowed.ico',
None,
False,
False,
b'<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n<assembly xmlns='
b'"urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">\n <trustInfo x'
b'mlns="urn:schemas-microsoft-com:asm.v3">\n <security>\n <requested'
b'Privileges>\n <requestedExecutionLevel level="asInvoker" uiAccess='
b'"false"/>\n </requestedPrivileges>\n </security>\n </trustInfo>\n '
b'<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">\n <'
b'application>\n <supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f'
b'0}"/>\n <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>\n '
b' <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>\n <s'
b'upportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>\n <supporte'
b'dOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>\n </application>\n <'
b'/compatibility>\n <application xmlns="urn:schemas-microsoft-com:asm.v3">'
b'\n <windowsSettings>\n <longPathAware xmlns="http://schemas.micros'
b'oft.com/SMI/2016/WindowsSettings">true</longPathAware>\n </windowsSett'
b'ings>\n </application>\n <dependency>\n <dependentAssembly>\n <ass'
b'emblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version='
b'"6.0.0.0" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" langua'
b'ge="*"/>\n </dependentAssembly>\n </dependency>\n</assembly>',
True,
False,
None,
None,
None,
'C:\\Users\\Pepitho\\Desktop\\hid-scanner\\build\\gui_scanner\\gui_scanner.pkg',
[('pyi-contents-directory _internal', '', 'OPTION'),
('PYZ-00.pyz',
'C:\\Users\\Pepitho\\Desktop\\hid-scanner\\build\\gui_scanner\\PYZ-00.pyz',
'PYZ'),
('struct',
'C:\\Users\\Pepitho\\Desktop\\hid-scanner\\build\\gui_scanner\\localpycs\\struct.pyc',
'PYMODULE'),
('pyimod01_archive',
'C:\\Users\\Pepitho\\Desktop\\hid-scanner\\build\\gui_scanner\\localpycs\\pyimod01_archive.pyc',
'PYMODULE'),
('pyimod02_importers',
'C:\\Users\\Pepitho\\Desktop\\hid-scanner\\build\\gui_scanner\\localpycs\\pyimod02_importers.pyc',
'PYMODULE'),
('pyimod03_ctypes',
'C:\\Users\\Pepitho\\Desktop\\hid-scanner\\build\\gui_scanner\\localpycs\\pyimod03_ctypes.pyc',
'PYMODULE'),
('pyimod04_pywin32',
'C:\\Users\\Pepitho\\Desktop\\hid-scanner\\build\\gui_scanner\\localpycs\\pyimod04_pywin32.pyc',
'PYMODULE'),
('pyiboot01_bootstrap',
'C:\\Users\\Pepitho\\AppData\\Roaming\\Python\\Python314\\site-packages\\PyInstaller\\loader\\pyiboot01_bootstrap.py',
'PYSOURCE'),
('pyi_rth_inspect',
'C:\\Users\\Pepitho\\AppData\\Roaming\\Python\\Python314\\site-packages\\PyInstaller\\hooks\\rthooks\\pyi_rth_inspect.py',
'PYSOURCE'),
('pyi_rth_usb',
'C:\\Users\\Pepitho\\AppData\\Roaming\\Python\\Python314\\site-packages\\_pyinstaller_hooks_contrib\\rthooks\\pyi_rth_usb.py',
'PYSOURCE'),
('pyi_rth_pkgutil',
'C:\\Users\\Pepitho\\AppData\\Roaming\\Python\\Python314\\site-packages\\PyInstaller\\hooks\\rthooks\\pyi_rth_pkgutil.py',
'PYSOURCE'),
('pyi_rth_multiprocessing',
'C:\\Users\\Pepitho\\AppData\\Roaming\\Python\\Python314\\site-packages\\PyInstaller\\hooks\\rthooks\\pyi_rth_multiprocessing.py',
'PYSOURCE'),
('pyi_rth_setuptools',
'C:\\Users\\Pepitho\\AppData\\Roaming\\Python\\Python314\\site-packages\\PyInstaller\\hooks\\rthooks\\pyi_rth_setuptools.py',
'PYSOURCE'),
('pyi_rth__tkinter',
'C:\\Users\\Pepitho\\AppData\\Roaming\\Python\\Python314\\site-packages\\PyInstaller\\hooks\\rthooks\\pyi_rth__tkinter.py',
'PYSOURCE'),
('gui_scanner',
'C:\\Users\\Pepitho\\Desktop\\hid-scanner\\gui_scanner.py',
'PYSOURCE')],
[],
False,
False,
1773774112,
[('runw.exe',
'C:\\Users\\Pepitho\\AppData\\Roaming\\Python\\Python314\\site-packages\\PyInstaller\\bootloader\\Windows-64bit-intel\\runw.exe',
'EXECUTABLE')],
'C:\\Python314\\python314.dll')

View File

@@ -0,0 +1,61 @@
('C:\\Users\\Pepitho\\Desktop\\hid-scanner\\build\\gui_scanner\\gui_scanner.pkg',
{'BINARY': True,
'DATA': True,
'EXECUTABLE': True,
'EXTENSION': True,
'PYMODULE': True,
'PYSOURCE': True,
'PYZ': False,
'SPLASH': True,
'SYMLINK': False},
[('pyi-contents-directory _internal', '', 'OPTION'),
('PYZ-00.pyz',
'C:\\Users\\Pepitho\\Desktop\\hid-scanner\\build\\gui_scanner\\PYZ-00.pyz',
'PYZ'),
('struct',
'C:\\Users\\Pepitho\\Desktop\\hid-scanner\\build\\gui_scanner\\localpycs\\struct.pyc',
'PYMODULE'),
('pyimod01_archive',
'C:\\Users\\Pepitho\\Desktop\\hid-scanner\\build\\gui_scanner\\localpycs\\pyimod01_archive.pyc',
'PYMODULE'),
('pyimod02_importers',
'C:\\Users\\Pepitho\\Desktop\\hid-scanner\\build\\gui_scanner\\localpycs\\pyimod02_importers.pyc',
'PYMODULE'),
('pyimod03_ctypes',
'C:\\Users\\Pepitho\\Desktop\\hid-scanner\\build\\gui_scanner\\localpycs\\pyimod03_ctypes.pyc',
'PYMODULE'),
('pyimod04_pywin32',
'C:\\Users\\Pepitho\\Desktop\\hid-scanner\\build\\gui_scanner\\localpycs\\pyimod04_pywin32.pyc',
'PYMODULE'),
('pyiboot01_bootstrap',
'C:\\Users\\Pepitho\\AppData\\Roaming\\Python\\Python314\\site-packages\\PyInstaller\\loader\\pyiboot01_bootstrap.py',
'PYSOURCE'),
('pyi_rth_inspect',
'C:\\Users\\Pepitho\\AppData\\Roaming\\Python\\Python314\\site-packages\\PyInstaller\\hooks\\rthooks\\pyi_rth_inspect.py',
'PYSOURCE'),
('pyi_rth_usb',
'C:\\Users\\Pepitho\\AppData\\Roaming\\Python\\Python314\\site-packages\\_pyinstaller_hooks_contrib\\rthooks\\pyi_rth_usb.py',
'PYSOURCE'),
('pyi_rth_pkgutil',
'C:\\Users\\Pepitho\\AppData\\Roaming\\Python\\Python314\\site-packages\\PyInstaller\\hooks\\rthooks\\pyi_rth_pkgutil.py',
'PYSOURCE'),
('pyi_rth_multiprocessing',
'C:\\Users\\Pepitho\\AppData\\Roaming\\Python\\Python314\\site-packages\\PyInstaller\\hooks\\rthooks\\pyi_rth_multiprocessing.py',
'PYSOURCE'),
('pyi_rth_setuptools',
'C:\\Users\\Pepitho\\AppData\\Roaming\\Python\\Python314\\site-packages\\PyInstaller\\hooks\\rthooks\\pyi_rth_setuptools.py',
'PYSOURCE'),
('pyi_rth__tkinter',
'C:\\Users\\Pepitho\\AppData\\Roaming\\Python\\Python314\\site-packages\\PyInstaller\\hooks\\rthooks\\pyi_rth__tkinter.py',
'PYSOURCE'),
('gui_scanner',
'C:\\Users\\Pepitho\\Desktop\\hid-scanner\\gui_scanner.py',
'PYSOURCE')],
'python314.dll',
True,
False,
False,
[],
None,
None,
None)

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More