Hexa's Blog

How to record video for K1 Max with ffmpeg?

08/05/2026 @ Saigon 3D Print

Step 1: Find K1 Max livestream url

  • stream: http://K1_MAX_IP:8080/?action=stream
  • snapshot: http://K1_MAX_IP:8080/?action=snapshot

For more details, go to http://K1_MAX_IP:8080

Step 2: Use ffmpeg command to take stream input (mjpg) and create video

I use encoder libvpx-vp9 to create

ffmpeg -i http://K1_MAX_IP:8080/?action=stream \
       -c:v libvpx-vp9 -vf fps=12 \
       -crf 30 -b:v 0 \
       -f segment \
       -segment_time 3600 \
       -reset_timestamps 1 \
       -strftime 1 \
       "%Y-%m-%dT%H%M%S.webm"
  • -c:v: Use video encoder named libvpx-vp9
  • fps=12: Use 12 frame per second for output video
  • -crf 30: Constant Rate Factor. It refers to video quality. 0 means the best quality, 40+ means super compressed, worse quality. (tbh, I only test Constant Rate Factor 30)
  • -b:v 0: Do not configure fixed video bitrate.
  • -f segment -segment_time 3600: For each one hour, create a video file.
  • -reset_timestamps 1: when create video, start timestamps at 1 (by default, it starts from the end timestamps of previous video)
  • -strftime 1 "%Y-%m-%dT%H%M%S.webm": Output with filename format.

Credit

ChatGPT helps me so much with ffmpeg, without it, I can’t understand all the ffmpeg options quickyly.

Kỹ thuật sử dụng cảm biến hiệu điện thế ZMPT101B

11/04/2026 @ Saigon Arduino

Bài viết này miêu tả những kỹ thuật và lưu ý mà tôi đã học được để sử dụng cảm biển ZMPT101B.

  1. Ngay khi mua cảm biến ZMPT101B, cần tinh chỉnh triết áp với Serial Plotter, xung không nên bị bè ở đầu, mất góc ở đỉnh.
  2. Tiếp theo, tìm giá trị tham chiếu zero point ở 0 voltage AC.
  3. Cuối cùng là tìm hệ số chuyển đổi Scale.
  4. Mỗi cảm biến lại có những sai số khác nhau, hoàn toàn không thể sử dụng hệ số chuyển đổi (SCALE)của cảm biến này áp dụng cho cảm biển khác, mặc dù là cùng một model.

1. Vặn triết áp và theo dõi trên Serial Plotter

Trước khi điều chỉnh, bắt buộc phải cắm dây điện xoay chiều vào cảm biến ZMPT101B. Bên cạnh đó, phải có phần mềm Arduino-IDE, mở công cụ Serial Plotter. Arduino-CLI không có tính năng này.

Việc điều chỉnh triết áp dựa trên các tiêu chí sau:

  • Giá trị / biên độ là lớn nhất khi xem trên Serial
  • Đỉnh của sóng không được phép bè ra, mất góc nhọn.
void setup() {
  Serial.begin(115200);
}

void loop() {
  Serial.println(analogRead(A0));
  delayMicroseconds(1000);
}
[1] Serial Plotter sau khi tinh chỉnh triết áp
[1] Serial Plotter sau khi tinh chỉnh triết áp

2. Tìm kiếm vị trí tham chiếu (Zero Point)

Định nghĩa vị trí tham chiếu (Zero Point) là vị trí mà ở đó không ghi nhận giá trị hiệu điện thế điện xoay chiều.

Để tim giá trị này, ta tháo toàn bộ dây AC cần đo ra khỏi cảm biến và chạy đoạn code sau. Ở phía bên tôi, tôi thu được giá trị là 511.

const int WINDOW_TIME = 1000;
const int ZMPT101B_PIN = A0;

unsigned long sumOfSampleData = 0;
unsigned long sampleCounter = 0;
unsigned long startTime;
void setup() {
  Serial.begin(115200);
  startTime = millis();
}

void loop() {

  int sampleData = analogRead(ZMPT101B_PIN);
  sumOfSampleData += sampleData;
  sampleCounter += 1;

  if(millis() - startTime >= WINDOW_TIME) {
    int sampleAvg = sumOfSampleData / sampleCounter;
    Serial.print("Zero Point: ");
    Serial.println(sampleAvg);
    sumOfSampleData = 0;
    sampleCounter = 0;
    startTime = millis();
  }
}
14:11:15.109 -> Zero Point: 511
14:11:16.106 -> Zero Point: 511
...
14:11:22.113 -> Zero Point: 511
14:11:23.109 -> Zero Point: 511

