Saturday, May 30, 2026

Java: enumerate com serial using fazecast jSerialComm

Simple code to enumerate serial com port


/*
 * Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license
 */

package com.dedetok.enumcomport;

import com.fazecast.jSerialComm.SerialPort;

/**
 *
 * @author dedetok
 */
public class EnumComPort {

    public static void main(String[] args) {
        System.out.println("Hello World!");

        SerialPort[] ports = SerialPort.getCommPorts();

        if (ports.length == 0) {
            System.out.println("No serial ports detected.");
            return;
        }

        System.out.println("Available Serial Ports:");
        System.out.println("------------------------");

        for (SerialPort port : ports) {

            System.out.println("System Port Name : " + port.getSystemPortName());
            System.out.println("Descriptive Name: " + port.getDescriptivePortName());
            System.out.println("Port Description: " + port.getPortDescription());
            System.out.println("System Location : " + port.getSystemPortPath());
            System.out.println("------------------------");
        }
        
    }
}

Note: lpx device (e.g /dev/usb/lp1) does not use serial com, you need to send hex code direct via OutputStream.

Java: Hook shutdown to run java application as systemd

This is minimum java code to hook shutdown


/*
 * Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license
 */

package com.dedetok.javaservicetest;

/**
 *
 * @author dedetok
 */
public class JavaServiceTest {

    private static volatile boolean running = true;
        
    public static void main(String[] args) {
        System.out.println("Hello World!");
        
        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            System.out.println("Shutdown hook triggered");

            running = false;

            // Cleanup resources here
            // Close database connections
            // Flush logs
            // Stop worker threads
            // Main shutdown path
            //cleanupResources();

            System.out.println("Cleanup complete");
        }));

        System.out.println("Daemon started");

        while (running) {
            System.out.println("Working...");
            try {
                Thread.sleep(5000);
            } catch (InterruptedException ex) {
                System.getLogger(JavaServiceTest.class.getName()).log(System.Logger.Level.ERROR, (String) null, ex);
            }
        }

    }
}

to run/test this application, you can not run it inside netbeans. use terminal to run it, go to your project root and run


$ java -cp target/classes \
     com.dedetok.javaservicetest.JavaServiceTest
Hello World!
Daemon started
Working...
Working...
^CShutdown hook triggered
Cleanup complete

 

 

Java Netbeans: upgrade binary Netbeans 29 to Netbeans 30

 This step is straight forward:

  1. go to your root directory (e.g your home dir ~/), and rename old folder e.g ~/netbeans29.
  2. extract netbeans 30 binary and put it in root directory to replace old folder (e.g your home dir ~/).
  3. Run your netbeans 30, import previous configuration
  4. if no error found you safe to remove
    1. old netbeans 29 (e.g ~/netbeans29) 
    2. ~/.netbeans/29
    3. ~/.cache/netbeans/29

 

Monday, May 25, 2026

Java: maven escpos-coffee library, output to binary file

This is working demo how to create print preview and generate output.bin which is valid byte format to send to thermal printer.

Edit pom.xml and add escpos-coffee

<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
         https://maven.apache.org/xsd/maven-4.0.0.xsd">

    <modelVersion>4.0.0</modelVersion>

    <groupId>com.dedetok</groupId>
    <artifactId>myapp</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <build>
        <plugins>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.13.0</version>
            </plugin>

            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>exec-maven-plugin</artifactId>
                <version>3.6.3</version>
                <configuration>
                    <mainClass>com.dedetok.myapp.App</mainClass>
                </configuration>
            </plugin>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <version>3.4.2</version>
                <configuration>
                    <archive>
                        <manifest>
                            <mainClass>com.dedetok.myapp.App</mainClass>
                        </manifest>
                    </archive>
                </configuration>
            </plugin>

        </plugins>
    </build>

	<dependencies>

		<dependency>
			<groupId>com.github.anastaciocintra</groupId>
			<artifactId>escpos-coffee</artifactId>
			<version>4.1.0</version>
		</dependency>

	</dependencies>
</project>

run package or compile to download library

$ mvn compile

Edit App.java


package com.dedetok.myapp;

import com.github.anastaciocintra.escpos.EscPos;
import com.github.anastaciocintra.escpos.EscPosConst;
import com.github.anastaciocintra.escpos.Style;
import com.github.anastaciocintra.output.PrinterOutputStream;

import javax.swing.*;
import javax.swing.border.EmptyBorder;
import java.awt.*;
import java.io.FileOutputStream;
import java.io.IOException;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;

public class App extends JFrame {

    private JTextArea txtPreview;
    private DecimalFormat formatter;

