Tôi sẽ mở đầu bài viết này từ vấn đề tôi đã gặp phải!
Trong dự án Dolphin Shopkit, mô hình dữ liệu bao gồm Collection (Bộ sưu tập) và
Product (Sản phẩm). Đây là mối quan hệ many-many, Một bộ sưu tập có nhiều sản phẩm, một sản phẩm
thuộc nhiều bộ sưu tập.
Nhu cầu của tôi là: thay đổi thứ tự hiển thị các sản phẩm này trong bộ sưu tập. Tâm sự một chút thì
đây là lần đầu tiên tôi làm tính năng kiểu như thế này! Và tôi tin là trong tương lai tôi sẽ còn gặp lại nó.
Bài post như là cách để tôi tra cứu trong tương lai khi cần thiết.
Để dễ hình dung, đây là database schema!
Product & Product Variant schema
Hãy chú ý cột product_display_order trong bảng collections_products.
Cách làm sẽ là như sau, tôi sẽ chỉ phương hướng, chút nữa sẽ là mã nguồn để tham khảo.
Phía UI sẽ có các thao tác drag-n-drop (kéo thả), client sẽ gửi lên server danh sách collection_product_id_list
với thứ tự mới.
Dưới database, sẽ chạy sql query cập nhật tất cả các collections_products entry record với giá trị cột product_display_order
mới.
collection_product_id
product_display_order(Cũ)
product_display_order(Mới)
1000
1
5
1001
2
4
1002
3
3
1003
4
2
1004
5
1
Keyword để tra cứu ở đây là ORDINALITY (Postgresql). Mục tiêu là từ danh sách collection_product_id với trật tự mới,
ta sẽ thêm cột dữ liệu tuần tự product_display_order.
Bạn thấy đó, kết quả là chỉ cần duy nhất một câu SQL là giải quyết được bài toán thay đổi thứ tự.
Hey, nhưng nếu bạn nghĩ rằng bài viết này vậy là xong thì chưa đâu nhé! Sẽ còn vài điều quan trọng nữa, ít nhất là cho
tôi - người dùng Phoenix Webframework, LiveView và SortableJS để thay đổi vị trí Product trong Collection
[1] Về phía frontend (HTML), đây là code sample, chú ý phx-hook
[4] Phía Ecto (Database Libary) khi chạy sql query
defreorder_collection_product_list(collection_product_id_list)domod_collection_product_id_list=collection_product_id_list|>Enum.map(&String.to_integer/1)query="""
UPDATE collections_products cp
SET product_display_order = x.product_display_order
FROM (SELECT * FROM unnest($1::bigint[])
WITH ORDINALITY AS t(collection_product_id, product_display_order)) as x
WHERE cp.id = x.collection_product_id;
"""Ecto.Adapters.SQL.query(Repo,query,[mod_collection_product_id_list])end
Credit: để giải quyết bài toán này, tôi đã dùng ChatGPT để hỗ trợ. Nếu không có ChatGPT, khả năng cao
là tôi sẽ mở database session, sau đó update từng dòng một, rồi commit session. Phương pháp sẽ
không thể nào gọn gàng như hiện tại nếu thiếu ORDINALITY.
Nhân tiện, từ khi tôi triển khai tính năng này đến thời điểm viết bài cũng cả tháng rồi, tuy nhiên, rất
khó nhớ. Tôi viết blog cũng vì lý do này, mỗi lần viết là lại thêm một lần nhớ, ORDINALITY này rất
thú vị, để quên thì thật đáng tiếc.
-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.
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.
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.
Tiếp theo, tìm giá trị tham chiếu zero point ở 0 voltage AC.
Cuối cùng là tìm hệ số chuyển đổi Scale.
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.
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>constintZMPT101B_PIN=A0;constfloatEXPECTED_VOLTAGE=233.0;constunsignedintzeroPoint=511;constintWINDOW_TIME=1000;unsignedlongsumOfSquareOfSampleDataMinusZeroPoint=0;unsignedlongsampleCounter=0;unsignedlongstartTime;floatrootMeanSquare=0.0f;voidsetup(){Serial.begin(115200);startTime=millis();}voidloop(){intsampleData=analogRead(ZMPT101B_PIN);sumOfSquareOfSampleDataMinusZeroPoint+=(sampleData-zeroPoint)*(sampleData-zeroPoint);sampleCounter+=1;if(millis()-startTime>=WINDOW_TIME){rootMeanSquare=sqrt((float)sumOfSquareOfSampleDataMinusZeroPoint/sampleCounter);floatscale=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ô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>voidsetup(){Wire.begin();Serial.begin(9600);delay(1000);Serial.println("Hexalink.xyz");Serial.println("Scanning...");}voidloop(){for(bytei=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.
$ 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.