Bên cảm biến của tôi, kết quả là 511.

3. Tìm hệ số chuyển đổi scale

Ở đoạn này bắt buộc phải có đồng hồ chính xác bên ngoài, đo trước hiệu điện thế điện xoay chiều AC. Sau đó, gài giá trị vào EXPECTED_VOLTAGE ở đoạn code dưới đây và chạy. Khi chạy code, đảm bảo dây AC cần đo đã gắn vào cảm biến ZMPT101B chắc chắn.

Ở bên nhà tôi, hiệu điện thế đo được là 233V (Hoàn toàn không phải là 220V đâu nhé). Giá trị scale tôi thu được là 1.38

#include <math.h>

const int ZMPT101B_PIN       = A0;
const float EXPECTED_VOLTAGE = 233.0;
const unsigned int zeroPoint = 511;
const int WINDOW_TIME        = 1000;

unsigned long  sumOfSquareOfSampleDataMinusZeroPoint = 0;
unsigned long  sampleCounter                         = 0;
unsigned long  startTime;
float          rootMeanSquare = 0.0f;

void setup() {
  Serial.begin(115200);
  startTime = millis();
}

void loop() {
  int sampleData = analogRead(ZMPT101B_PIN);
  sumOfSquareOfSampleDataMinusZeroPoint += (sampleData - zeroPoint) * (sampleData - zeroPoint);
  sampleCounter += 1;

  if (millis() - startTime >= WINDOW_TIME) {
    rootMeanSquare = sqrt((float) sumOfSquareOfSampleDataMinusZeroPoint / sampleCounter);
    float scale = EXPECTED_VOLTAGE / rootMeanSquare;
    Serial.print("Root Mean Square: ");
    Serial.print(rootMeanSquare);
    Serial.print(" (in ");
    Serial.print(sampleCounter);
    Serial.print(" samples). Scale: ");
    Serial.println(scale);

    sumOfSquareOfSampleDataMinusZeroPoint = 0;
    sampleCounter = 0;
    startTime = millis();
  }
}
14:04:15.083 -> Voltage: 233.00 | Root Mean Square: 168.56 (in 17856 samples) | Scale: 1.38
14:04:17.108 -> Voltage: 233.00 | Root Mean Square: 168.53 (in 17855 samples) | Scale: 1.38
14:04:19.099 -> Voltage: 233.00 | Root Mean Square: 168.69 (in 17855 samples) | Scale: 1.38
14:04:21.088 -> Voltage: 233.00 | Root Mean Square: 168.73 (in 17855 samples) | Scale: 1.38

Phương pháp như sau:

  • Tính toán giá trị hiệu dụng (Root Mean Square - RMS) của giá trị sensor với giá trị tham chiếu (zero point) trong một khoảng thời gian (WINDOW_TIME)
  • Kết hợp với giá trị hiệu điện thế AC đo đạc từ một đồng hồ đo điện khác (EXPECTED_VOLTAGE)
  • Tồn tại sự tương quan giữa giá trị hiệu dụng của sensor và hiệu điện thế EXPECTED_VOLTAGE, tỷ lệ này ta gọi là SCALE(Hệ số chuyển đổi)
  • Tính toán tỷ lệ SCALE = EXPECTED_VOLTAGE / RMS

4. Tìm giá trị hiệu điện thế tức thời

Sau khi tìm được giá trị SCALE - hệ số chuyển đổi, ví dụ cảm biến cho ta giá trị là X, muốn tìm ra hiệu điện thế tức thời, ta làm như sau:

Vtức_thời = (X - ZeroPoint) * Scale

5. Lưu ý

  • Một khi đã tinh chỉnh xong, không được phép vặn triết áp. Nếu mà lỡ vặn, phải làm lại từ đầu.

Tìm kiếm I2C address cho board Arduino

09/04/2026 @ Saigon Arduino