    public App() {

        setTitle("ESC/POS Receipt Preview");
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setSize(420, 700);
        setLocationRelativeTo(null);

        JPanel root = new JPanel(new GridBagLayout());
        root.setBackground(new Color(180, 180, 180));

        JPanel paper = new JPanel(new BorderLayout());
        paper.setPreferredSize(new Dimension(300, 600));
        paper.setBackground(Color.WHITE);

        txtPreview = new JTextArea();

        txtPreview.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 15));
        txtPreview.setEditable(false);
        txtPreview.setLineWrap(false);

        txtPreview.setBackground(Color.WHITE);
        txtPreview.setForeground(Color.BLACK);

        txtPreview.setBorder(new EmptyBorder(12, 12, 12, 12));

        JScrollPane scroll = new JScrollPane(txtPreview);
        scroll.setBorder(null);

        paper.add(scroll, BorderLayout.CENTER);

        root.add(paper);

        add(root);

        generatePreview();

        // generate ESC/POS binary file
        try {
            generateEscPosFile();
            System.out.println("ESC/POS output saved to output.bin");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void generatePreview() {

        DecimalFormatSymbols symbols = new DecimalFormatSymbols();
        symbols.setGroupingSeparator('.');

        formatter = new DecimalFormat("#,###", symbols);

        StringBuilder sb = new StringBuilder();

        sb.append(center("MY COFFEE SHOP")).append("\n");
        sb.append(center("Bluetooth ESC/POS")).append("\n");
        sb.append(center("Jl. Ubud Bali")).append("\n");

        sb.append("--------------------------------\n");

        addItem(sb, "Espresso", 2, 18000);
        addItem(sb, "Cappuccino", 1, 25000);
        addItem(sb, "Croissant", 3, 12000);

        sb.append("--------------------------------\n");

        sb.append(String.format("%-20s %10s\n",
                "SUBTOTAL",
                formatter.format(97000)));

        sb.append(String.format("%-20s %10s\n",
                "TAX",
                formatter.format(9700)));

        sb.append("--------------------------------\n");

        sb.append(String.format("%-20s %10s\n",
                "TOTAL",
                formatter.format(106700)));

        sb.append("\n");

        sb.append(center("THANK YOU")).append("\n");
        sb.append(center("PLEASE COME AGAIN")).append("\n");

        sb.append("\n\n\n");

        txtPreview.setText(sb.toString());
    }

    private void generateEscPosFile() throws IOException {

        FileOutputStream fos = new FileOutputStream("output.bin");

        EscPos escpos = new EscPos(fos);

        Style titleStyle = new Style()
                .setJustification(EscPosConst.Justification.Center)
                .setBold(true)
                .setFontSize(Style.FontSize._2, Style.FontSize._2);

        Style centerStyle = new Style()
                .setJustification(EscPosConst.Justification.Center);

        Style normalStyle = new Style();

        escpos.writeLF(titleStyle, "MY COFFEE SHOP");
        escpos.writeLF(centerStyle, "Bluetooth ESC/POS");
        escpos.writeLF(centerStyle, "Jl. Ubud Bali");

        escpos.writeLF("--------------------------------");

        addItemEscPos(escpos, "Espresso", 2, 18000);
        addItemEscPos(escpos, "Cappuccino", 1, 25000);
        addItemEscPos(escpos, "Croissant", 3, 12000);

        escpos.writeLF("--------------------------------");

        escpos.writeLF(normalStyle,
                String.format("%-20s %10s",
                        "SUBTOTAL",
                        formatter.format(97000)));

        escpos.writeLF(normalStyle,
                String.format("%-20s %10s",
                        "TAX",
                        formatter.format(9700)));

        escpos.writeLF("--------------------------------");

        escpos.writeLF(normalStyle,
                String.format("%-20s %10s",
                        "TOTAL",
                        formatter.format(106700)));

        escpos.writeLF("");

        escpos.writeLF(centerStyle, "THANK YOU");
        escpos.writeLF(centerStyle, "PLEASE COME AGAIN");

        escpos.feed(4);
        escpos.cut(EscPos.CutMode.FULL);

        escpos.close();
        fos.close();
    }

    private void addItemEscPos(EscPos escpos,
                               String item,
                               int qty,
                               int price) throws IOException {

        int total = qty * price;

        escpos.writeLF(item);

        escpos.writeLF(
                String.format(
                        " %2dx %-14s %10s",
                        qty,
                        "",
                        formatter.format(total)
                )
        );
    }

    private void addItem(StringBuilder sb,
                         String item,
                         int qty,
                         int price) {

        int total = qty * price;

        sb.append(item).append("\n");

        sb.append(String.format(
                " %2dx %-14s %10s\n",
                qty,
                "",
                formatter.format(total)
        ));
    }

    private String center(String text) {

        int width = 32;

        if (text.length() >= width)
            return text;

        int leftPadding = (width - text.length()) / 2;

        return " ".repeat(leftPadding) + text;
    }

    public static void main(String[] args) {

        SwingUtilities.invokeLater(() -> {
            new App().setVisible(true);
        });
    }
}

Note: lpx device (e.g /dev/usb/lp1) does not use serial com, you need to send hex code direct via OutputStream 

 

Sunday, May 24, 2026

Java: using maven in Debian 13

Apache Maven is an open-source build automation and project management tool primarily used for Java applications. It simplifies the development process by managing a project's build, reporting, and documentation from a single piece of information.

To install maven:

# apt install maven
Installing:                     
  maven

Installing dependencies:
...

Showing version

$ mvn --version
Apache Maven 3.9.9
Maven home: /usr/share/maven
Java version: 21.0.11, vendor: Debian, runtime: /usr/lib/jvm/java-21-openjdk-amd64
Default locale: en_US, platform encoding: UTF-8
OS name: "linux", version: "6.12.88+deb13-amd64", arch: "amd64", family: "unix"

Go to your workspace, and run

$ mvn archetype:generate \
  -DgroupId=com.dedetok.myapp \
  -DartifactId=myapp \
  -DarchetypeArtifactId=maven-archetype-quickstart \
  -DarchetypeVersion=1.5 \
  -DinteractiveMode=false