Tôi không sử dụng Arduino-IDE mà sử dụng arduino-cli để tạo project ($ arduino-cli sketch new i2c_address_finder). Sau khi tạo xong project, hãy thay đổi nội dung của file .ino giống như dưới đây, trước khi copy-paste, hay để tôi giải thích về quy trình:

  • Thư viện Wire dành cho giao thức với I2C. Method Wire.begin() dùng để xác lập board arduino chúng ta đang sử dụng sẽ đóng vai trò là MAIN. Các linh kiện sensor, màn hình bên ngoài sẽ đóng vai trò là SUB.
  • Chúng ta muốn là arduino khi tìm được address I2C sẽ in ra trên màn hình monitor cho chúng ta xem. Giai đoạn này sẽ phụ thuộc vào Serial
    • Serial.begin(__baudrate__): Xác định truyền tín hiệu với giao thức Serial cùng baudrate. Trong code là 9600 (Liên quan đến bước )
    • Serial.print("__text__"): In text trên màn hình monitor
  • Địa chỉ I2C được miêu tả bởi 7 bit, có số lượng tối đa là 128 giá trị. Theo như tài liệu, trong số 128 giá trị khả dĩ, sẽ có các địa chỉ đặc biệt, không được sử dụng cho thiết bị SUB. Trong code dưới đây, chúng ta vẫn sẽ quét qua. Và sử dụng hai method sau:
    • Wire.beginTransmission(__byte_address__): Khởi tạo kết nối đến SUB với địa chỉ __byte_address__
    • Wire.endTransmission(): Đóng kết nối, nếu giá trị trả về bằng 0 thì nghĩa là đã kết nối và đóng thành công. Ta tìm được địa chỉ I2C hợp lệ.
    • Serial.println(i, HEX): In giá trị address ở dạng hexadecimal
#include <Wire.h>

void setup() {
  Wire.begin();
  Serial.begin(9600);
  delay(1000);
  Serial.println("Hexalink.xyz");
  Serial.println("Scanning...");
}

void loop() {
  for (byte i = 0; i < 128; i++) {
    Wire.beginTransmission(i);
    if (Wire.endTransmission() == 0) {
      Serial.print("Found at 0x");
      Serial.println(i, HEX);
    }
  }
  delay(2000);
}

Bước 1: Find board & port

$ arduino-cli board list

Port         Protocol Type              Board Name  FQBN            Core
/dev/ttyACM0 serial   Serial Port (USB) Arduino UNO arduino:avr:uno arduino:avr
  • Fully Qualified Board Name (FQBN): arduino:avr:uno
  • Port: /dev/ttyACM0

Bước 2: Compile

$ arduino-cli compile --fqbn arduino:avr:uno

Sketch uses 3704 bytes (11%) of program storage space. Maximum is 32256 bytes.
Global variables use 428 bytes (20%) of dynamic memory, leaving 1620 bytes for local variables. Maximum is 2048 bytes.

Bước 3: Upload

$ arduino-cli upload --fqbn arduino:avr:uno --port /dev/ttyACM0

New upload port: /dev/ttyACM0 (serial)

Bước 4: Monitor

$ arduino-cli monitor -p /dev/ttyACM0 --config  9600

Monitor port settings:
  baudrate=9600
  bits=8
  dtr=on
  parity=none
  rts=on
  stop_bits=1

Connecting to /dev/ttyACM0. Press CTRL-C to exit.
Scat 0x27
Scanning...
Found at 0x27
Found at 0x27

Xem trên monitor, địa chỉ I2C 0x27 đã tìm được.

Credit

Kiến thức này là tôi lấy được từ nhiều nguồn, bao gồm cả ChatGPT để hiểu I2C và Serial trong bài viết này. Cảm ơn ChatGPT.

Cách lấy lại unallocated space cho btrfs

28/03/2026 @ Saigon Linux

Bài này tôi viết ra sau khi gặp tình huống như sau:

  • Diskspace bị đầy 99% (kiểm tra với df -h -T)
  • Tôi đã tìm ra và muốn xóa các thư mục không cần thiết. Tuy nhiên, rm chạy rất lâu, treo máy
  • Từ bước này trở đi, tôi bắt đầu sử dụng ChatGPT để hỗ trợ.
  • Thư mục muốn xóa nằm trong file system btrfs. (check với command findmnt /PATH_TO_DELETE_DIR)
  • Kiểm tra tình trạng process rm với command cat /proc/pid/stack