...
$ ls myapp
pom.xml  src

DartifactId=myapp -> project root directory.

The main java code is ~/myapp/src/main/java/com/dedetok/myapp/App.java

package com.dedetok.myapp;

/**
 * Hello world!
 */
public class App {
    public static void main(String[] args) {
        System.out.println("Hello World!");
    }
}

To run the project using mvn, go to folder myapp, edit pom.xml


<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
         https://maven.apache.org/xsd/maven-4.0.0.xsd">

    <modelVersion>4.0.0</modelVersion>

    <groupId>com.dedetok</groupId>
    <artifactId>myapp</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <build>
        <plugins>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.13.0</version>
            </plugin>

            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>exec-maven-plugin</artifactId>
                <version>3.6.3</version>
                <configuration>
                    <mainClass>com.dedetok.myapp.App</mainClass>
                </configuration>
            </plugin>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <version>3.4.2</version>
                <configuration>
                    <archive>
                        <manifest>
                            <mainClass>com.dedetok.myapp.App</mainClass>
                        </manifest>
                    </archive>
                </configuration>
            </plugin>

        </plugins>
    </build>

</project>

then run:

$ mvn package

Run your application 


$ mvn exec:java -Dexec.mainClass="com.dedetok.myapp.App"
[INFO] Scanning for projects...
[INFO] 
[INFO] --------------------------< com.dedetok:myap >--------------------------
[INFO] Building myap 1.0-SNAPSHOT
[INFO]   from pom.xml
[INFO] --------------------------------[ jar ]---------------------------------
[INFO] 
[INFO] --- exec:3.6.3:java (default-cli) @ myap ---
Hello World!
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  8.265 s
[INFO] Finished at: 2026-05-24T14:59:08+07:00
[INFO] -----------------------------------------

or

$ mvn exec:java
[INFO] Scanning for projects...
[INFO] 
[INFO] -------------------------< com.dedetok:myapp >--------------------------
[INFO] Building myapp 1.0-SNAPSHOT
[INFO]   from pom.xml
[INFO] --------------------------------[ jar ]---------------------------------
[INFO] 
[INFO] --- exec:3.6.3:java (default-cli) @ myapp ---
Hello World!
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  4.712 s
[INFO] Finished at: 2026-05-25T14:43:48+07:00
[INFO] ------------------------------------------------------------------------

To use old school java, copy library needed

$ mvn dependency:copy-dependencies

run using java


$ mvn compile
$ java -cp "target/classes:target/dependency/*" com.dedetok.myapp.App
Hello World!

 

 

 

Tuesday, May 19, 2026

Debian 13: configurating Syris RD200-M1-G v01.55 Card Reader Part 2, detecting card reader in Debian

Add you as group of dialout (20)


# usermod -aG dialout myuser
# usermod -aG uucp myuser
# usermod -aG lock myuser
# usermod -aG tty myuser

Note: 

  1. All of these groups may not exist on every Linux distro. (Note, this process must only be done once for each user)
  2. if you use terminal only, this will effect directly, but if you use window Manager, you require to logout and re-login to make it effect especially on Netbeans, If aftar re-login fail, try restart.  

Confirm which com the reader assign


# ls -l /dev/ttyACM0
crw-rw---- 1 root dialout 166, 0 May 19 13:43 /dev/ttyACM0

The card reader assign com at /dev/ttyACM0

This is simple java code to read card when tap on device


import com.fazecast.jSerialComm.SerialPort;
import java.io.InputStream;
import java.io.OutputStream;

/**
 *
 * @author dedetok
 */
public class DedetokSerialCom {

    static private SerialPort serialPort;
    static private int boudRate = 115200;
            
    public static void main(String[] args) {
        System.out.println("Hello World!");
        
        serialPort = SerialPort.getCommPort("/dev/ttyACM0");
        
        // Configure serial port
        serialPort.setBaudRate(boudRate);
        serialPort.setNumDataBits(8);
        serialPort.setNumStopBits(SerialPort.ONE_STOP_BIT);
        serialPort.setParity(SerialPort.NO_PARITY);
        
        // Optional timeouts
        serialPort.setComPortTimeouts(
                SerialPort.TIMEOUT_READ_SEMI_BLOCKING,
                1000,
                0
        );
        
        
        // Open port
        if (!serialPort.openPort()) {
            System.out.println("Failed to open port");
            return;
        }
        
        System.out.println("Port opened"); // debug
                
        // ------------------read mifare id card ----------------------
        System.out.println("MIFARE"); // debug
        // STX LEN CMD
        byte[] cmd = new byte[] {
                (byte)0x02, // STX
                (byte)0x01, // LEN
                (byte)0x11  // CMD Read UID
        };
        
        // Send
        serialPort.writeBytes(cmd, cmd.length);

        System.out.println("command Send"); // debug
        
        /*
        02 02 11 10 : command error
        02 02 11 01 : no card
        02 06 11 00 xx xx xx xx : mifare id xx xx xx xx
        */

        byte[] rx = new byte[64];

        int n = serialPort.readBytes(rx, rx.length);

        System.out.println("Received bytes: " + n);

                // Print raw HEX
        for (int i = 0; i < n; i++) {
            System.out.printf("%02X ", rx[i]);
        }
        System.out.println();

        // ------------------read ISO15693 inventory card ----------------------
        // NOT SUPPORTED


        System.out.println();

        serialPort.closePort();

        
        System.out.println("Port Closed"); // debug
    }
}