[<0>] read_extent_buffer_pages+0x1de/0x220
[<0>] btrfs_read_extent_buffer+0x94/0x1c0
[<0>] read_tree_block+0x32/0x90
[<0>] read_block_for_search+0x247/0x360
[<0>] btrfs_search_slot+0x375/0x1050
[<0>] lookup_inline_extent_backref+0x174/0x810
[<0>] lookup_extent_backref+0x41/0xd0
[<0>] __btrfs_free_extent.isra.0+0x107/0x9f0
[<0>] __btrfs_run_delayed_refs+0x66b/0xfa0
[<0>] btrfs_run_delayed_refs+0x3b/0xd0
[<0>] flush_space+0x183/0x5b0                     <----
[<0>] priority_reclaim_metadata_space+0x94/0x150  <----
[<0>] __reserve_bytes+0x2a7/0x6e0
[<0>] btrfs_reserve_metadata_bytes+0x1d/0xc0      <----
[<0>] btrfs_block_rsv_refill+0x6b/0xa0
[<0>] evict_refill_and_join+0x4b/0xc0
[<0>] btrfs_evict_inode+0x30a/0x3c0
[<0>] evict+0xcd/0x1d0
[<0>] do_unlinkat+0x2de/0x330
[<0>] __x64_sys_unlinkat+0x56/0xc0
[<0>] do_syscall_64+0x82/0x160
[<0>] entry_SYSCALL_64_after_hwframe+0x76/0x7e

  • ChatGPT nghi ngờ rằng có áp lực lớn bên phía btrfs, do trên stack có các lệnh sau:
    • flush_space
    • priority_reclaim_metadata_space
    • btrfs_reserve_metadata_bytes
  • Từ điểm này, bắt đầu kiểm tra unallocated space của btrfs.

Thực sự là cảm ơn ChatGPT rất nhiều, những kiến thức liên quan đến btrfs tôi thực sự không biết.

1. Kiểm tra unallocated space

$ btrfs filesystem usage /

Overall:
    Device size:		 929.93GiB
    Device allocated:		 929.92GiB
    Device unallocated:		   1.00MiB    <----
    Device missing:		     0.00B
    Device slack:		     0.00B
    Used:			 877.81GiB
    Free (estimated):		  43.98GiB	(min: 43.98GiB) <----
    Free (statfs, df):		  43.98GiB
    Data ratio:			      1.00
    Metadata ratio:		      2.00
    Global reserve:		 512.00MiB	(used: 0.00B)
    Multiple profiles:		        no

Data,single: Size:883.91GiB, Used:839.92GiB (95.02%)
   /dev/nvme0n1p3	 883.91GiB

Metadata,DUP: Size:23.00GiB, Used:18.94GiB (82.36%)
   /dev/nvme0n1p3	  46.00GiB

System,DUP: Size:8.00MiB, Used:112.00KiB (1.37%)
   /dev/nvme0n1p3	  16.00MiB

Unallocated:
   /dev/nvme0n1p3	   1.00MiB    <----
┌[root@caheo-workstation-hocmon] [/dev/pts/3]
└[~]> btrfs filesystem df /
Data, single: total=883.91GiB, used=838.96GiB
System, DUP: total=8.00MiB, used=112.00KiB
Metadata, DUP: total=23.00GiB, used=18.92GiB
GlobalReserve, single: total=512.00MiB, used=0.00B

Lưu ý con số này nhé: Device unallocated: 1.00MiB. Tại vì không còn Device unallocated, tôi không thể chạy lệnh rm như bình thường được nữa (^,.,^)!

Còn một chỉ số rất quan trọng nữa đó là Free (estimated): 43.98GiB (min: 43.98GiB). Nó có nghĩa là dung lượng còn trống khả dĩ.

Bạn thấy sự chênh lệch giữa Device unallocatedFree (estimated) chứ. Nó cần cân bằng, xấp xỉ bằng nhau hoặc ít nhất là đừng có lệch quá.

2. Cân bằng (balance) lại btrfs

Lưu ý siêu quan trọng, để việc cân bằng tốt hơn, cực kỳ hạn chế các tác vụ đọc/ghi dữ liệu.

2.1 Start

$ btrfs balance start -dusage=75 ‌/your_mount_path

2.2 Pause - Tạm dừng

Btrfs cho phép dừng tạm thời quá trình cân bằng, reboot rồi quay lại làm tiếp cũng được.

$ btrfs balance pause /your_mount_path

2.3 Resume - Làm tiếp

Nếu mà đang cân bằng, và bạn reboot. Sau khi reboot, btrfs sẽ tự động quay lại quá trình cân bằng.

$ btrfs balance resume /your_mount_path

2.4 Cancel - Hủy bỏ

$ btrfs balance cancel /your_mount_path

2.5 Status - Xem trạng thái

$ btrfs balance status /your_mount_path

3. Kết quả

Sau quá trình cân bằng‌/balance, tôii có thử rm với thư mục lúc trước bị treo, hoàn toàn không có trục trặc.