This is sample output with uid masked Netbeans


Hello World!
Port opened
MIFARE
command Send
Received bytes: 8
02 06 xx xx xx xx xx 52 

What are they?

02 STX
06 LENGTH
Data 1-5 xx xx xx xx xx
Checksum 52 

e.g. to read Mifare UID (0x11)

command in bytes: 02 01 11

02: run command

01: length

11: command to read Mifare UID

10 checksum ( 01 xor 11 )

if return 4 bytes: MIFARE Classic 1K/4K

if return 7 bytes: NTAG / Ultralight / DESFire

if return 8 bytes: ISO15693

Example response 4 bytes UID (classic mifare)


02 06 01 xx xx xx xx 8E 
 |  |  |  |           |
 |  |  |  |           -> checksum
 |  |  |  -------------> data uid
 |  |  ----------------> status 
 |  -------------------> length data
 ----------------------> STX

Example response 6 bytes UID


02 0A 11 00 xx xx xx xx xx xx 80 00
 |  |  |  |                    |  |
 |  |  |  |                    |  -> SAK
 |  |  |  |                    ----> checksum
 |  |  |  -------------------------> data uid
 |  |  ----------------------------> status
 |  -------------------------------> length data
 ----------------------------------> STX

 

 

 

Debian 13: configurating Syris RD200-M1-G v01.55 Card Reader Part 1, firmware upgrade

By default Syris RD200-M1-G v01.55 run in keyboard mode. It will send to text when card detected. 

You need to upgrade the firmware for support some new card and change it to com mode. At com mode, you can communication with card to utilize it.

DO THIS IN WINDOWS TO UPDATE FIRMWARE:

All documentation link provided by syris is https://syris.com/ftp/index.php. Goto SYRIS_RFID_DVD -> RD200_300 -> USB_ReaderTools
_V0291.rar (All tools and docs), download and extract. 

Inside RD200_RD300_SDK_V0193 -> UtilityTools -> FirmwareFiles, there are 2 directory v1 and v2:

  • RD200(v1)
    • RD200_M1_COM_V0271_20230901.SYB
    • RD200_M1_V0271_20230901.SYB 
  • RD200(v2)
    • RD200-M1-V2_V0290_20250603.SYB
    • RD200-M1-V2_V0291_20251029_01.SYB
    • RD200-M1-V2-COM_V0283_20240216.SYB 

Run USB_ReaderTools_V0291_20251029.exe, go to tab firmware and use RD200_M1_COM_V0271_20230901.SYB to RD200-M1-G v01.55. If you choose wrong firmware, the tools will warn you "firmware type error". upgrade it, wait until finish.

After firmware upgraded, you need to change Auto Usb to Com, e.g. com3,(RD200-M1 0155) 12110056 -> (RD200-M1 0271) 12110056

Windows part is done.

This is Debian before upgrade and change to com mode


# dmesg
[  841.193326] usb 8-1: new full-speed USB device number 2 using xhci_hcd
[  841.349102] usb 8-1: New USB device found, idVendor=0e6a, idProduct=0317, bcdDevice= 1.40
[  841.349118] usb 8-1: New USB device strings: Mfr=1, Product=2, SerialNumber=3
[  841.349126] usb 8-1: Product: USB Reader
[  841.349133] usb 8-1: Manufacturer: SYRIS Technology Corp.
[  841.349139] usb 8-1: SerialNumber: 00000000
[  841.389585] input: SYRIS Technology Corp. USB Reader as /devices/pci0000:00/0000:00:08.3/0000:06:00.4/usb8/8-1/8-1:1.0/0003:0E6A:0317.0004/input/input23
[  841.505357] hid-generic 0003:0E6A:0317.0004: input,hidraw3: USB HID v1.10 Keyboard [SYRIS Technology Corp. USB Reader] on usb-0000:06:00.4-1/input0
[  841.522787] input: SYRIS Technology Corp. USB Reader Consumer Control as /devices/pci0000:00/0000:00:08.3/0000:06:00.4/usb8/8-1/8-1:1.1/0003:0E6A:0317.0005/input/input24
[  841.577455] input: SYRIS Technology Corp. USB Reader System Control as /devices/pci0000:00/0000:00:08.3/0000:06:00.4/usb8/8-1/8-1:1.1/0003:0E6A:0317.0005/input/input25
[  841.577717] hid-generic 0003:0E6A:0317.0005: input,hidraw4: USB HID v1.10 Device [SYRIS Technology Corp. USB Reader] on usb-0000:06:00.4-1/input1
# lsusb
...
Bus 008 Device 002: ID 0e6a:0317 Megawin Technology Co., Ltd USB Reader
...
# lsusb -v -d 0e6a:0317