Đây là trạng thái của btrfs sau khi balance

$ btrfs filesystem usage /

Overall:
    Device size:		 929.93GiB
    Device allocated:		 891.92GiB
    Device unallocated:		  38.00GiB
    Device missing:		     0.00B
    Device slack:		     0.00B
    Used:			 829.75GiB
    Free (estimated):		  77.77GiB	(min: 58.77GiB)
    Free (statfs, df):		  77.77GiB
    Data ratio:			      1.00
    Metadata ratio:		      2.00
    Global reserve:		 512.00MiB	(used: 0.00B)
    Multiple profiles:		        no

Data,single: Size:833.91GiB, Used:794.14GiB (95.23%)
   /dev/nvme0n1p3	 833.91GiB

Metadata,DUP: Size:29.00GiB, Used:17.80GiB (61.40%)
   /dev/nvme0n1p3	  58.00GiB

System,DUP: Size:8.00MiB, Used:128.00KiB (1.56%)
   /dev/nvme0n1p3	  16.00MiB

Unallocated:
   /dev/nvme0n1p3	  38.00GiB

4. Credit

Thực sự phải cảm ơn ChatGPT. Đây là lần đầu tiên tôi gặp phải tình huống phải balance btrfs.

Arduino UNO pinout diagram

25/03/2026 @ Saigon Arduino

References

Cách kiểm tra disk space trên linux

24/03/2026 @ Saigon Linux

1. Kiểm tra tình trạng mount point - df -h

$ df -Th

Filesystem      Type    Size  Used Avail Use% Mounted on
/dev/nvme0n1p3  btrfs   930G  919G  4.5G 100% /
devtmpfs                4.0M     0  4.0M   0% /dev
tmpfs                   16G  1.1M   16G   1% /dev/shm
...
/dev/nvme0n1p3  btrfs   930G  919G  4.5G 100% /home

Dựa vào cột Use%, ta dễ dàng thấy được đâu là mount point cần quan tâm:

  • /
  • /home

2. Kiểm tra tình trạng sử dụng chi tiết trong mount mount - du -h

  • Thay / bằng path khác, mặc định là directory hiện tại.
  • Xem tổng dung lượng của từng directory, chỉ lấy 1 level (max-depth=1)
  • Sắp xếp lại từ cao xuống thấp (sort -hr)
$ du -h --max-depth=1 / | sort -hr

Từng bước một, đi vào các thư mục sâu hơn, bạn hoàn toàn có thể giải phỏng bớt disk space. Chúc may mắn.

Drillhole map for motherboard m-ITX(repost)

23/03/2026 @ Saigon etc

1. Drillhole map

2. References

Drillhole map for motherboard mATX(repost)

23/03/2026 @ Saigon etc

1. Drillhole map

2. References

Drillhole map for motherboard ATX (repost)

23/03/2026 @ Saigon etc

1. Drillhole map

2. References

Hệ thống quản lý di sản

19/03/2026 @ Saigon Projects

Hôm nay là 18/3/2026, tôi vừa update toàn bộ dependencies lên version mới nhất. Dự án chính thức chuyển sang trạng thái bảo trì.

Với các dự án tôi làm, tôi luôn cố gắng update library định kỳ (khoảng mỗi tháng một lần). Tin tôi đi, không ai muốn bảo trì một codebase cũ kỹ, lâu không update.

Ví dụ đơn giản: năm 2026 mà phải maintain Phoenix 1.0.0 trong khi hiện tại là 1.8.x — cảm giác không dễ chịu chút nào đâu.


Quay lại dự án này, nó sinh ra nhằm giải quyết các câu hỏi sau:

  • Làm sau để biết món hàng X đang ở đâu trong 30,000m2, 20 khu trưng bày?
  • Lịch sử giá bán, số lượng ra sao, ai là người thay đổi?
  • Mỗi khách hàng lại cho giá khác nhau, làm sao để điều chỉnh giá cho từng người?
  • Từ khi hàng trong kho cho đến tay khách hàng, trải qua những bước gì, ai giảm sát?
  • Khách hàng không hài lòng, muốn trả hàng, quy trình ra sao, ai giảm sát?
  • Trong số hàng hóa trả lại, có bao nhiêu cái có thể đưa lên kệ và bán tiếp?
  • Hàng hóa bao nhiêu lâu rồi chưa có người đến kiểm tra, đối soát?