Bus 008 Device 002: ID 0e6a:0317 Megawin Technology Co., Ltd USB Reader
Negotiated speed: Full Speed (12Mbps)
Device Descriptor:
  bLength                18
  bDescriptorType         1
  bcdUSB               1.10
  bDeviceClass            0 [unknown]
  bDeviceSubClass         0 [unknown]
  bDeviceProtocol         0 
  bMaxPacketSize0         8
  idVendor           0x0e6a Megawin Technology Co., Ltd
  idProduct          0x0317 USB Reader
  bcdDevice            1.40
  iManufacturer           1 SYRIS Technology Corp.
  iProduct                2 USB Reader
  iSerial                 3 00000000
  bNumConfigurations      1
  Configuration Descriptor:
    bLength                 9
    bDescriptorType         2
    wTotalLength       0x0042
    bNumInterfaces          2
    bConfigurationValue     1
    iConfiguration          0 
    bmAttributes         0xa0
      (Bus Powered)
      Remote Wakeup
    MaxPower              100mA
    Interface Descriptor:
      bLength                 9
      bDescriptorType         4
      bInterfaceNumber        0
      bAlternateSetting       0
      bNumEndpoints           1
      bInterfaceClass         3 Human Interface Device
      bInterfaceSubClass      1 Boot Interface Subclass
      bInterfaceProtocol      1 Keyboard
      iInterface              0 
        HID Device Descriptor:
          bLength                 9
          bDescriptorType        33
          bcdHID               1.10
          bCountryCode            0 Not supported
          bNumDescriptors         1
          bDescriptorType        34 (null)
          wDescriptorLength      73
          Report Descriptors: 
            ** UNAVAILABLE **
      Endpoint Descriptor:
        bLength                 7
        bDescriptorType         5
        bEndpointAddress     0x81  EP 1 IN
        bmAttributes            3
          Transfer Type            Interrupt
          Synch Type               None
          Usage Type               Data
        wMaxPacketSize     0x0008  1x 8 bytes
        bInterval              10
    Interface Descriptor:
      bLength                 9
      bDescriptorType         4
      bInterfaceNumber        1
      bAlternateSetting       0
      bNumEndpoints           2
      bInterfaceClass         3 Human Interface Device
      bInterfaceSubClass      0 [unknown]
      bInterfaceProtocol      0 
      iInterface              0 
        HID Device Descriptor:
          bLength                 9
          bDescriptorType        33
          bcdHID               1.10
          bCountryCode            0 Not supported
          bNumDescriptors         1
          bDescriptorType        34 (null)
          wDescriptorLength     861
          Report Descriptors: 
            ** UNAVAILABLE **
      Endpoint Descriptor:
        bLength                 7
        bDescriptorType         5
        bEndpointAddress     0x82  EP 2 IN
        bmAttributes            3
          Transfer Type            Interrupt
          Synch Type               None
          Usage Type               Data
        wMaxPacketSize     0x0040  1x 64 bytes
        bInterval               1
      Endpoint Descriptor:
        bLength                 7
        bDescriptorType         5
        bEndpointAddress     0x03  EP 3 OUT
        bmAttributes            3
          Transfer Type            Interrupt
          Synch Type               None
          Usage Type               Data
        wMaxPacketSize     0x0040  1x 64 bytes
        bInterval               1
Device Status:     0x0000
  (Bus Powered)

After firmware upgrade and com mode


# dmesg
[   65.250090] usb 8-1: New USB device strings: Mfr=1, Product=2, SerialNumber=0
[   65.250095] usb 8-1: Product: USB Reader
[   65.250098] usb 8-1: Manufacturer: SYRIS Technology Corp.
[   65.293079] cdc_acm 8-1:1.0: ttyACM0: USB ACM device
[   65.293108] usbcore: registered new interface driver cdc_acm
[   65.293110] cdc_acm: USB Abstract Control Model driver for USB modems and ISDN 
# lsusb
...
Bus 008 Device 002: ID 0e6a:0316 Megawin Technology Co., Ltd USB Reader
...
# lsusb -v -d 0e6a:0316
Bus 008 Device 002: ID 0e6a:0316 Megawin Technology Co., Ltd USB Reader
Negotiated speed: Full Speed (12Mbps)
Device Descriptor:
  bLength                18
  bDescriptorType         1
  bcdUSB               1.10
  bDeviceClass            2 Communications
  bDeviceSubClass         0 [unknown]
  bDeviceProtocol         0 
  bMaxPacketSize0         8
  idVendor           0x0e6a Megawin Technology Co., Ltd
  idProduct          0x0316 USB Reader
  bcdDevice            1.06
  iManufacturer           1 SYRIS Technology Corp.
  iProduct                2 USB Reader
  iSerial                 0 
  bNumConfigurations      1
  Configuration Descriptor:
    bLength                 9
    bDescriptorType         2
    wTotalLength       0x0043
    bNumInterfaces          2
    bConfigurationValue     1
    iConfiguration          0 
    bmAttributes         0x80
      (Bus Powered)
    MaxPower              100mA
    Interface Descriptor:
      bLength                 9
      bDescriptorType         4
      bInterfaceNumber        0
      bAlternateSetting       0
      bNumEndpoints           1
      bInterfaceClass         2 Communications
      bInterfaceSubClass      2 Abstract (modem)
      bInterfaceProtocol      1 AT-commands (v.25ter)
      iInterface              0 
      CDC Header:
        bcdCDC               1.10
      CDC Call Management:
        bmCapabilities       0x01
          call management
        bDataInterface          1
      CDC ACM:
        bmCapabilities       0x06
          sends break
          line coding and serial state
      CDC Union:
        bMasterInterface        0
        bSlaveInterface         1 
      Endpoint Descriptor:
        bLength                 7
        bDescriptorType         5
        bEndpointAddress     0x81  EP 1 IN
        bmAttributes            3
          Transfer Type            Interrupt
          Synch Type               None
          Usage Type               Data
        wMaxPacketSize     0x0040  1x 64 bytes
        bInterval              16
    Interface Descriptor:
      bLength                 9
      bDescriptorType         4
      bInterfaceNumber        1
      bAlternateSetting       0
      bNumEndpoints           2
      bInterfaceClass        10 CDC Data
      bInterfaceSubClass      0 [unknown]
      bInterfaceProtocol      0 
      iInterface              0 
      Endpoint Descriptor:
        bLength                 7
        bDescriptorType         5
        bEndpointAddress     0x82  EP 2 IN
        bmAttributes            2
          Transfer Type            Bulk
          Synch Type               None
          Usage Type               Data
        wMaxPacketSize     0x0040  1x 64 bytes
        bInterval               0
      Endpoint Descriptor:
        bLength                 7
        bDescriptorType         5
        bEndpointAddress     0x03  EP 3 OUT
        bmAttributes            2
          Transfer Type            Bulk
          Synch Type               None
          Usage Type               Data
        wMaxPacketSize     0x0040  1x 64 bytes
        bInterval               0
Device Status:     0x0000
  (Bus Powered)

 

 

Saturday, May 16, 2026

Android java: calculating grid column and grid row

Target emoji size 48 dp


        // ------------ EMOJI PICKER
        // target emoji size
        int finalWidth=xxx,finalHeight=yyy; // final value for IME View
        int orientationIdx = (cfg.orientation == Configuration.ORIENTATION_PORTRAIT) ? 0 : 1;
        float density = context.getResources().getDisplayMetrics().density; // 
        final float targetEmojiSize = 48; // dp
        //final float targetEmojiSize = (orientationIdx==0) ? 48f : 40f; // orientationIdx==0 -> portrait

        //final float targetEmojiWidthPx = targetEmojiSize * (mXdpi / 160f);
        //final float targetEmojiHeightPx = targetEmojiSize * (mYdpi / 160f);
        //emojiGridColumns = Math.max(1, (int) (finalWidth/targetEmojiWidthPx));
        //emojiGridRows = Math.max(1, finalHeight/targetEmojiHeightPx;
        float targetEmojiSizePx = targetEmojiSize*density;  // landscape too small
        //float targetEmojiSizePx = (orientationIdx==0) ?
        //          targetEmojiSize * (mYdpi / 160f) :
        //        targetEmojiSize * (mXdpi / 160f) ;

        emojiGridColumns = Math.max(3, (int) (finalWidth/targetEmojiSizePx));
        //emojiGridRows = Math.max(1f,
        //        ((finalHeight*0.8f)/targetEmojiSizePx)-1f); // we just use 0.8 of finalHeight
        emojiGridRows = (orientationIdx==0) ?
                Math.max(1f,
                ((finalHeight*0.8f)/targetEmojiSizePx)) :
                Math.max(1f,
                        ((finalHeight*0.8f)/targetEmojiSizePx)-1)
        ; // we just use 0.8 of finalHeight

        Log.e("dedetok", "targetEmojiSize "+targetEmojiSize+" targetEmojiSizePx "+targetEmojiSizePx); // debug
        Log.e("dedetok", "finalWidth "+finalWidth+" finalHeight "+finalHeight); // debug
        Log.e("dedetok", "emojiGridColumns "+emojiGridColumns+" emojiGridRows "+emojiGridRows); // debug

 Note, you can remove *0.8f, in this context, the emoji only occupied 0.8 of finalHeight, not full. -1 is magic forcing internal emoji picker to reduce row and recalculate the emoji size. 

Friday, May 15, 2026

Android java: calculation for ime wreda keyboard build 2026.09