Dự án này tôi làm một mình từ giai đoạn phân tích nghiệp vụ, viết phần mềm, triển khai.

Tôi đặc biệt không thích lạm dụng cloud, hay thêm nhiều technical stacks. Mỗi technical stack lại làm cho người bảo trì đến sau mệt mỏi hơn một chút. Bên cạnh đó, phần cứng hiện tại đã quá rẻ, lượng data cũng không lớn đến mức mà cần phải tối ưu hóa database, khách hàng cũng không cần hệ thống hoạt động 24/7 uptime 99%.

Càng đơn giản thì càng tốt. Tôi muốn như vậy.

Từ commit đầu tiên ngày 4/7/2025, đến hôm nay là 18/3/2026:

  • Khoảng 9 tháng phát triển phần mềm
  • Chính xác 777 commits - số đẹp 😇

Nhìn chung, tôi rất hài lòng với kết quả của dự án này, nó thực sự mang lại giá trị cho người dùng.

Hey, quên mất một cái rất quan trọng là tôi không chỉ viết phần mềm, tôi còn đến tận nơi, thu thập và nhập dữ liệu vào hệ thống. Bằng cách thực làm và quan sát, tôi hiểu nỗi đau và tối ưu hóa nghiệp vụ tốt hơn ai hết.


Có ba thứ mà tôi cực kỳ tâm đắc khi làm dự án này.

1. Luôn luôn cho phép con người sửa lỗi cho dù họ có làm điều ngu ngốc như thế nào chăng nữa

Việc lưu lại lịch sử thay đổi, có giá trị mới giúp người dùng sửa sai nếu có nhầm lẫn. Tuyên ngôn là sai thì sửa, chửa thì đẻ.

Ngoại trừ việc sinh tử, tất cả thứ khác là chuyện nhỏ.

2. Không phải lúc nào cũng cần message queue.

Nếu dữ liệu chỉ được update bởi một hành động tại một thời điểm, không cần thêm queue.

Thay vào đó, mình dùng một cơ chế đơn giản gọi là Totem(tín vật):

  • Muốn update đơn hàng -> phải có Totem
  • Không có -> không được update
  • Update xong -> trả lại Totem
  • Nếu lỗi -> hệ thống tự thu hồi sau 15s

Nó giống như tín hiệu đường sắt: Chỉ một tàu được chiếm dụng đường ray tại một thời điểm.

Với hệ thống nhỏ, cách này đủ dùng, đơn giản.

❌ Tôi đã từng debug system sử dụng Kafka rồi, cảm ơn, quá đủ rồi ❌

3‌. Không nên lạm dụng AI trong quá trình nhập dữ liệu.

Dữ liệu đúng ngay từ đầu tốt hơn là phải xử lý hậu kỳ. Khi dữ liệu đến tay mình đã đúng, hệ thống phía sau nhẹ đi rất nhiều.

Tôi sẽ nói sơ qua một chút về khó khăn

Mỗi sản phẩm đều cần:

  • Dán tem (QR code + mã định danh)
  • Chụp ảnh
  • Nhập dữ liệu

Yêu cầu phải quét QR Code làm tăng đáng kể thời gian nhập liệu.


Giải pháp sau nhiều lần tối ưu đó là:

  • Phát triển app Android khác có tên là Batch Shot, nhằm mục tiêu chụp ảnh sản phẩm và chia ảnh vào folder riêng.
  • Trong quá trình chụp ảnh, quét luôn mã QR Code, bỏ vào file data.json trong từng folder. Khai thác tối đa dữ liệu lúc chụp ảnh, không đợi phải xử lý hậu kỳ cho QR Code.
  • Sau một ngày chụp ảnh sản phẩm, zip toàn bộ thư mục ảnh, gửi lên server.
  • Server sau đó giải nén, có giao diện để nhập dữ liệu
  • Các field có tính lặp lại đều có cache, không cần chọn lại khi nhập dữ liệu mới.

Kết quả là tôi giảm cực kỳ nhiều số thao tác khi nhập dữ liệu, tốc độ tăng nhanh đáng kể.

Viết đến đây cũng đã rất dài rồi, tôi sẽ share một vài hình ảnh không quá riêng tư. 😇

[1] Phòng trưng bày tư nhân
[1] Phòng trưng bày tư nhân
[2] Danh sách di sản
[2] Danh sách di sản
[3] Chi tiết di sản
[3] Chi tiết di sản
[4] Lịch sử thay đổi
[4] Lịch sử thay đổi