Code


    // Emoji column and row
    int emojiGridColumns = 8; // store number of emoji
    float emojiGridRows = 3.5f; // store row in emoji
    // IME View size
    int finalWidth=0,finalHeight=0; // final value for IME View
    
    // -------------- set ime view size and emoji -----------------------
    // old version
    private void setIMEViewSizeAndEmojiOld() {
        // 1. Get the Calculator instance
        WindowMetricsCalculator calculator = WindowMetricsCalculator.getOrCreate();
        // 2. Compute current metrics for this specific Activity
        // This works on API 24 (Split Screen) up to API 37
        WindowMetrics metrics = calculator.computeCurrentWindowMetrics(myWredaKeyboard);

        // 3. Get the bounds (Rect)
        Rect bounds = metrics.getBounds();
        float density = myWredaKeyboard.getResources().getDisplayMetrics().density;

        int screenWidth = bounds.width();
        int screenHeight = bounds.height();
        boolean isWide = screenWidth > screenHeight;
        // Handle Navigation Bar Overlap (API 24+ Compatibility)
        // MANUAL INSET
        if (isWide) {
            // In landscape, navigation bars are usually on the left or right.
            // 48dp is the standard minimum size for system bars.
            int navBarWidth = (int) (48 * density);

            // Subtract space for both sides to be safe and centered
            screenWidth = screenWidth - (2 * navBarWidth);
        }

        float screenRatio = (float) screenWidth/screenHeight;
        float smallestWidthDp = Math.min(screenWidth, screenHeight) / density;

        int numberOfKeysColumn = 10;
        int numberOfKeysRow = 5;
        float keyWHRatioPortrait = 0.5f;// width : height = 1:2


        // --- ADAPTIVE ENGINE ---

        if (screenRatio > 0.85f && screenRatio < 1.2f) {
            // 📱 FOLDABLES / SQUARE DEVICES (Unfolded state)

            // 1. Keep it short. 25% is good because the screen is huge vertically.
            finalHeight = (int) (0.25f * screenHeight);

            // 2. Calculate the "Perfect Square" key size
            // If keys are square, width = height.
            int keyHeight = finalHeight / numberOfKeysRow;

            // 3. Set Width based on the Square Key
            // We multiply keyHeight by columns to get a keyboard that fits the keys perfectly.
            int idealWidth = keyHeight * numberOfKeysColumn;

            // 4. Center it
            // This ensures the keyboard doesn't stretch. It will sit in the middle
            // with empty space on the sides, making it reachable for thumbs.
            finalWidth = Math.min(screenWidth, idealWidth);

            // Debug for Foldable
            //Log.e("dedetok", "Foldable Mode active. Width constrained to: " + finalWidth); // debug

        } else if (isWide) {

            // 📺 LANDSCAPE (TV, Tablet, Wide Phone)
            // Goal: "Fat" Keys (Wide). You mentioned "landscape make it fat"
            // To make them fat (wider than tall), we DIVIDE by the 0.5 ratio

            if (smallestWidthDp >= 600) {
                // ️ TABLET LANDSCAPE: Keep it shorter so it doesn't cover the whole screen
                finalHeight = (int) (0.45f * screenHeight);
            } else {
                //  PHONE LANDSCAPE: Make it "bigger" to be usable
                finalHeight = (int) (0.55f * screenHeight);
            }
            // Re-calculate keyHeight AFTER setting finalHeight
            int keyHeight = finalHeight / numberOfKeysRow;
            int idealWidth = (int) (keyHeight / keyWHRatioPortrait * numberOfKeysColumn);
            //Log.e("dedetok", "screenWidth "+screenWidth+" idealWidth "+idealWidth);// debug

            // Use min to ensure it doesn't bleed off screen
            finalWidth = Math.min(screenWidth, idealWidth);

        } else {
            // phone
            if (smallestWidthDp >= 600) {
                // 🏗️ TABLET LANDSCAPE: Keep it shorter so it doesn't cover the whole screen
                finalHeight = (int) (0.30f * screenHeight);
                int keyHeight = finalHeight / numberOfKeysRow;
                int idealWidth = (int) (keyHeight / keyWHRatioPortrait * numberOfKeysColumn);

                // Use min to ensure it doesn't bleed off screen
                finalWidth = Math.min(screenWidth, idealWidth);
            } else {
                // 📱 PHONE PORTRAIT: Make it "bigger" to be usable
                finalHeight = (int) (0.35f * screenHeight);
                finalWidth = screenWidth;
            }

        }
        //Log.e("dedetok", "Width: "+finalWidth+" Height: "+finalHeight); // debug

        // ------------ now calculate emoji grid column and row
        int availableHeight = (int)(finalHeight * 0.8f);
        int emojiSize;
        if (screenRatio > 0.85f && screenRatio < 1.2f) {
            // Treat foldables like portrait tablets
            int targetRows = 4;
            int emojiSizeBox = (availableHeight / targetRows) + 6;
            emojiSize = Math.max(emojiSizeBox, 120); // slightly bigger for the big screen

            emojiGridColumns = finalWidth / emojiSize;
            emojiGridRows = (float) availableHeight / emojiSize;
        } else if (isWide && smallestWidthDp < 600) {
            // Force 9 columns to make emojis large enough to only fit ~2 rows
            emojiGridColumns = 9;
            emojiSize = finalWidth / emojiGridColumns;
            emojiGridRows = (float) availableHeight / emojiSize;
        } else {
            // Portrait: Standard 4 rows
            int targetRows = 4;
            int emojiSizeBox = (availableHeight / targetRows) + 6; // 6 space
            emojiSize = Math.max(emojiSizeBox, 100);

            emojiGridColumns = finalWidth / emojiSize;
            emojiGridRows = (float) availableHeight / emojiSize;
        }
    }    

Build 2026.09 

Friday, May 1, 2026

lamp debian 13: configure codeigniter 4


Minimum codeigniter 4.7.2: php 8.2

Target access http://localhost/app1 

Download zip codeiginiter 4 from official site and extract into folder root e.g. /home/[username]/ci4/app1.

Install required php php-mbstring

# apt install php-intl php-mbstring
# systemctl restart apache2

The following PHP extensions should be enabled on your server:

  • php-curl
  • php-json
  • php-mysqlnd/php-mysql
  • php-imagick php-gd
  • php-xml/php-simplexml

Default root folder

+-- app
+-- public
+-- tests
+-- spark
+-- system
+-- writable
|
+composer.json
+env
+LICENSE
+preload.php
+phpunit.xml.dist
+README.md

Change group and permission:


# usermod -a -G www-data [username]
# groups [username]
[username] : [username] cdrom floppy audio dip www-data video plugdev users netdev bluetooth lpadmin scanner libvirt
# chgrp www-data /home/[username] /home/[username]/ci4 /home/[username]/ci4/app1
# chmod g+x /home/[username] /home/[username]/ci4 /home/[username]/ci4/app1 

Edit /home/[username]/ci4/app1/app/Config/


<?php

namespace Config;

use CodeIgniter\Config\BaseConfig;

class App extends BaseConfig
{
    /**
     * --------------------------------------------------------------------------
     * Base Site URL
     * --------------------------------------------------------------------------
     *
     * URL to your CodeIgniter root. Typically, this will be your base URL,
     * WITH a trailing slash:
     *
     * E.g., http://example.com/
     */
    //public string $baseURL = 'http://localhost:8080/';
    public string $baseURL = 'http://localhost/app1/';


Option 1 Using subpath in user home folder

assume your root codeigniter is /home/[username]/ci4/app1

edit file /etc/apache2/sites-enabled/000-default.conf and restart


    <virtualhost>
	# The ServerName directive sets the request scheme, hostname and port that
	# the server uses to identify itself. This is used when creating
	# redirection URLs. In the context of virtual hosts, the ServerName
	# specifies what hostname must appear in the request's Host: header to
	# match this virtual host. For the default virtual host (this file) this
	# value is not decisive as it is used as a last resort host regardless.
	# However, you must set it for any further virtual host explicitly.
	#ServerName www.example.com

	ServerAdmin webmaster@localhost
	DocumentRoot /var/www/html

        # --- SUBPATH
        Alias /app1 /home/[username]/ci4/app1/public
        <directory app1="" ci4="" home="" public="" username="">
           Options Indexes FoolowSymLinks
           AllowOverride All
           Require all granted
        </directory>

~# apache2ctl configtest
AH00558: apache2: Could not reliably determine the server's fully qualified domain name, using 127.0.1.1. Set the 'ServerName' directive globally to suppress this message
Syntax OK
~# systemctl restart apache2

No configuration in this option.

Option 2 Using subpath in copy public folder to /var/www/html/ 

this is close to shared web hosting

create folder app1 inside /var/www/html and move all content inside [ci4_project_root]/public  into app1

Edit file index.php


/*
 *---------------------------------------------------------------
 * BOOTSTRAP THE APPLICATION
 *---------------------------------------------------------------
 * This process sets up the path constants, loads and registers
 * our autoloader, along with Composer's, loads our constants
 * and fires up an environment-specific bootstrapping.
 */

// LOAD OUR PATHS CONFIG FILE
// This is the line that might need to be changed, depending on your folder structure.
//require FCPATH . '../app/Config/Paths.php'; // NOT USED
// ^^^ Change this line if you move your application folder 
// USE ABOSOLUTE PATH
require '/home/[username]/ci4/app1/app/Config/Paths.php'; 

You may do this in web shared hosting. the deference is /var/www/html and /home/[username]/public_html (usually in cpanel).

NOTE: choose one of the options above, don't mix it.

Using Sub domain e.g. app1.example.com (not tested)

Subdomain Method (Best Practice)

This is the cleaner and more secure method as it keeps the entire framework structure intact.

Steps:

  1. Upload and extract your CodeIgniter zip into the folder you specified above e.g. /home/[username]/ci4/app1/.
  2. In cPanel/Plesk, create a Subdomain (e.g., app1.domain.com).
  3. Set its Document Root for sub domain to the public folder of your project (e.g., /home/[username]/ci4/app1/public) .
  4. No code changes are needed. Your setup is complete.

For development, you can find file name env. Edit it, set CI_ENVIRONMENT = development (default is production), save it as .env (with . in leading file name).

 

Debian 13: configure php apache environment for personal development

Add user to group of www-data (this is example for CodeIgniter 4 Framework)

# usermod -a -G www-data [username]
# groups [username]
[username] : [username] cdrom floppy audio dip www-data video plugdev users netdev bluetooth lpadmin scanner libvirt
# chgrp www-data /home/[username] /home/[username]/ci4 /home/[username]/ci4/app1
# chmod g+x /home/[username] /home/[username]/ci4 /home/[username]/ci4/app1

Folder structure for code igniter api

/home/[username]/ci4/app1
|
+-- app
+-- public
+-- tests
+-- spark
+-- system
+-- writable
|
+composer.json
+env
+LICENSE
+preload.php
+phpunit.xml.dist
+README.md    

Assume apache already installed, edit file /etc/apache2/sites-enabled/000-default.conf and add virtual folder e.g localhost/app1


    <virtualhost>
	# The ServerName directive sets the request scheme, hostname and port that
	# the server uses to identify itself. This is used when creating
	# redirection URLs. In the context of virtual hosts, the ServerName
	# specifies what hostname must appear in the request's Host: header to
	# match this virtual host. For the default virtual host (this file) this
	# value is not decisive as it is used as a last resort host regardless.
	# However, you must set it for any further virtual host explicitly.
	#ServerName www.example.com

	ServerAdmin webmaster@localhost
	DocumentRoot /var/www/html

        # --- SUBPATH
        Alias /app1 /home/[username]/ci4/app1/public
        <directory app1="" ci4="" home="" public="" username="">
           Options Indexes FoolowSymLinks
           AllowOverride All
           Require all granted
        </directory>

~# apache2ctl configtest
AH00558: apache2: Could not reliably determine the server's fully qualified domain name, using 127.0.1.1. Set the 'ServerName' directive globally to suppress this message
Syntax OK

Replace [username] with yours

Restart apache