Skip to content

Conversation

@MartinJM
Copy link

This PR adds support for the Korad KA3005PS. Protocol is almost the same as for the KA3005P, except that it needs a newline1 at the end of every request, and sends back a newline at the end of every response. That is added in this PR.

One thing to keep in mind: This PR also sends the newline for the *IDN? to identify the device. I assume this will also work for the KA3005P, but I cannot check.

Footnotes

  1. It also seems to work with a carriage return, but the responses will still end with a newline. I've implemented it with newlines.

@cnf
Copy link

cnf commented May 25, 2025

I think this is the same as the velleman LABPS3005DN needs i'll try and check somewhere this week on mine

@cnf
Copy link

cnf commented May 26, 2025

That doesn't work.
I don't know if this is the right place. but attached is the .patch file i got it to detect my Velleman with, but it doesn't operate well.

LABPS3005DN.txt
it seems the LABPS3005DN expects an actual '\n' as a string.

@MartinJM
Copy link
Author

I'm not sure where you applied that patch? For this PR you'd need to add the KORAD_QUIRK_NEWLINE for your model to enable the newline sending/stripping.

I also see a change of OUT[01] to OUTPUT[01], which would break on other models.

@cnf
Copy link

cnf commented May 26, 2025

I'm not sure where you applied that patch? For this PR you'd need to add the KORAD_QUIRK_NEWLINE for your model to enable the newline sending/stripping.

I also see a change of OUT[01] to OUTPUT[01], which would break on other models.

Right, I took your patch, and added

+	{"Velleman", "LABPS3005DN", "QJE3005PV1.0", 1, volts_30, amps_5,
+        KORAD_QUIRK_ID_OPT_VERSION|KORAD_QUIRK_NEWLINE},

which did not detect my velleman.

My patch I uploaded lets me detect my velleman, but does not get me to a functioning state.

https://cdn.velleman.eu/downloads/80/labps3005dn_communication_protocol.pdf is the doc for my velleman btw.

I was just hopeful your patch would work for my device, because you mentioned it needed the newlines, I'm sorry if I am highjacking the thread...

@MartinJM MartinJM force-pushed the add_device_korad_ka3005ps branch from cf8934c to c79a973 Compare May 26, 2025 18:29
@MartinJM
Copy link
Author

MartinJM commented May 26, 2025

It does look fairly close - the only real differences seem to be the OUTPUT request and the STATUS response.

I was just hopeful your patch would work for my device, because you mentioned it needed the newlines, I'm sorry if I am highjacking the thread...

No worries! I have changed the code a bit to see if it will allow your device to connect properly as well. If it doesn't take too much work it'd be nice to add support for it as well!

You'll need to apply something like the following patch for your device though:

diff --git a/src/hardware/korad-kaxxxxp/api.c b/src/hardware/korad-kaxxxxp/api.c
index c6b9b1f6..583252a3 100644
--- a/src/hardware/korad-kaxxxxp/api.c
+++ b/src/hardware/korad-kaxxxxp/api.c
@@ -76,6 +76,8 @@ static const struct korad_kaxxxxp_model models[] = {
        {"Tenma", "72-2710", "", 1, volts_30, amps_5, 0},
        {"Velleman", "LABPS3005D", "", 1, volts_30, amps_5,
                KORAD_QUIRK_LABPS_OVP_EN},
+       {"Velleman", "LABPS3005DN", "", 1, volts_30, amps_5,
+               KORAD_QUIRK_VELLEMAN | KORAD_QUIRK_NEWLINE},
        {"Velleman", "PS3005D V1.3", "VELLEMANPS3005DV1.3" , 1, volts_30, amps_5,
                KORAD_QUIRK_ID_TRAILING | KORAD_QUIRK_SLOW_PROCESSING},
        {"Velleman", "PS3005D", "", 1, volts_30, amps_5, 0},
diff --git a/src/hardware/korad-kaxxxxp/protocol.c b/src/hardware/korad-kaxxxxp/protocol.c
index 0ce7206e..b27d8f9c 100644
--- a/src/hardware/korad-kaxxxxp/protocol.c
+++ b/src/hardware/korad-kaxxxxp/protocol.c
@@ -229,8 +229,13 @@ SR_PRIV int korad_kaxxxxp_set_value(struct sr_serial_dev_inst *serial,
                        "VSET1:%05.2f", devc->set_voltage_target);
                break;
        case KAXXXXP_OUTPUT:
-               sr_snprintf_ascii(msg, sizeof(msg),
-                       "OUT%1d", (devc->set_output_enabled) ? 1 : 0);
+               if (devc->model->quirks & KORAD_QUIRK_VELLEMAN) {
+                       sr_snprintf_ascii(msg, sizeof(msg),
+                               "OUTPUT%1d", (devc->set_output_enabled) ? 1 : 0);
+               } else {
+                       sr_snprintf_ascii(msg, sizeof(msg),
+                               "OUT%1d", (devc->set_output_enabled) ? 1 : 0);
+               }
                /* Set value back to recognize changes */
                devc->output_enabled = devc->set_output_enabled;
                break;
diff --git a/src/hardware/korad-kaxxxxp/protocol.h b/src/hardware/korad-kaxxxxp/protocol.h
index 43653d14..19aef882 100644
--- a/src/hardware/korad-kaxxxxp/protocol.h
+++ b/src/hardware/korad-kaxxxxp/protocol.h
@@ -40,7 +40,8 @@ enum korad_quirks_flag {
        KORAD_QUIRK_ID_OPT_VERSION = 1UL << 3,
        KORAD_QUIRK_SLOW_PROCESSING = 1UL << 4,
        KORAD_QUIRK_NEWLINE = 1UL << 5,
-       KORAD_QUIRK_ALL = (1UL << 6) - 1,
+       KORAD_QUIRK_VELLEMAN = 1UL << 6,
+       KORAD_QUIRK_ALL = (1UL << 7) - 1,
 };
 
 /* Information on single model */

The STATUS response is probably not parsed properly though.

@cnf
Copy link

cnf commented May 27, 2025

Im probably doing something weird, but I am faling at applying your patch for my velleman ^^;

error: builder for '/nix/store/vxbwd5bik9h9vir6s196v8yp25h27bnb-libsigrok-0.5.2-unstable-2024-10-20.drv' failed with exit code 1;
        last 14 log lines:
        > Running phase: unpackPhase
        > unpacking source archive /nix/store/xcxgsps2nbp1ny2ksw0gpcs6171hqycq-libsigrok-c79a973
        > source root is libsigrok-c79a973
        > Running phase: patchPhase
        > applying patch /nix/store/vfl9vlhwsm52yxd3pripz6g9ic7fcn8y-VELLEMAN.patch
        > patching file src/hardware/korad-kaxxxxp/api.c
        > Hunk #1 FAILED at 76.
        > 1 out of 1 hunk FAILED -- saving rejects to file src/hardware/korad-kaxxxxp/api.c.rej
        > patching file src/hardware/korad-kaxxxxp/protocol.c
        > Hunk #1 FAILED at 229.
        > 1 out of 1 hunk FAILED -- saving rejects to file src/hardware/korad-kaxxxxp/protocol.c.rej
        > patching file src/hardware/korad-kaxxxxp/protocol.h
        > Hunk #1 FAILED at 40.
        > 1 out of 1 hunk FAILED -- saving rejects to file src/hardware/korad-kaxxxxp/protocol.h.rej
        For full logs, run 'nix log /nix/store/vxbwd5bik9h9vir6s196v8yp25h27bnb-libsigrok-0.5.2-unstable-2024-10-20.drv'.

I'll have another poke later on, my brain is a bit foggy atm i'm afraid.

@MartinJM
Copy link
Author

You'll need to apply it on top of this PR. I think you tried to apply it on master right?

I also forgot to add the newline quirk for your device - just edited it in, so you might need to grab it again.

@cnf
Copy link

cnf commented May 27, 2025

i first tried master, then this PR as a patch, then the other one.
then i tried the hash from this PR, and the new patch, both gave that error

final: prev: 
{
  libsigrok = prev.libsigrok.overrideAttrs (prevAttrs: {
    buildInputs = (prevAttrs.buildInputs or []) ++ [prev.nettle];
      src = prev.fetchgit {
        #url = "git://sigrok.org/libsigrok";
        url = "https://github.com/sigrokproject/libsigrok/";
        rev = "c79a9738f8643fe84998d95d0578ec1035235471";
        hash = "sha256-Ogg7/8ZIqKejyp9HWLRa6DMWM7nxGgVMew+EbaNxCKo=";
      };
    patches = (prevAttrs.patches or []) ++ [
      #../patches/269.patch
      ../patches/VELLEMAN.patch
    ];
  });
}

I don;t know if you are familiar with nix, but that is the last one i tried.

@MartinJM
Copy link
Author

Ah I think something went wrong with my patch. I recreated it and double-checked this one, so it should work:

diff --git a/src/hardware/korad-kaxxxxp/api.c b/src/hardware/korad-kaxxxxp/api.c
index c6b9b1f6..3967a0fb 100644
--- a/src/hardware/korad-kaxxxxp/api.c
+++ b/src/hardware/korad-kaxxxxp/api.c
@@ -76,6 +76,8 @@ static const struct korad_kaxxxxp_model models[] = {
 	{"Tenma", "72-2710", "", 1, volts_30, amps_5, 0},
 	{"Velleman", "LABPS3005D", "", 1, volts_30, amps_5,
 		KORAD_QUIRK_LABPS_OVP_EN},
+	{"Velleman", "LABPS3005DN", "", 1, volts_30, amps_5,
+		KORAD_QUIRK_NEWLINE | KORAD_QUIRK_VELLEMAN},
 	{"Velleman", "PS3005D V1.3", "VELLEMANPS3005DV1.3" , 1, volts_30, amps_5,
 		KORAD_QUIRK_ID_TRAILING | KORAD_QUIRK_SLOW_PROCESSING},
 	{"Velleman", "PS3005D", "", 1, volts_30, amps_5, 0},
diff --git a/src/hardware/korad-kaxxxxp/protocol.c b/src/hardware/korad-kaxxxxp/protocol.c
index 0ce7206e..b27d8f9c 100644
--- a/src/hardware/korad-kaxxxxp/protocol.c
+++ b/src/hardware/korad-kaxxxxp/protocol.c
@@ -229,8 +229,13 @@ SR_PRIV int korad_kaxxxxp_set_value(struct sr_serial_dev_inst *serial,
 			"VSET1:%05.2f", devc->set_voltage_target);
 		break;
 	case KAXXXXP_OUTPUT:
-		sr_snprintf_ascii(msg, sizeof(msg),
-			"OUT%1d", (devc->set_output_enabled) ? 1 : 0);
+		if (devc->model->quirks & KORAD_QUIRK_VELLEMAN) {
+			sr_snprintf_ascii(msg, sizeof(msg),
+				"OUTPUT%1d", (devc->set_output_enabled) ? 1 : 0);
+		} else {
+			sr_snprintf_ascii(msg, sizeof(msg),
+				"OUT%1d", (devc->set_output_enabled) ? 1 : 0);
+		}
 		/* Set value back to recognize changes */
 		devc->output_enabled = devc->set_output_enabled;
 		break;
diff --git a/src/hardware/korad-kaxxxxp/protocol.h b/src/hardware/korad-kaxxxxp/protocol.h
index 43653d14..19aef882 100644
--- a/src/hardware/korad-kaxxxxp/protocol.h
+++ b/src/hardware/korad-kaxxxxp/protocol.h
@@ -40,7 +40,8 @@ enum korad_quirks_flag {
 	KORAD_QUIRK_ID_OPT_VERSION = 1UL << 3,
 	KORAD_QUIRK_SLOW_PROCESSING = 1UL << 4,
 	KORAD_QUIRK_NEWLINE = 1UL << 5,
-	KORAD_QUIRK_ALL = (1UL << 6) - 1,
+	KORAD_QUIRK_VELLEMAN = 1UL << 6,
+	KORAD_QUIRK_ALL = (1UL << 7) - 1,
 };
 
 /* Information on single model */

@cnf
Copy link

cnf commented May 27, 2025

hmm, not sure what I am doing wrong:

sr: [00:00.014127] hwdriver: sr_config_list(): key 2147418112 (NULL) sdi (nil) cg NULL -> [uint32 20000, 20001, 20003]
sr: [00:00.014149] serial: Opening serial port '/dev/ttyUSB0' (flags 1).
sr: [00:00.015854] serial: Parsing parameters from "9600/8n1".
sr: [00:00.015946] serial: Got params: rate 9600, frame 8/0/1, flow 0, rts -1, dtr -1.
sr: [00:00.015962] serial: Setting serial parameters on port /dev/ttyUSB0.
sr: [00:00.016191] serial: DBG: serial_set_params() rate 9600, 8n1
sr: [00:00.016205] serial: Flushing serial port /dev/ttyUSB0.
sr: [00:00.016227] korad-kaxxxxp: Want max 48 bytes.
sr: [00:00.016245] korad-kaxxxxp: Sending '*IDN?'.
sr: [00:00.016283] serial: Wrote 6/6 bytes.
sr: [00:00.016651] korad-kaxxxxp: want 48 bytes, timeout/retry: init 60/100, later 13/1.
sr: [00:06.030752] korad-kaxxxxp: got 0 bytes, received: ''.
sr: [00:06.030806] korad-kaxxxxp: Received: 0, 
sr: [00:06.030845] korad-kaxxxxp: Unsupported model ID '', aborting.
sr: [00:06.030882] hwdriver: Scan found 0 devices (korad-kaxxxxp).
No devices found.

@cnf
Copy link

cnf commented May 27, 2025

little bit more info:

± sigrok-cli -d korad-kaxxxxp:conn=/dev/ttyUSB0:force_detect=QJE3005PV1.0 -l 5 --show
sr: [00:00.000060] log: libsigrok loglevel set to 5.
sr: [00:00.000099] backend: libsigrok 0.6.0/4:0:0.
sr: [00:00.000261] backend: Libs: glib 2.82.5 (rt: 2.82.5/8205:5), zlib 1.3.1, libzip 1.11.3, minilzo 2.10, libserialport 0.1.1/1:0:1 (rt: 0.1.1/1:0:1), libusb-1.0 1.0.27.11882 API 0x0100010a, hidapi 0.14.0, bluez 5.79, libftdi 1.5.
sr: [00:00.000333] backend: Host: x86_64-pc-linux-gnu, little-endian.
sr: [00:00.000379] backend: SCPI backends: TCP, serial, USBTMC.
sr: [00:00.000422] backend: Firmware search paths:
sr: [00:00.000538] backend:  - /home/cnf/.local/share/sigrok-firmware
sr: [00:00.000573] backend:  - /nix/store/6xmixr2x5h9bqifb4zf4hv3y3m8f5mlf-libsigrok-0.5.2-unstable-2024-10-20/share/sigrok-firmware
sr: [00:00.000614] backend:  - /nix/store/rhphinzabvbbb65d1qdjayhb0fx4q2kj-network-manager-applet-1.36.0/share/sigrok-firmware
sr: [00:00.000670] backend:  - /nix/store/ciigf79sbx1pckyyjzzv9nrny96lrijf-gnome-mimeapps/share/sigrok-firmware
sr: [00:00.000698] backend:  - /nix/store/m3cndj0jvn72ri17pmd6yz83d582186r-desktops/share/sigrok-firmware
sr: [00:00.000725] backend:  - /home/cnf/.nix-profile/share/sigrok-firmware
sr: [00:00.000752] backend:  - /nix/profile/share/sigrok-firmware
sr: [00:00.000779] backend:  - /home/cnf/.local/state/nix/profile/share/sigrok-firmware
sr: [00:00.000805] backend:  - /etc/profiles/per-user/cnf/share/sigrok-firmware
sr: [00:00.000833] backend:  - /nix/var/nix/profiles/default/share/sigrok-firmware
sr: [00:00.000868] backend:  - /run/current-system/sw/share/sigrok-firmware
sr: [00:00.000933] backend:  - /nix/store/ibzdvd08vnfzihyq9k4h9c3m2ghi5sp2-gnome-shell-47.2/share/gsettings-schemas/gnome-shell-47.2/sigrok-firmware
sr: [00:00.000988] backend:  - /nix/store/57kcdgar0vnchc9f2049hlwj9cjl4p3s-gnome-shell-extensions-47.2/share/gsettings-schemas/gnome-shell-extensions-47.2/sigrok-firmware
sr: [00:00.001137] backend: Sanity-checking all drivers.
sr: [00:00.001214] backend: Sanity-checking all input modules.
sr: [00:00.001289] backend: Sanity-checking all output modules.
sr: [00:00.001363] backend: Sanity-checking all transform modules.
srd: libsigrokdecode loglevel set to 5.
Driver functions:
    Power supply
sr: [00:00.044541] hwdriver: sr_config_list(): key 2147418112 (NULL) sdi (nil) cg NULL -> [uint32 20000, 20001, 20003]
Scan options:
    conn
    serialcomm
    force_detect
sr: [00:00.044682] hwdriver: sr_config_list(): key 2147418112 (NULL) sdi (nil) cg NULL -> [uint32 20000, 20001, 20003]
sr: [00:00.044742] serial: Opening serial port '/dev/ttyUSB0' (flags 1).
sr: [00:00.047382] serial: Parsing parameters from "9600/8n1".
sr: [00:00.047533] serial: Got params: rate 9600, frame 8/0/1, flow 0, rts -1, dtr -1.
sr: [00:00.047556] serial: Setting serial parameters on port /dev/ttyUSB0.
sr: [00:00.047781] serial: DBG: serial_set_params() rate 9600, 8n1
sr: [00:00.047814] serial: Flushing serial port /dev/ttyUSB0.
sr: [00:00.047836] korad-kaxxxxp: Want max 48 bytes.
sr: [00:00.047874] korad-kaxxxxp: Sending '*IDN?'.
sr: [00:00.047921] serial: Wrote 6/6 bytes.
sr: [00:00.048330] korad-kaxxxxp: want 48 bytes, timeout/retry: init 60/100, later 13/1.
sr: [00:06.063348] korad-kaxxxxp: got 0 bytes, received: ''.
sr: [00:06.063384] korad-kaxxxxp: Received: 0, 
sr: [00:06.063421] korad-kaxxxxp: Could not find model ID '', trying 'QJE3005PV1.0'.
sr: [00:06.063475] korad-kaxxxxp: Looking up: [QJE3005PV1.0].
sr: [00:06.063510] korad-kaxxxxp: Matches expected ID text: 'QJE3005PV1.0'.
sr: [00:06.063601] korad-kaxxxxp: Found: [Velleman] [LABPS3005DN]
sr: [00:06.063694] korad-kaxxxxp: Found replacement, using it instead.
sr: [00:06.063730] korad-kaxxxxp: Found: Velleman LABPS3005DN (idx 13).
sr: [00:06.063793] korad-kaxxxxp: Sending 'IOUT1?'.
sr: [00:06.063889] serial: Wrote 7/7 bytes.
sr: [00:06.064425] korad-kaxxxxp: want 6 bytes, timeout/retry: init 16/100, later 13/1.
sr: [00:07.678406] korad-kaxxxxp: got 0 bytes, received: ''.
sr: [00:07.678455] korad-kaxxxxp: value: 0.000000
sr: [00:07.678512] korad-kaxxxxp: Sending 'ISET1?'.
sr: [00:07.678714] serial: Wrote 7/7 bytes.
sr: [00:07.679272] korad-kaxxxxp: want 6 bytes, timeout/retry: init 16/100, later 13/1.
sr: [00:09.293462] korad-kaxxxxp: got 0 bytes, received: ''.
sr: [00:09.293510] korad-kaxxxxp: value: 0.000000
sr: [00:09.303754] korad-kaxxxxp: Sending 'VOUT1?'.
sr: [00:09.303904] serial: Wrote 7/7 bytes.
sr: [00:09.304442] korad-kaxxxxp: want 6 bytes, timeout/retry: init 16/100, later 13/1.
sr: [00:10.918909] korad-kaxxxxp: got 0 bytes, received: ''.
sr: [00:10.918962] korad-kaxxxxp: value: 0.000000
sr: [00:10.919034] korad-kaxxxxp: Sending 'VSET1?'.
sr: [00:10.919169] serial: Wrote 7/7 bytes.
sr: [00:10.920004] korad-kaxxxxp: want 6 bytes, timeout/retry: init 16/100, later 13/1.
sr: [00:12.534282] korad-kaxxxxp: got 0 bytes, received: ''.
sr: [00:12.534347] korad-kaxxxxp: value: 0.000000
sr: [00:12.534417] korad-kaxxxxp: Sending 'STATUS?'.
sr: [00:12.534505] serial: Wrote 8/8 bytes.
sr: [00:12.535095] korad-kaxxxxp: want 2 bytes, timeout/retry: init 12/100, later 13/1.
sr: [00:13.749701] korad-kaxxxxp: got 0 bytes, received: ''.
sr: [00:13.749764] korad-kaxxxxp: Status: 0x00
sr: [00:13.749810] korad-kaxxxxp: Status: CH1: constant current CH2: constant current. Tracking would be series and independent. Output is disabled. OCP is disabled, OVP is disabled. Device is silent.
sr: [00:13.749970] serial: Closing serial port /dev/ttyUSB0.
sr: [00:13.754861] hwdriver: Scan found 1 devices (korad-kaxxxxp).
sr: [00:13.754999] hwdriver: sr_config_get(): key 20000 (conn) sdi 0x14dbfa10 cg NULL -> '/dev/ttyUSB0'
korad-kaxxxxp:conn=/dev/ttyUSB0 - Velleman LABPS3005DN with 2 channels: V I
sr: [00:13.755065] device: korad-kaxxxxp: Opening device instance.
sr: [00:13.755096] serial: Opening serial port '/dev/ttyUSB0' (flags 1).
sr: [00:13.758369] serial: Parsing parameters from "9600/8n1".
sr: [00:13.758489] serial: Got params: rate 9600, frame 8/0/1, flow 0, rts -1, dtr -1.
sr: [00:13.758536] serial: Setting serial parameters on port /dev/ttyUSB0.
sr: [00:13.758889] serial: DBG: serial_set_params() rate 9600, 8n1
sr: [00:13.758949] serial: Flushing serial port /dev/ttyUSB0.
Supported configuration options:
sr: [00:13.759079] hwdriver: sr_config_get(): key 20000 (conn) sdi 0x14dbfa10 cg NULL -> '/dev/ttyUSB0'
    conn: /dev/ttyUSB0 (current)
    continuous: on, off
sr: [00:13.759219] hwdriver: sr_config_get(): key 50001 (limit_samples) sdi 0x14dbfa10 cg NULL -> uint64 0
    limit_samples: 0 (current)
sr: [00:13.759335] hwdriver: sr_config_get(): key 50000 (limit_time) sdi 0x14dbfa10 cg NULL -> uint64 0
    limit_time: 0 (current)
sr: [00:13.759432] korad-kaxxxxp: Sending 'VOUT1?'.
sr: [00:13.759511] serial: Wrote 7/7 bytes.
sr: [00:13.760007] korad-kaxxxxp: want 6 bytes, timeout/retry: init 16/100, later 13/1.
sr: [00:15.373886] korad-kaxxxxp: got 0 bytes, received: ''.
sr: [00:15.373938] korad-kaxxxxp: value: 0.000000
sr: [00:15.374003] hwdriver: sr_config_get(): key 30029 (voltage) sdi 0x14dbfa10 cg NULL -> 0.0
    voltage: 0.000000 (current)
sr: [00:15.374122] korad-kaxxxxp: Sending 'VSET1?'.
sr: [00:15.374199] serial: Wrote 7/7 bytes.
sr: [00:15.374856] korad-kaxxxxp: want 6 bytes, timeout/retry: init 16/100, later 13/1.
sr: [00:16.988147] korad-kaxxxxp: got 0 bytes, received: ''.
sr: [00:16.988205] korad-kaxxxxp: value: 0.000000
sr: [00:16.988387] hwdriver: sr_config_get(): key 30030 (voltage_target) sdi 0x14dbfa10 cg NULL -> 0.0
sr: [00:16.988522] hwdriver: sr_config_list(): key 30030 (voltage_target) sdi 0x14dbfa10 cg NULL -> [0.0, 31.0, 0.01]
    voltage_target: 0.000000 (current), 31.000000, 0.010000
sr: [00:16.988667] korad-kaxxxxp: Sending 'IOUT1?'.
sr: [00:16.988757] serial: Wrote 7/7 bytes.
sr: [00:16.989392] korad-kaxxxxp: want 6 bytes, timeout/retry: init 16/100, later 13/1.
sr: [00:18.603001] korad-kaxxxxp: got 0 bytes, received: ''.
sr: [00:18.603040] korad-kaxxxxp: value: 0.000000
sr: [00:18.603106] hwdriver: sr_config_get(): key 30031 (current) sdi 0x14dbfa10 cg NULL -> 0.0
    current: 0.000000 (current)
sr: [00:18.603210] korad-kaxxxxp: Sending 'ISET1?'.
sr: [00:18.603290] serial: Wrote 7/7 bytes.
sr: [00:18.603955] korad-kaxxxxp: want 6 bytes, timeout/retry: init 16/100, later 13/1.
sr: [00:20.217483] korad-kaxxxxp: got 0 bytes, received: ''.
sr: [00:20.217544] korad-kaxxxxp: value: 0.000000
sr: [00:20.227778] hwdriver: sr_config_get(): key 30032 (current_limit) sdi 0x14dbfa10 cg NULL -> 0.0
sr: [00:20.227902] hwdriver: sr_config_list(): key 30032 (current_limit) sdi 0x14dbfa10 cg NULL -> [0.0, 5.0999999999999996, 0.001]
    current_limit: 0.000000 (current), 5.100000, 0.001000
sr: [00:20.228139] korad-kaxxxxp: Sending 'STATUS?'.
sr: [00:20.228222] serial: Wrote 8/8 bytes.
sr: [00:20.228956] korad-kaxxxxp: want 2 bytes, timeout/retry: init 12/100, later 13/1.
sr: [00:21.437401] korad-kaxxxxp: got 0 bytes, received: ''.
sr: [00:21.437421] korad-kaxxxxp: Status: 0x00
sr: [00:21.437430] korad-kaxxxxp: Status: CH1: constant current CH2: constant current. Tracking would be series and independent. Output is disabled. OCP is disabled, OVP is disabled. Device is silent.
sr: [00:21.437454] hwdriver: sr_config_get(): key 30033 (enabled) sdi 0x14dbfa10 cg NULL -> false
    enabled: on, off (current)
sr: [00:21.437482] korad-kaxxxxp: Sending 'STATUS?'.
sr: [00:21.437506] serial: Wrote 8/8 bytes.
sr: [00:21.437913] korad-kaxxxxp: want 2 bytes, timeout/retry: init 12/100, later 13/1.
sr: [00:22.646398] korad-kaxxxxp: got 0 bytes, received: ''.
sr: [00:22.646433] korad-kaxxxxp: Status: 0x00
sr: [00:22.646462] korad-kaxxxxp: Status: CH1: constant current CH2: constant current. Tracking would be series and independent. Output is disabled. OCP is disabled, OVP is disabled. Device is silent.
sr: [00:22.646559] hwdriver: sr_config_get(): key 30043 (regulation) sdi 0x14dbfa10 cg NULL -> 'CC'
    regulation: CC (current)
sr: [00:22.646740] korad-kaxxxxp: Sending 'STATUS?'.
sr: [00:22.646920] serial: Wrote 8/8 bytes.
sr: [00:22.647518] korad-kaxxxxp: want 2 bytes, timeout/retry: init 12/100, later 13/1.
sr: [00:23.862576] korad-kaxxxxp: got 0 bytes, received: ''.
sr: [00:23.862609] korad-kaxxxxp: Status: 0x00
sr: [00:23.862669] korad-kaxxxxp: Status: CH1: constant current CH2: constant current. Tracking would be series and independent. Output is disabled. OCP is disabled, OVP is disabled. Device is silent.
sr: [00:23.862736] hwdriver: sr_config_get(): key 30038 (ocp_enabled) sdi 0x14dbfa10 cg NULL -> false
    ocp_enabled: on, off (current)
sr: [00:23.862826] korad-kaxxxxp: Sending 'STATUS?'.
sr: [00:23.862910] serial: Wrote 8/8 bytes.
sr: [00:23.863488] korad-kaxxxxp: want 2 bytes, timeout/retry: init 12/100, later 13/1.
sr: [00:25.076744] korad-kaxxxxp: got 0 bytes, received: ''.
sr: [00:25.076790] korad-kaxxxxp: Status: 0x00
sr: [00:25.076830] korad-kaxxxxp: Status: CH1: constant current CH2: constant current. Tracking would be series and independent. Output is disabled. OCP is disabled, OVP is disabled. Device is silent.
sr: [00:25.076899] hwdriver: sr_config_get(): key 30035 (ovp_enabled) sdi 0x14dbfa10 cg NULL -> false
    ovp_enabled: on, off (current)
sr: [00:25.076953] device: korad-kaxxxxp: Closing device instance.
sr: [00:25.076987] serial: Closing serial port /dev/ttyUSB0.
sr: [00:25.081576] hwdriver: Cleaning up all drivers.

@cnf
Copy link

cnf commented May 27, 2025

are you sending a newline, or the actual characters '\n'
mine responds to the actual characterss `'n'

tio /dev/ttyUSB0 -b 9600
.....
[19:19:53.820] INLCRNL is set
*IDN?\nQJE3005PV1.0
STATUS?\n000
VSET1?\n00.00

@MartinJM
Copy link
Author

MartinJM commented May 27, 2025

You're doing nothing wrong, but your device is not responding to the *IDN? request. Otherwise it would show something like the following (which is for my device):

sr: [00:00.001787] serial: Opening serial port '/dev/ttyACM0' (flags 1).
sr: [00:00.002961] serial: Parsing parameters from "9600/8n1".
sr: [00:00.003094] serial: Got params: rate 9600, frame 8/0/1, flow 0, rts -1, dtr -1.
sr: [00:00.003108] serial: Setting serial parameters on port /dev/ttyACM0.
sr: [00:00.003140] serial: DBG: serial_set_params() rate 9600, 8n1
sr: [00:00.003153] serial: Flushing serial port /dev/ttyACM0.
sr: [00:00.003171] korad-kaxxxxp: Want max 48 bytes.
sr: [00:00.003198] korad-kaxxxxp: Sending '*IDN?'.
sr: [00:00.003322] serial: Wrote 6/6 bytes.
sr: [00:00.003358] korad-kaxxxxp: want 48 bytes, timeout/retry: init 60/100, later 13/1.
sr: [00:00.063849] serial: Read 32/48 bytes.
sr: [00:00.077013] korad-kaxxxxp: receive timed out, want 48, received 32.
sr: [00:00.077055] korad-kaxxxxp: got 31 bytes, received: 'KORAD KA3005PS V1.0 SN:........'.
sr: [00:00.077081] korad-kaxxxxp: Received: 31, KORAD KA3005PS V1.0 SN:........
sr: [00:00.077107] korad-kaxxxxp: Looking up: [KORAD KA3005PS V1.0].
sr: [00:00.077169] korad-kaxxxxp: Matches generic '[vendor] model [vers] [trail]' pattern.
sr: [00:00.077896] korad-kaxxxxp: Found: [Korad] [KA3005PS]
sr: [00:00.078028] korad-kaxxxxp: Found: Korad KA3005PS (idx 0).

Also just read your original diff again: and noticed that you're actually adding a literal backslash and then n character. Which may make it work.

Can you try the following patch? It will take a while to connect, but might work for your device:

diff --git a/src/hardware/korad-kaxxxxp/api.c b/src/hardware/korad-kaxxxxp/api.c
index c6b9b1f6..824303f1 100644
--- a/src/hardware/korad-kaxxxxp/api.c
+++ b/src/hardware/korad-kaxxxxp/api.c
@@ -76,6 +76,8 @@ static const struct korad_kaxxxxp_model models[] = {
 	{"Tenma", "72-2710", "", 1, volts_30, amps_5, 0},
 	{"Velleman", "LABPS3005D", "", 1, volts_30, amps_5,
 		KORAD_QUIRK_LABPS_OVP_EN},
+	{"Velleman", "LABPS3005DN", "", 1, volts_30, amps_5,
+		KORAD_QUIRK_NEWLINE | KORAD_QUIRK_VELLEMAN},
 	{"Velleman", "PS3005D V1.3", "VELLEMANPS3005DV1.3" , 1, volts_30, amps_5,
 		KORAD_QUIRK_ID_TRAILING | KORAD_QUIRK_SLOW_PROCESSING},
 	{"Velleman", "PS3005D", "", 1, volts_30, amps_5, 0},
@@ -290,6 +292,17 @@ static GSList *scan(struct sr_dev_driver *di, GSList *options)
 	ret = korad_kaxxxxp_read_chars(serial, len, reply, true);
 	if (ret < 0)
 		return NULL;
+
+	// Also attempt literal \\n for VELLMAN
+	if (ret == 0) {
+		ret = korad_kaxxxxp_send_cmd(serial, "*IDN?\\n", true);
+		if (ret < 0)
+			return NULL;
+
+		ret = korad_kaxxxxp_read_chars(serial, len, reply, true);
+		if (ret < 0)
+			return NULL;
+	}
 	sr_dbg("Received: %d, %s", ret, reply);
 
 	/*
diff --git a/src/hardware/korad-kaxxxxp/protocol.c b/src/hardware/korad-kaxxxxp/protocol.c
index 0ce7206e..b27d8f9c 100644
--- a/src/hardware/korad-kaxxxxp/protocol.c
+++ b/src/hardware/korad-kaxxxxp/protocol.c
@@ -229,8 +229,13 @@ SR_PRIV int korad_kaxxxxp_set_value(struct sr_serial_dev_inst *serial,
 			"VSET1:%05.2f", devc->set_voltage_target);
 		break;
 	case KAXXXXP_OUTPUT:
-		sr_snprintf_ascii(msg, sizeof(msg),
-			"OUT%1d", (devc->set_output_enabled) ? 1 : 0);
+		if (devc->model->quirks & KORAD_QUIRK_VELLEMAN) {
+			sr_snprintf_ascii(msg, sizeof(msg),
+				"OUTPUT%1d", (devc->set_output_enabled) ? 1 : 0);
+		} else {
+			sr_snprintf_ascii(msg, sizeof(msg),
+				"OUT%1d", (devc->set_output_enabled) ? 1 : 0);
+		}
 		/* Set value back to recognize changes */
 		devc->output_enabled = devc->set_output_enabled;
 		break;
diff --git a/src/hardware/korad-kaxxxxp/protocol.h b/src/hardware/korad-kaxxxxp/protocol.h
index 43653d14..19aef882 100644
--- a/src/hardware/korad-kaxxxxp/protocol.h
+++ b/src/hardware/korad-kaxxxxp/protocol.h
@@ -40,7 +40,8 @@ enum korad_quirks_flag {
 	KORAD_QUIRK_ID_OPT_VERSION = 1UL << 3,
 	KORAD_QUIRK_SLOW_PROCESSING = 1UL << 4,
 	KORAD_QUIRK_NEWLINE = 1UL << 5,
-	KORAD_QUIRK_ALL = (1UL << 6) - 1,
+	KORAD_QUIRK_VELLEMAN = 1UL << 6,
+	KORAD_QUIRK_ALL = (1UL << 7) - 1,
 };
 
 /* Information on single model */

Might also be necessary for the other commands, not sure yet, we will see. Another option could be to change the \n from the protocol.c to a \r, but lets take it one step at a time.

@MartinJM
Copy link
Author

MartinJM commented May 27, 2025

are you sending a newline, or the actual characters '\n'

Newlines. Only noticed that you meant the actual characters just before my previous comment.

For my device it's about newlines (or carriage-returns). A literal \\n is very weird to me.


Edit:

This clears up a lot:

tio /dev/ttyUSB0 -b 9600
.....
[19:19:53.820] INLCRNL is set
*IDN?\nQJE3005PV1.0
STATUS?\n000
VSET1?\n00.00

Try the following patch:

diff --git a/src/hardware/korad-kaxxxxp/api.c b/src/hardware/korad-kaxxxxp/api.c
index c6b9b1f6..30d5d36a 100644
--- a/src/hardware/korad-kaxxxxp/api.c
+++ b/src/hardware/korad-kaxxxxp/api.c
@@ -76,6 +76,8 @@ static const struct korad_kaxxxxp_model models[] = {
 	{"Tenma", "72-2710", "", 1, volts_30, amps_5, 0},
 	{"Velleman", "LABPS3005D", "", 1, volts_30, amps_5,
 		KORAD_QUIRK_LABPS_OVP_EN},
+	{"Velleman", "LABPS3005DN", "", 1, volts_30, amps_5,
+		KORAD_QUIRK_VELLEMAN},
 	{"Velleman", "PS3005D V1.3", "VELLEMANPS3005DV1.3" , 1, volts_30, amps_5,
 		KORAD_QUIRK_ID_TRAILING | KORAD_QUIRK_SLOW_PROCESSING},
 	{"Velleman", "PS3005D", "", 1, volts_30, amps_5, 0},
@@ -282,7 +284,10 @@ static GSList *scan(struct sr_dev_driver *di, GSList *options)
 
 	// TODO: check if adding the newline breaks some other devices - I cannot do so
 	// This is required for the KA3005PS
-	ret = korad_kaxxxxp_send_cmd(serial, "*IDN?", true);
+	// TODO: check if adding the \\n breaks some other devices - I cannot do so
+	// This is required for the Velleman LABPS3005DN
+	// It does not break the KA3005PS
+	ret = korad_kaxxxxp_send_cmd(serial, "*IDN?", true, true);
 	if (ret < 0)
 		return NULL;
 
@@ -290,6 +295,7 @@ static GSList *scan(struct sr_dev_driver *di, GSList *options)
 	ret = korad_kaxxxxp_read_chars(serial, len, reply, true);
 	if (ret < 0)
 		return NULL;
+
 	sr_dbg("Received: %d, %s", ret, reply);
 
 	/*
diff --git a/src/hardware/korad-kaxxxxp/protocol.c b/src/hardware/korad-kaxxxxp/protocol.c
index 0ce7206e..76672e4c 100644
--- a/src/hardware/korad-kaxxxxp/protocol.c
+++ b/src/hardware/korad-kaxxxxp/protocol.c
@@ -25,18 +25,22 @@
 #define EXTRA_PROCESSING_TIME_MS  450
 
 SR_PRIV int korad_kaxxxxp_send_cmd(struct sr_serial_dev_inst *serial,
-	const char *cmd, bool add_newline)
+	const char *cmd, bool add_newline, bool velleman_literal)
 {
 	int ret;
 
 	char* addition = "";
-	if (add_newline)
+	if (add_newline && velleman_literal)
+		addition = "\\n\n";
+	else if (add_newline)
 		addition = "\n";
+	else if (velleman_literal)
+		addition = "\\n"; // Very weird one
 
-	// 21 was chosen here because 20 is chosen in korad_kaxxxxp_set_value
-	char newcmd[21];
-	ret = sr_snprintf_ascii(newcmd, 21, "%s%s", cmd, addition);
-	if (ret < 0 || ret >= 21) {
+	// 23 was chosen here because 20 is chosen in korad_kaxxxxp_set_value
+	char newcmd[23];
+	ret = sr_snprintf_ascii(newcmd, sizeof(newcmd), "%s%s", cmd, addition);
+	if (ret < 0 || ret >= (int) sizeof(newcmd)) {
 		sr_err("Error creating command: %d.", ret);
 		if (ret > 0)
 			ret = -ret; // make errors always return negative numbers
@@ -229,8 +233,13 @@ SR_PRIV int korad_kaxxxxp_set_value(struct sr_serial_dev_inst *serial,
 			"VSET1:%05.2f", devc->set_voltage_target);
 		break;
 	case KAXXXXP_OUTPUT:
-		sr_snprintf_ascii(msg, sizeof(msg),
-			"OUT%1d", (devc->set_output_enabled) ? 1 : 0);
+		if (devc->model->quirks & KORAD_QUIRK_VELLEMAN) {
+			sr_snprintf_ascii(msg, sizeof(msg),
+				"OUTPUT%1d", (devc->set_output_enabled) ? 1 : 0);
+		} else {
+			sr_snprintf_ascii(msg, sizeof(msg),
+				"OUT%1d", (devc->set_output_enabled) ? 1 : 0);
+		}
 		/* Set value back to recognize changes */
 		devc->output_enabled = devc->set_output_enabled;
 		break;
@@ -277,7 +286,8 @@ SR_PRIV int korad_kaxxxxp_set_value(struct sr_serial_dev_inst *serial,
 	}
 
 	if (ret == SR_OK && msg[0]) {
-		ret = korad_kaxxxxp_send_cmd(serial, msg, devc->model->quirks & KORAD_QUIRK_NEWLINE);
+		ret = korad_kaxxxxp_send_cmd(serial, msg, devc->model->quirks & KORAD_QUIRK_NEWLINE,
+			devc->model->quirks & KORAD_QUIRK_VELLEMAN);
 		devc->next_req_time = next_req_time(devc, TRUE, target);
 	}
 
@@ -303,26 +313,27 @@ SR_PRIV int korad_kaxxxxp_get_value(struct sr_serial_dev_inst *serial,
 	count = 6;
 
 	bool newline_quirk = devc->model->quirks & KORAD_QUIRK_NEWLINE;
+	bool velleman_quirk = devc->model->quirks & KORAD_QUIRK_VELLEMAN;
 
 	switch (target) {
 	case KAXXXXP_CURRENT:
 		/* Read current from device. */
-		ret = korad_kaxxxxp_send_cmd(serial, "IOUT1?", newline_quirk);
+		ret = korad_kaxxxxp_send_cmd(serial, "IOUT1?", newline_quirk, velleman_quirk);
 		value = &(devc->current);
 		break;
 	case KAXXXXP_CURRENT_LIMIT:
 		/* Read set current from device. */
-		ret = korad_kaxxxxp_send_cmd(serial, "ISET1?", newline_quirk);
+		ret = korad_kaxxxxp_send_cmd(serial, "ISET1?", newline_quirk, velleman_quirk);
 		value = &(devc->current_limit);
 		break;
 	case KAXXXXP_VOLTAGE:
 		/* Read voltage from device. */
-		ret = korad_kaxxxxp_send_cmd(serial, "VOUT1?", newline_quirk);
+		ret = korad_kaxxxxp_send_cmd(serial, "VOUT1?", newline_quirk, velleman_quirk);
 		value = &(devc->voltage);
 		break;
 	case KAXXXXP_VOLTAGE_TARGET:
 		/* Read set voltage from device. */
-		ret = korad_kaxxxxp_send_cmd(serial, "VSET1?", newline_quirk);
+		ret = korad_kaxxxxp_send_cmd(serial, "VSET1?", newline_quirk, velleman_quirk);
 		value = &(devc->voltage_target);
 		break;
 	case KAXXXXP_STATUS:
@@ -330,7 +341,7 @@ SR_PRIV int korad_kaxxxxp_get_value(struct sr_serial_dev_inst *serial,
 	case KAXXXXP_OCP:
 	case KAXXXXP_OVP:
 		/* Read status from device. */
-		ret = korad_kaxxxxp_send_cmd(serial, "STATUS?", newline_quirk);
+		ret = korad_kaxxxxp_send_cmd(serial, "STATUS?", newline_quirk, velleman_quirk);
 		count = 1;
 		if (newline_quirk)
 			count = 2;
diff --git a/src/hardware/korad-kaxxxxp/protocol.h b/src/hardware/korad-kaxxxxp/protocol.h
index 43653d14..4540c831 100644
--- a/src/hardware/korad-kaxxxxp/protocol.h
+++ b/src/hardware/korad-kaxxxxp/protocol.h
@@ -40,7 +40,8 @@ enum korad_quirks_flag {
 	KORAD_QUIRK_ID_OPT_VERSION = 1UL << 3,
 	KORAD_QUIRK_SLOW_PROCESSING = 1UL << 4,
 	KORAD_QUIRK_NEWLINE = 1UL << 5,
-	KORAD_QUIRK_ALL = (1UL << 6) - 1,
+	KORAD_QUIRK_VELLEMAN = 1UL << 6,
+	KORAD_QUIRK_ALL = (1UL << 7) - 1,
 };
 
 /* Information on single model */
@@ -105,7 +106,7 @@ struct dev_context {
 };
 
 SR_PRIV int korad_kaxxxxp_send_cmd(struct sr_serial_dev_inst *serial,
-		const char *cmd, bool add_newline);
+		const char *cmd, bool add_newline, bool velleman_literal);
 SR_PRIV int korad_kaxxxxp_read_chars(struct sr_serial_dev_inst *serial,
 		size_t count, char *buf, bool strip_newline);
 SR_PRIV int korad_kaxxxxp_set_value(struct sr_serial_dev_inst *serial,

I doubt it will work at once, you'll probably need something like QJE3005PV1.0 instead of LABPS3005DN, but I'm unfamiliar with how that should be implemented exactly (as it contains version information, and something different from what the documentation suggests?).

And maybe you also need the newline quirk? I don't think you do so I've removed it from the quirks now, but if it doesn't work, you can try adding it again as well.

@cnf
Copy link

cnf commented May 27, 2025

are you sending a newline, or the actual characters '\n'

Newlines. Only noticed that you meant the actual characters just before my previous comment.

For my device it's about newlines (or carriage-returns). A literal \\n is very weird to me.

A literal '\n' is very weird to me, too! idno what the person that wrote that was smoking :P

sr: [00:00.020582] hwdriver: sr_config_list(): key 2147418112 (NULL) sdi (nil) cg NULL -> [uint32 20000, 20001, 20003]
sr: [00:00.020610] serial: Opening serial port '/dev/ttyUSB0' (flags 1).
sr: [00:00.022520] serial: Parsing parameters from "9600/8n1".
sr: [00:00.022623] serial: Got params: rate 9600, frame 8/0/1, flow 0, rts -1, dtr -1.
sr: [00:00.022644] serial: Setting serial parameters on port /dev/ttyUSB0.
sr: [00:00.022838] serial: DBG: serial_set_params() rate 9600, 8n1
sr: [00:00.022860] serial: Flushing serial port /dev/ttyUSB0.
sr: [00:00.022883] korad-kaxxxxp: Want max 48 bytes.
sr: [00:00.022896] korad-kaxxxxp: Sending '*IDN?'.
sr: [00:00.022919] serial: Wrote 6/6 bytes.
sr: [00:00.023272] korad-kaxxxxp: want 48 bytes, timeout/retry: init 60/100, later 13/1.
sr: [00:06.036559] korad-kaxxxxp: got 0 bytes, received: ''.
sr: [00:06.036611] korad-kaxxxxp: Sending '*IDN?\n'.
sr: [00:06.036733] serial: Wrote 7/7 bytes.
sr: [00:06.037321] korad-kaxxxxp: want 48 bytes, timeout/retry: init 60/100, later 13/1.
sr: [00:06.097543] serial: Read 13/48 bytes.
sr: [00:06.110754] korad-kaxxxxp: receive timed out, want 48, received 13.
sr: [00:06.110794] korad-kaxxxxp: got 12 bytes, received: 'QJE3005PV1.0'.
sr: [00:06.110866] korad-kaxxxxp: Received: 12, QJE3005PV1.0
sr: [00:06.110907] korad-kaxxxxp: Looking up: [QJE3005PV1.0].
sr: [00:06.110945] korad-kaxxxxp: Matches expected ID text: 'QJE3005PV1.0'.
sr: [00:06.110981] korad-kaxxxxp: Found: [Velleman] [LABPS3005DN]
sr: [00:06.111013] korad-kaxxxxp: Found: Velleman LABPS3005DN (idx 13).
sr: [00:06.111060] korad-kaxxxxp: Sending 'IOUT1?'.

So I changed your last patch a bit,
it responds to *IDN?\n with QJE3005PV1.0' so i added that to:

+	{"Velleman", "LABPS3005DN", "QJE3005PV1.0", 1, volts_30, amps_5,
+		KORAD_QUIRK_NEWLINE | KORAD_QUIRK_VELLEMAN},

And I changed

ret = korad_kaxxxxp_send_cmd(serial, "*IDN?\\n", true);

to

ret = korad_kaxxxxp_send_cmd(serial, "*IDN?\\n", false);

It seems it doesn't like ACTUAL newlines, at that 🤦

It now gets detected, but of course the rest doesn't work, because actual \n instead of a newline :P

@MartinJM
Copy link
Author

MartinJM commented May 27, 2025

Ah I just edited my previous response. See the patch there, that might still need some of your edits.

It seems it doesn't like ACTUAL newlines, at that 🤦

Ah that will be an issue for supporting both of our devices then. Mine must have a newline, and yours cannot have one... Maybe one different option: Can you try it with a carriage return (\r) instead of a newline? That works on my device as well, if it works on yours too it could be a nice middle-road.


Edit: Oh yeah your STATUS? response also seems way different. I don't think it's likely to work at the moment.

@cnf
Copy link

cnf commented May 27, 2025

that last one isn't detecting it anymore at all... I'll poke at it some more tomorrow, thanks for the help!

@cnf
Copy link

cnf commented May 28, 2025

ok, you last patch works, if I change:

Add QJE3005PV1.0
+       {"Velleman", "LABPS3005DN", "QJE3005PV1.0", 1, volts_30, amps_5,
+               KORAD_QUIRK_VELLEMAN},
set add_newline to false in the discovery
ret = korad_kaxxxxp_send_cmd(serial, "*IDN?", false, true);
So the patch that works atm is
diff --git a/src/hardware/korad-kaxxxxp/api.c b/src/hardware/korad-kaxxxxp/api.c
index c6b9b1f6..30d5d36a 100644
--- a/src/hardware/korad-kaxxxxp/api.c
+++ b/src/hardware/korad-kaxxxxp/api.c
@@ -76,6 +76,8 @@ static const struct korad_kaxxxxp_model models[] = {
 	{"Tenma", "72-2710", "", 1, volts_30, amps_5, 0},
 	{"Velleman", "LABPS3005D", "", 1, volts_30, amps_5,
 		KORAD_QUIRK_LABPS_OVP_EN},
+	{"Velleman", "LABPS3005DN", "QJE3005PV1.0", 1, volts_30, amps_5,
+		KORAD_QUIRK_VELLEMAN},
 	{"Velleman", "PS3005D V1.3", "VELLEMANPS3005DV1.3" , 1, volts_30, amps_5,
 		KORAD_QUIRK_ID_TRAILING | KORAD_QUIRK_SLOW_PROCESSING},
 	{"Velleman", "PS3005D", "", 1, volts_30, amps_5, 0},
@@ -282,7 +284,10 @@ static GSList *scan(struct sr_dev_driver *di, GSList *options)
 
 	// TODO: check if adding the newline breaks some other devices - I cannot do so
 	// This is required for the KA3005PS
-	ret = korad_kaxxxxp_send_cmd(serial, "*IDN?", true);
+	// TODO: check if adding the \\n breaks some other devices - I cannot do so
+	// This is required for the Velleman LABPS3005DN
+	// It does not break the KA3005PS
+	ret = korad_kaxxxxp_send_cmd(serial, "*IDN?", false, true);
 	if (ret < 0)
 		return NULL;
 
@@ -290,6 +295,7 @@ static GSList *scan(struct sr_dev_driver *di, GSList *options)
 	ret = korad_kaxxxxp_read_chars(serial, len, reply, true);
 	if (ret < 0)
 		return NULL;
+
 	sr_dbg("Received: %d, %s", ret, reply);
 
 	/*
diff --git a/src/hardware/korad-kaxxxxp/protocol.c b/src/hardware/korad-kaxxxxp/protocol.c
index 0ce7206e..76672e4c 100644
--- a/src/hardware/korad-kaxxxxp/protocol.c
+++ b/src/hardware/korad-kaxxxxp/protocol.c
@@ -25,18 +25,22 @@
 #define EXTRA_PROCESSING_TIME_MS  450
 
 SR_PRIV int korad_kaxxxxp_send_cmd(struct sr_serial_dev_inst *serial,
-	const char *cmd, bool add_newline)
+	const char *cmd, bool add_newline, bool velleman_literal)
 {
 	int ret;
 
 	char* addition = "";
-	if (add_newline)
+	if (add_newline && velleman_literal)
+		addition = "\\n\n";
+	else if (add_newline)
 		addition = "\n";
+	else if (velleman_literal)
+		addition = "\\n"; // Very weird one
 
-	// 21 was chosen here because 20 is chosen in korad_kaxxxxp_set_value
-	char newcmd[21];
-	ret = sr_snprintf_ascii(newcmd, 21, "%s%s", cmd, addition);
-	if (ret < 0 || ret >= 21) {
+	// 23 was chosen here because 20 is chosen in korad_kaxxxxp_set_value
+	char newcmd[23];
+	ret = sr_snprintf_ascii(newcmd, sizeof(newcmd), "%s%s", cmd, addition);
+	if (ret < 0 || ret >= (int) sizeof(newcmd)) {
 		sr_err("Error creating command: %d.", ret);
 		if (ret > 0)
 			ret = -ret; // make errors always return negative numbers
@@ -229,8 +233,13 @@ SR_PRIV int korad_kaxxxxp_set_value(struct sr_serial_dev_inst *serial,
 			"VSET1:%05.2f", devc->set_voltage_target);
 		break;
 	case KAXXXXP_OUTPUT:
-		sr_snprintf_ascii(msg, sizeof(msg),
-			"OUT%1d", (devc->set_output_enabled) ? 1 : 0);
+		if (devc->model->quirks & KORAD_QUIRK_VELLEMAN) {
+			sr_snprintf_ascii(msg, sizeof(msg),
+				"OUTPUT%1d", (devc->set_output_enabled) ? 1 : 0);
+		} else {
+			sr_snprintf_ascii(msg, sizeof(msg),
+				"OUT%1d", (devc->set_output_enabled) ? 1 : 0);
+		}
 		/* Set value back to recognize changes */
 		devc->output_enabled = devc->set_output_enabled;
 		break;
@@ -277,7 +286,8 @@ SR_PRIV int korad_kaxxxxp_set_value(struct sr_serial_dev_inst *serial,
 	}
 
 	if (ret == SR_OK && msg[0]) {
-		ret = korad_kaxxxxp_send_cmd(serial, msg, devc->model->quirks & KORAD_QUIRK_NEWLINE);
+		ret = korad_kaxxxxp_send_cmd(serial, msg, devc->model->quirks & KORAD_QUIRK_NEWLINE,
+			devc->model->quirks & KORAD_QUIRK_VELLEMAN);
 		devc->next_req_time = next_req_time(devc, TRUE, target);
 	}
 
@@ -303,26 +313,27 @@ SR_PRIV int korad_kaxxxxp_get_value(struct sr_serial_dev_inst *serial,
 	count = 6;
 
 	bool newline_quirk = devc->model->quirks & KORAD_QUIRK_NEWLINE;
+	bool velleman_quirk = devc->model->quirks & KORAD_QUIRK_VELLEMAN;
 
 	switch (target) {
 	case KAXXXXP_CURRENT:
 		/* Read current from device. */
-		ret = korad_kaxxxxp_send_cmd(serial, "IOUT1?", newline_quirk);
+		ret = korad_kaxxxxp_send_cmd(serial, "IOUT1?", newline_quirk, velleman_quirk);
 		value = &(devc->current);
 		break;
 	case KAXXXXP_CURRENT_LIMIT:
 		/* Read set current from device. */
-		ret = korad_kaxxxxp_send_cmd(serial, "ISET1?", newline_quirk);
+		ret = korad_kaxxxxp_send_cmd(serial, "ISET1?", newline_quirk, velleman_quirk);
 		value = &(devc->current_limit);
 		break;
 	case KAXXXXP_VOLTAGE:
 		/* Read voltage from device. */
-		ret = korad_kaxxxxp_send_cmd(serial, "VOUT1?", newline_quirk);
+		ret = korad_kaxxxxp_send_cmd(serial, "VOUT1?", newline_quirk, velleman_quirk);
 		value = &(devc->voltage);
 		break;
 	case KAXXXXP_VOLTAGE_TARGET:
 		/* Read set voltage from device. */
-		ret = korad_kaxxxxp_send_cmd(serial, "VSET1?", newline_quirk);
+		ret = korad_kaxxxxp_send_cmd(serial, "VSET1?", newline_quirk, velleman_quirk);
 		value = &(devc->voltage_target);
 		break;
 	case KAXXXXP_STATUS:
@@ -330,7 +341,7 @@ SR_PRIV int korad_kaxxxxp_get_value(struct sr_serial_dev_inst *serial,
 	case KAXXXXP_OCP:
 	case KAXXXXP_OVP:
 		/* Read status from device. */
-		ret = korad_kaxxxxp_send_cmd(serial, "STATUS?", newline_quirk);
+		ret = korad_kaxxxxp_send_cmd(serial, "STATUS?", newline_quirk, velleman_quirk);
 		count = 1;
 		if (newline_quirk)
 			count = 2;
diff --git a/src/hardware/korad-kaxxxxp/protocol.h b/src/hardware/korad-kaxxxxp/protocol.h
index 43653d14..4540c831 100644
--- a/src/hardware/korad-kaxxxxp/protocol.h
+++ b/src/hardware/korad-kaxxxxp/protocol.h
@@ -40,7 +40,8 @@ enum korad_quirks_flag {
 	KORAD_QUIRK_ID_OPT_VERSION = 1UL << 3,
 	KORAD_QUIRK_SLOW_PROCESSING = 1UL << 4,
 	KORAD_QUIRK_NEWLINE = 1UL << 5,
-	KORAD_QUIRK_ALL = (1UL << 6) - 1,
+	KORAD_QUIRK_VELLEMAN = 1UL << 6,
+	KORAD_QUIRK_ALL = (1UL << 7) - 1,
 };
 
 /* Information on single model */
@@ -105,7 +106,7 @@ struct dev_context {
 };
 
 SR_PRIV int korad_kaxxxxp_send_cmd(struct sr_serial_dev_inst *serial,
-		const char *cmd, bool add_newline);
+		const char *cmd, bool add_newline, bool velleman_literal);
 SR_PRIV int korad_kaxxxxp_read_chars(struct sr_serial_dev_inst *serial,
 		size_t count, char *buf, bool strip_newline);
 SR_PRIV int korad_kaxxxxp_set_value(struct sr_serial_dev_inst *serial,

log:

± sigrok-cli -d korad-kaxxxxp:conn=/dev/ttyUSB0 -l 5 --show 
sr: [00:00.000044] log: libsigrok loglevel set to 5.
sr: [00:00.000067] backend: libsigrok 0.6.0/4:0:0.
sr: [00:00.000157] backend: Libs: glib 2.82.5 (rt: 2.82.5/8205:5), zlib 1.3.1, libzip 1.11.3, minilzo 2.10, libserialport 0.1.1/1:0:1 (rt: 0.1.1/1:0:1), libusb-1.0 1.0.27.11882 API 0x0100010a, hidapi 0.14.0, bluez 5.79, libftdi 1.5.
sr: [00:00.000195] backend: Host: x86_64-pc-linux-gnu, little-endian.
sr: [00:00.000222] backend: SCPI backends: TCP, serial, USBTMC.
sr: [00:00.000258] backend: Firmware search paths:
sr: [00:00.000316] backend:  - /home/cnf/.local/share/sigrok-firmware
sr: [00:00.000338] backend:  - /nix/store/7jc15rv458d6zj247dcj5lwxysl6sppw-libsigrok-0.5.2-unstable-2024-10-20/share/sigrok-firmware
sr: [00:00.000375] backend:  - /nix/store/y2m5aj2saaafp2fqa2rp8v0qlbzjmihb-network-manager-applet-1.36.0/share/sigrok-firmware
sr: [00:00.000411] backend:  - /nix/store/ciigf79sbx1pckyyjzzv9nrny96lrijf-gnome-mimeapps/share/sigrok-firmware
sr: [00:00.000448] backend:  - /nix/store/m3cndj0jvn72ri17pmd6yz83d582186r-desktops/share/sigrok-firmware
sr: [00:00.000476] backend:  - /home/cnf/.nix-profile/share/sigrok-firmware
sr: [00:00.000499] backend:  - /nix/profile/share/sigrok-firmware
sr: [00:00.000524] backend:  - /home/cnf/.local/state/nix/profile/share/sigrok-firmware
sr: [00:00.000548] backend:  - /etc/profiles/per-user/cnf/share/sigrok-firmware
sr: [00:00.000579] backend:  - /nix/var/nix/profiles/default/share/sigrok-firmware
sr: [00:00.000598] backend:  - /run/current-system/sw/share/sigrok-firmware
sr: [00:00.000619] backend:  - /nix/store/ibzdvd08vnfzihyq9k4h9c3m2ghi5sp2-gnome-shell-47.2/share/gsettings-schemas/gnome-shell-47.2/sigrok-firmware
sr: [00:00.000645] backend:  - /nix/store/57kcdgar0vnchc9f2049hlwj9cjl4p3s-gnome-shell-extensions-47.2/share/gsettings-schemas/gnome-shell-extensions-47.2/sigrok-firmware
sr: [00:00.000736] backend: Sanity-checking all drivers.
sr: [00:00.000776] backend: Sanity-checking all input modules.
sr: [00:00.000835] backend: Sanity-checking all output modules.
sr: [00:00.000884] backend: Sanity-checking all transform modules.
srd: libsigrokdecode loglevel set to 5.
Driver functions:
    Power supply
sr: [00:00.019758] hwdriver: sr_config_list(): key 2147418112 (NULL) sdi (nil) cg NULL -> [uint32 20000, 20001, 20003]
Scan options:
    conn
    serialcomm
    force_detect
sr: [00:00.019823] hwdriver: sr_config_list(): key 2147418112 (NULL) sdi (nil) cg NULL -> [uint32 20000, 20001, 20003]
sr: [00:00.019841] serial: Opening serial port '/dev/ttyUSB0' (flags 1).
sr: [00:00.021879] serial: Parsing parameters from "9600/8n1".
sr: [00:00.021972] serial: Got params: rate 9600, frame 8/0/1, flow 0, rts -1, dtr -1.
sr: [00:00.022006] serial: Setting serial parameters on port /dev/ttyUSB0.
sr: [00:00.022181] serial: DBG: serial_set_params() rate 9600, 8n1
sr: [00:00.022197] serial: Flushing serial port /dev/ttyUSB0.
sr: [00:00.022211] korad-kaxxxxp: Want max 48 bytes.
sr: [00:00.022234] korad-kaxxxxp: Sending '*IDN?'.
sr: [00:00.022258] serial: Wrote 7/7 bytes.
sr: [00:00.022708] korad-kaxxxxp: want 48 bytes, timeout/retry: init 60/100, later 13/1.
sr: [00:00.082833] serial: Read 13/48 bytes.
sr: [00:00.095978] korad-kaxxxxp: receive timed out, want 48, received 13.
sr: [00:00.096012] korad-kaxxxxp: got 12 bytes, received: 'QJE3005PV1.0'.
sr: [00:00.096052] korad-kaxxxxp: Received: 12, QJE3005PV1.0
sr: [00:00.096093] korad-kaxxxxp: Looking up: [QJE3005PV1.0].
sr: [00:00.096172] korad-kaxxxxp: Matches expected ID text: 'QJE3005PV1.0'.
sr: [00:00.096209] korad-kaxxxxp: Found: [Velleman] [LABPS3005DN]
sr: [00:00.096249] korad-kaxxxxp: Found: Velleman LABPS3005DN (idx 13).
sr: [00:00.096311] korad-kaxxxxp: Sending 'IOUT1?'.
sr: [00:00.096394] serial: Wrote 8/8 bytes.
sr: [00:00.097183] korad-kaxxxxp: want 6 bytes, timeout/retry: init 16/100, later 13/1.
sr: [00:00.123354] serial: Read 6/6 bytes.
sr: [00:00.123412] korad-kaxxxxp: got 6 bytes, received: '0.000'.
sr: [00:00.123523] korad-kaxxxxp: value: 0.000000
sr: [00:00.176607] korad-kaxxxxp: Sleeping for processing 52889 usec
sr: [00:00.176685] korad-kaxxxxp: Sending 'ISET1?'.
sr: [00:00.176776] serial: Wrote 8/8 bytes.
sr: [00:00.177315] korad-kaxxxxp: want 6 bytes, timeout/retry: init 16/100, later 13/1.
sr: [00:00.193509] serial: Read 4/6 bytes.
sr: [00:00.195488] serial: Read 2/2 bytes.
sr: [00:00.195541] korad-kaxxxxp: got 6 bytes, received: '0.000'.
sr: [00:00.195588] korad-kaxxxxp: value: 0.000000
sr: [00:00.256965] korad-kaxxxxp: Sleeping for processing 50920 usec
sr: [00:00.257006] korad-kaxxxxp: Sending 'VOUT1?'.
sr: [00:00.257115] serial: Wrote 8/8 bytes.
sr: [00:00.257822] korad-kaxxxxp: want 6 bytes, timeout/retry: init 16/100, later 13/1.
sr: [00:00.274020] serial: Read 3/6 bytes.
sr: [00:00.277123] serial: Read 3/3 bytes.
sr: [00:00.277175] korad-kaxxxxp: got 6 bytes, received: '00.00'.
sr: [00:00.277234] korad-kaxxxxp: value: 0.000000
sr: [00:00.337279] korad-kaxxxxp: Sleeping for processing 59850 usec
sr: [00:00.337319] korad-kaxxxxp: Sending 'VSET1?'.
sr: [00:00.337470] serial: Wrote 8/8 bytes.
sr: [00:00.338039] korad-kaxxxxp: want 6 bytes, timeout/retry: init 16/100, later 13/1.
sr: [00:00.360935] serial: Read 6/6 bytes.
sr: [00:00.360991] korad-kaxxxxp: got 6 bytes, received: '08.00'.
sr: [00:00.361036] korad-kaxxxxp: value: 0.000000
sr: [00:00.417643] korad-kaxxxxp: Sleeping for processing 56433 usec
sr: [00:00.417709] korad-kaxxxxp: Sending 'STATUS?'.
sr: [00:00.417820] serial: Wrote 9/9 bytes.
sr: [00:00.418461] korad-kaxxxxp: want 1 bytes, timeout/retry: init 11/100, later 13/1.
sr: [00:00.437977] serial: Read 1/1 bytes.
sr: [00:00.438010] korad-kaxxxxp: got 1 bytes, received: '0'.
sr: [00:00.438047] korad-kaxxxxp: Status: 0x30
sr: [00:00.438098] korad-kaxxxxp: Status: CH1: constant current CH2: constant current. Tracking would be series and independent. Output is disabled. OCP is enabled, OVP is disabled. Device is beeping.
sr: [00:00.438149] serial: Closing serial port /dev/ttyUSB0.
sr: [00:00.441167] hwdriver: Scan found 1 devices (korad-kaxxxxp).
sr: [00:00.441321] hwdriver: sr_config_get(): key 20000 (conn) sdi 0x332819f0 cg NULL -> '/dev/ttyUSB0'
korad-kaxxxxp:conn=/dev/ttyUSB0 - Velleman LABPS3005DN with 2 channels: V I
sr: [00:00.441395] device: korad-kaxxxxp: Opening device instance.
sr: [00:00.441476] serial: Opening serial port '/dev/ttyUSB0' (flags 1).
sr: [00:00.444366] serial: Parsing parameters from "9600/8n1".
sr: [00:00.444524] serial: Got params: rate 9600, frame 8/0/1, flow 0, rts -1, dtr -1.
sr: [00:00.444566] serial: Setting serial parameters on port /dev/ttyUSB0.
sr: [00:00.444950] serial: DBG: serial_set_params() rate 9600, 8n1
sr: [00:00.445010] serial: Flushing serial port /dev/ttyUSB0.
Supported configuration options:
sr: [00:00.445141] hwdriver: sr_config_get(): key 20000 (conn) sdi 0x332819f0 cg NULL -> '/dev/ttyUSB0'
    conn: /dev/ttyUSB0 (current)
    continuous: on, off
sr: [00:00.445339] hwdriver: sr_config_get(): key 50001 (limit_samples) sdi 0x332819f0 cg NULL -> uint64 0
    limit_samples: 0 (current)
sr: [00:00.445475] hwdriver: sr_config_get(): key 50000 (limit_time) sdi 0x332819f0 cg NULL -> uint64 0
    limit_time: 0 (current)
sr: [00:00.498095] korad-kaxxxxp: Sleeping for processing 52367 usec
sr: [00:00.498153] korad-kaxxxxp: Sending 'VOUT1?'.
sr: [00:00.498238] serial: Wrote 8/8 bytes.
sr: [00:00.498780] korad-kaxxxxp: want 6 bytes, timeout/retry: init 16/100, later 13/1.
sr: [00:00.523378] serial: Read 6/6 bytes.
sr: [00:00.523411] korad-kaxxxxp: got 6 bytes, received: '00.00'.
sr: [00:00.523478] korad-kaxxxxp: value: 0.000000
sr: [00:00.523562] hwdriver: sr_config_get(): key 30029 (voltage) sdi 0x332819f0 cg NULL -> 0.0
    voltage: 0.000000 (current)
sr: [00:00.578439] korad-kaxxxxp: Sleeping for processing 54614 usec
sr: [00:00.578515] korad-kaxxxxp: Sending 'VSET1?'.
sr: [00:00.578597] serial: Wrote 8/8 bytes.
sr: [00:00.579164] korad-kaxxxxp: want 6 bytes, timeout/retry: init 16/100, later 13/1.
sr: [00:00.595371] serial: Read 5/6 bytes.
sr: [00:00.596127] serial: Read 1/1 bytes.
sr: [00:00.596216] korad-kaxxxxp: got 6 bytes, received: '08.00'.
sr: [00:00.596256] korad-kaxxxxp: value: 0.000000
sr: [00:00.596321] hwdriver: sr_config_get(): key 30030 (voltage_target) sdi 0x332819f0 cg NULL -> 0.0
sr: [00:00.596480] hwdriver: sr_config_list(): key 30030 (voltage_target) sdi 0x332819f0 cg NULL -> [0.0, 31.0, 0.01]
    voltage_target: 0.000000 (current), 31.000000, 0.010000
sr: [00:00.658790] korad-kaxxxxp: Sleeping for processing 62042 usec
sr: [00:00.658856] korad-kaxxxxp: Sending 'IOUT1?'.
sr: [00:00.658998] serial: Wrote 8/8 bytes.
sr: [00:00.659635] korad-kaxxxxp: want 6 bytes, timeout/retry: init 16/100, later 13/1.
sr: [00:00.675800] serial: Read 3/6 bytes.
sr: [00:00.678969] serial: Read 3/3 bytes.
sr: [00:00.679004] korad-kaxxxxp: got 6 bytes, received: '0.000'.
sr: [00:00.679048] korad-kaxxxxp: value: 0.000000
sr: [00:00.679112] hwdriver: sr_config_get(): key 30031 (current) sdi 0x332819f0 cg NULL -> 0.0
    current: 0.000000 (current)
sr: [00:00.739187] korad-kaxxxxp: Sleeping for processing 59813 usec
sr: [00:00.739225] korad-kaxxxxp: Sending 'ISET1?'.
sr: [00:00.739325] serial: Wrote 8/8 bytes.
sr: [00:00.739917] korad-kaxxxxp: want 6 bytes, timeout/retry: init 16/100, later 13/1.
sr: [00:00.756155] serial: Read 2/6 bytes.
sr: [00:00.759942] serial: Read 4/4 bytes.
sr: [00:00.759987] korad-kaxxxxp: got 6 bytes, received: '0.000'.
sr: [00:00.760027] korad-kaxxxxp: value: 0.000000
sr: [00:00.770296] hwdriver: sr_config_get(): key 30032 (current_limit) sdi 0x332819f0 cg NULL -> 0.0
sr: [00:00.770448] hwdriver: sr_config_list(): key 30032 (current_limit) sdi 0x332819f0 cg NULL -> [0.0, 5.0999999999999996, 0.001]
    current_limit: 0.000000 (current), 5.100000, 0.001000
sr: [00:00.819532] korad-kaxxxxp: Sleeping for processing 48801 usec
sr: [00:00.819581] korad-kaxxxxp: Sending 'STATUS?'.
sr: [00:00.819769] serial: Wrote 9/9 bytes.
sr: [00:00.820323] korad-kaxxxxp: want 1 bytes, timeout/retry: init 11/100, later 13/1.
sr: [00:00.836844] serial: Read 1/1 bytes.
sr: [00:00.836897] korad-kaxxxxp: got 1 bytes, received: '0'.
sr: [00:00.836940] korad-kaxxxxp: Status: 0x30
sr: [00:00.836979] korad-kaxxxxp: Status: CH1: constant current CH2: constant current. Tracking would be series and independent. Output is disabled. OCP is enabled, OVP is disabled. Device is beeping.
sr: [00:00.837043] hwdriver: sr_config_get(): key 30033 (enabled) sdi 0x332819f0 cg NULL -> false
    enabled: on, off (current)
sr: [00:00.899966] korad-kaxxxxp: Sleeping for processing 62699 usec
sr: [00:00.900026] korad-kaxxxxp: Sending 'STATUS?'.
sr: [00:00.900125] serial: Wrote 9/9 bytes.
sr: [00:00.900817] korad-kaxxxxp: want 1 bytes, timeout/retry: init 11/100, later 13/1.
sr: [00:00.900910] serial: Read 1/1 bytes.
sr: [00:00.900949] korad-kaxxxxp: got 1 bytes, received: '0'.
sr: [00:00.900994] korad-kaxxxxp: Status: 0x30
sr: [00:00.901031] korad-kaxxxxp: Status: CH1: constant current CH2: constant current. Tracking would be series and independent. Output is disabled. OCP is enabled, OVP is disabled. Device is beeping.
sr: [00:00.901099] hwdriver: sr_config_get(): key 30043 (regulation) sdi 0x332819f0 cg NULL -> 'CC'
    regulation: CC (current)
sr: [00:00.980298] korad-kaxxxxp: Sleeping for processing 78969 usec
sr: [00:00.980352] korad-kaxxxxp: Sending 'STATUS?'.
sr: [00:00.980478] serial: Wrote 9/9 bytes.
sr: [00:00.981147] korad-kaxxxxp: want 1 bytes, timeout/retry: init 11/100, later 13/1.
sr: [00:00.981243] serial: Read 1/1 bytes.
sr: [00:00.981283] korad-kaxxxxp: got 1 bytes, received: '0'.
sr: [00:00.981394] korad-kaxxxxp: Status: 0x30
sr: [00:00.981452] korad-kaxxxxp: Status: CH1: constant current CH2: constant current. Tracking would be series and independent. Output is disabled. OCP is enabled, OVP is disabled. Device is beeping.
sr: [00:00.981529] hwdriver: sr_config_get(): key 30038 (ocp_enabled) sdi 0x332819f0 cg NULL -> true
    ocp_enabled: on (current), off
sr: [00:01.060637] korad-kaxxxxp: Sleeping for processing 78929 usec
sr: [00:01.060659] korad-kaxxxxp: Sending 'STATUS?'.
sr: [00:01.060695] serial: Wrote 9/9 bytes.
sr: [00:01.061053] korad-kaxxxxp: want 1 bytes, timeout/retry: init 11/100, later 13/1.
sr: [00:01.061102] serial: Read 1/1 bytes.
sr: [00:01.061114] korad-kaxxxxp: got 1 bytes, received: ''.
sr: [00:01.061127] korad-kaxxxxp: Status: 0x0a
sr: [00:01.061140] korad-kaxxxxp: Status: CH1: constant current CH2: constant voltage. Tracking would be series and tracking. Output is disabled. OCP is disabled, OVP is disabled. Device is silent.
sr: [00:01.061162] hwdriver: sr_config_get(): key 30035 (ovp_enabled) sdi 0x332819f0 cg NULL -> false
    ovp_enabled: on, off (current)
sr: [00:01.061182] device: korad-kaxxxxp: Closing device instance.
sr: [00:01.061195] serial: Closing serial port /dev/ttyUSB0.
sr: [00:01.074325] hwdriver: Cleaning up all drivers.

Setting values works, reading them back does not.

@cnf
Copy link

cnf commented May 28, 2025

bit further poking it, sending *IDN?\\n\r doesn't work, hell sending *IDN?\\n doesn't work!

Who the hell implemented this thing, and why did I end up with one? 😁

@MartinJM
Copy link
Author

MartinJM commented May 28, 2025

Who the hell implemented this thing, and why did I end up with one? 😁

Hahaha good questions :P

set add_newline to false in the discovery
sending *IDN?\n\r doesn't work

Hmmm I was hoping that either of these would work for your device, as then there would be overlap with my device as well.

I'm wondering if we can just send *IDN?, *IDN?\n, and *IDN?\\n in a row to always get an answer? I cannot test right now but will do so when I can.


Edit: So for my device it seems to be that it needs to start with *IDN? and then needs a newline some time after, but what comes in between does not matter. So this also only prints the identification output: *IDN?STATUS?\n.

@cnf
Copy link

cnf commented Sep 14, 2025

I don't know if you are still wanting to know @MartinJM ... it seems you just append 'n'

± tio VELLEMAN
[18:07:04.658] tio 3.9
[18:07:04.658] Press ctrl-t q to quit
[18:07:04.660] Connected to /dev/tty-CP2102
[18:07:04.661] Running script
*IDN?nQJE3005PV1.0
VSET1:10.000nVSET1?n10.00
ISET1:01.000nISET1?n0.000
ISET1:1.123nISET1?n1.123

no \n no /n no \\n .... just a single n...

@cnf
Copy link

cnf commented Sep 14, 2025

± sigrok-cli -d korad-kaxxxxp:conn=/dev/tty-CP2102 --scan
The following devices were found:
korad-kaxxxxp:conn=/dev/tty-CP2102 - Velleman LABPS3005DN with 2 channels: V I

I can SET values, and enable/disable the output, but sigrok-cli can't read them back it seems:

Set / Read
OptiNix home-manager/patches ± sigrok-cli -d korad-kaxxxxp:conn=/dev/tty-CP2102 --config voltage_target=07.000 --set
OptiNix home-manager/patches ± sigrok-cli -d korad-kaxxxxp:conn=/dev/tty-CP2102 --show
Driver functions:
    Power supply
Scan options:
    conn
    serialcomm
    force_detect
korad-kaxxxxp:conn=/dev/tty-CP2102 - Velleman LABPS3005DN with 2 channels: V I
Supported configuration options:
    conn: /dev/tty-CP2102 (current)
    continuous: on, off
    limit_samples: 0 (current)
    limit_time: 0 (current)
    voltage: 0.000000 (current)
    voltage_target: 0.000000 (current), 31.000000, 0.010000
    current: 0.000000 (current)
    current_limit: 0.000000 (current), 5.100000, 0.001000
    enabled: on, off (current)
    regulation: CC (current)
    ocp_enabled: on (current), off
    ovp_enabled: on, off (current)
OptiNix home-manager/patches ± sigrok-cli -d korad-kaxxxxp:conn=/dev/tty-CP2102 --config current_limit=0.123 --set
OptiNix home-manager/patches ± sigrok-cli -d korad-kaxxxxp:conn=/dev/tty-CP2102 --show
Driver functions:
    Power supply
Scan options:
    conn
    serialcomm
    force_detect
korad-kaxxxxp:conn=/dev/tty-CP2102 - Velleman LABPS3005DN with 2 channels: V I
Supported configuration options:
    conn: /dev/tty-CP2102 (current)
    continuous: on, off
    limit_samples: 0 (current)
    limit_time: 0 (current)
    voltage: 0.000000 (current)
    voltage_target: 0.000000 (current), 31.000000, 0.010000
    current: 0.000000 (current)
    current_limit: 0.000000 (current), 5.100000, 0.001000
    enabled: on, off (current)
    regulation: CC (current)
    ocp_enabled: on (current), off
    ovp_enabled: on, off (current)
OptiNix home-manager/patches ±  

@cnf
Copy link

cnf commented Sep 14, 2025

Yeah, so control works, but reading back doesn't. from smuview i can enable the output, but it doesn't see it is on, and the voltage readings jump all over the place...

@MartinJM
Copy link
Author

Hey, yeah always interested in stuff like this.

it seems you just append 'n'

And this is giving me a good laugh, so thank you for sharing. Is your device still fine if you send *IDN?n\n (with the \n being an actual newline of course)? That should be supported by my device as well, so if that works it might be a solution for the identification. Then after the identification we know which device it is so we can use exactly what the device requires.

Yeah, so control works, but reading back doesn't. from smuview i can enable the output, but it doesn't see it is on, and the voltage readings jump all over the place...

Hmmm that's too bad. Is this with just the n, or did you also try this with the \n?

@cnf
Copy link

cnf commented Sep 21, 2025

Hey, yeah always interested in stuff like this.

it seems you just append 'n'

And this is giving me a good laugh, so thank you for sharing. Is your device still fine if you send *IDN?n\n (with the \n being an actual newline of course)? That should be supported by my device as well, so if that works it might be a solution for the identification. Then after the identification we know which device it is so we can use exactly what the device requires.

Yep, it doesn't seem to care about the \n, you just send the normal commands, and then 1 or 0 characters, and then an n, if an \n follows, it doesn't seem to notice much.

Yeah, so control works, but reading back doesn't. from smuview i can enable the output, but it doesn't see it is on, and the voltage readings jump all over the place...

Hmmm that's too bad. Is this with just the n, or did you also try this with the \n?

either, if I run sigrok-cli -d korad-kaxxxxp:conn=/dev/tty-CP2102 --continuous it just gives me 0 on everything, except occastionally. I do not know why.

sigrok-cli -d korad-kaxxxxp:conn=/dev/tty-CP2102 --continuous
I: 0 mA DC
V: 0.00 V DC
I: 0 mA DC
V: 0.00 V DC
I: 0 mA DC
V: 0.00 V DC
I: 0 mA DC
V: 0.00 V DC
I: 0 mA DC
V: 0.00 V DC
I: 0 mA DC
V: 0.00 V DC
I: 0 mA DC
V: 0.00 V DC
I: 0 mA DC
V: 0.00 V DC
I: 0 mA DC
V: 0.00 V DC
I: 0 mA DC
V: 0.00 V DC
META regulation: CV
I: 0 mA DC
V: 0.00 V DC
META regulation: CC
I: 0 mA DC
V: 0.00 V DC
META regulation: CV
META ocp_enabled: 0
I: 0 mA DC
V: 0.00 V DC
META ocp_enabled: 1
I: 0 mA DC
V: 0.00 V DC
META ocp_enabled: 0
I: 0 mA DC
V: 0.00 V DC
META regulation: CC
META ocp_enabled: 1
I: 0 mA DC
V: 0.00 V DC
META regulation: CV
META ocp_enabled: 0
I: 0 mA DC
V: 0.00 V DC
META regulation: CC
META ocp_enabled: 1
I: 0 mA DC
V: 0.00 V DC
I: 0 mA DC
V: 0.00 V DC
I: 0 mA DC
V: 0.00 V DC
I: 0 mA DC
V: 0.00 V DC
I: 0 mA DC
V: 0.00 V DC
I: 0 mA DC
V: 0.00 V DC
I: 0 mA DC
V: 0.00 V DC
I: 0 mA DC
V: 0.00 V DC
I: 0 mA DC
V: 0.00 V DC
META regulation: CV
I: 0 mA DC
V: 0.00 V DC
META regulation: CC
I: 0 mA DC
V: 0.00 V DC
META regulation: CV
META ocp_enabled: 0
I: 0 mA DC
V: 0.00 V DC
META ocp_enabled: 1
I: 0 mA DC
V: 0.00 V DC
META ocp_enabled: 0
I: 0 mA DC
V: 0.00 V DC
META regulation: CC
META ocp_enabled: 1
I: 0 mA DC
V: 0.00 V DC
META regulation: CV
META ocp_enabled: 0
I: 0 mA DC
V: 0.00 V DC
META regulation: CC
META ocp_enabled: 1
I: 0 mA DC
V: 0.00 V DC
I: 0 mA DC
V: 0.00 V DC
I: 0 mA DC
V: 0.00 V DC
I: 0 mA DC
V: 0.00 V DC
I: 0 mA DC
V: 0.00 V DC
I: 0 mA DC
V: 0.00 V DC
I: 0 mA DC
V: 0.00 V DC
I: 0 mA DC
V: 0.00 V DC
I: 0 mA DC
V: 0.00 V DC
META regulation: CV
I: 0 mA DC
V: 0.00 V DC
META regulation: CC
I: 0 mA DC
V: 0.00 V DC
META regulation: CV
META ocp_enabled: 0
I: 0 mA DC
V: 0.00 V DC
META ocp_enabled: 1
I: 0 mA DC
V: 0.00 V DC
META ocp_enabled: 0
I: 0 mA DC
V: 0.00 V DC
META regulation: CC
META ocp_enabled: 1
I: 0 mA DC
V: 0.00 V DC
META regulation: CV
META ocp_enabled: 0
I: 0 mA DC
V: 0.00 V DC
META regulation: CC
META ocp_enabled: 1
I: 0 mA DC
V: 0.00 V DC
I: 0 mA DC
V: 0.00 V DC
I: 0 mA DC
V: 0.00 V DC
I: 0 mA DC
V: 0.00 V DC
I: 0 mA DC
V: 0.00 V DC
I: 0 mA DC
V: 0.00 V DC
I: 0 mA DC
V: 0.00 V DC
I: 0 mA DC
V: 0.00 V DC
I: 0 mA DC
V: 0.00 V DC
META regulation: CV
I: 0 mA DC
V: 0.00 V DC
META regulation: CC
I: 0 mA DC
V: 0.00 V DC
META regulation: CV
META ocp_enabled: 0
I: 0 mA DC
V: 0.00 V DC
META ocp_enabled: 1
I: 0 mA DC
V: 0.00 V DC
META ocp_enabled: 0
I: 0 mA DC
V: 0.00 V DC
META regulation: CC
META ocp_enabled: 1
I: 0 mA DC
V: 0.00 V DC
META regulation: CV
META ocp_enabled: 0
I: 0 mA DC
V: 0.00 V DC
META regulation: CC
META ocp_enabled: 1
I: 0 mA DC
V: 0.00 V DC
I: 0 mA DC
V: 0.00 V DC
I: 0 mA DC
V: 0.00 V DC
I: 0 mA DC
V: 0.00 V DC
I: 0 mA DC
V: 0.00 V DC
I: 0 mA DC
V: 0.00 V DC
I: 0 mA DC
V: 0.00 V DC
I: 0 mA DC
V: 0.00 V DC
I: 0 mA DC
V: 0.00 V DC
META regulation: CV
I: 0 mA DC
OptiNix ~ $ tio /dev/tty-CP2102 -b 9600 
[18:41:52.166] tio 3.9
[18:41:52.166] Press ctrl-t q to quit
[18:41:52.168] Connected to /dev/tty-CP2102
[18:41:57.683] Switched local echo on
*IDN?nQJE3005PV1.0
VSET1?n09.00
ISET1?n0.000
ISET1:0.123nISET1?n0.123

@abraxa
Copy link
Member

abraxa commented Nov 9, 2025

What's your take on this PR, @MartinJM? Should I merge it as-is or do you intend to make any changes to it?

That said, please only use /* ... */ for comments, not //

return ret;
}

sr_dbg("Sending '%s'.", cmd);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This logs cmd, but actual command sent is newcmd.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it would only show up as an additional newline, which I thought would not be wanted. Though on second thought I agree that it makes more sense to actually log what is being sent (including the newline).

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, right, but still makes sense to log what is written, or maybe escape it into the string so it's clear.

Copy link
Author

@MartinJM MartinJM Nov 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's only for debug logging though, so I'd rather not do any extra processing. I agree that it makes sense to log what is written, will change it.

For the changes, do you prefer multiple smaller commits, one larger commit containing multiple smaller changes, or a change of the original commit (including a force-push)?


EDIT:

The default logger strips the newlines:

libsigrok/src/log.c

Lines 228 to 243 in c79a973

/* Copy the string. Strip unwanted line breaks. */
output = g_malloc(raw_len + 1);
if (!output) {
g_free(raw_output);
return SR_ERR;
}
out_ptr = output;
raw_ptr = raw_output;
while (*raw_ptr) {
c = *raw_ptr++;
if (c == '\r' || c == '\n')
continue;
*out_ptr++ = c;
}
*out_ptr = '\0';
g_free(raw_output);

I'll still make the change, as I understand that the logging function can be changed.

retries = retries_later;
}

if (strip_newline && buf[received-1] == 0x0a)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest to use '\n' instead of 0x0a, it's more clear for people who don't have ASCII burned in their retinas :-)

@MartinJM
Copy link
Author

Hey sorry for the slow answer. I have been busy - including a move that requires me to set up my dev environment for this again.

This should still work for the Korad KA3005PS (I'll double-check when I have a dev env for this again), but I cannot verify if it still works on the other devices listed:

{"Korad", "KA3005P", "", 1, volts_30, amps_5,
KORAD_QUIRK_ID_TRAILING},
{"Korad", "KD3005P", "", 1, volts_30, amps_5, 0},
{"Korad", "KD6005P", "", 1, volts_60, amps_5, 0},
{"RND", "KA3005P", "RND 320-KA3005P", 1, volts_30, amps_5,
KORAD_QUIRK_ID_OPT_VERSION},
{"RND", "KD3005P", "RND 320-KD3005P", 1, volts_30, amps_5,
KORAD_QUIRK_ID_OPT_VERSION},
{"Stamos Soldering", "S-LS-31", "", 1, volts_30, amps_5,
KORAD_QUIRK_ID_NO_VENDOR},
{"Tenma", "72-2535", "", 1, volts_30, amps_3, 0},
{"Tenma", "72-2540", "", 1, volts_30, amps_5, 0},
{"Tenma", "72-2550", "", 1, volts_60, amps_3, 0},
{"Tenma", "72-2705", "", 1, volts_30, amps_3, 0},
{"Tenma", "72-2710", "", 1, volts_30, amps_5, 0},
{"Velleman", "LABPS3005D", "", 1, volts_30, amps_5,
KORAD_QUIRK_LABPS_OVP_EN},
{"Velleman", "PS3005D V1.3", "VELLEMANPS3005DV1.3" , 1, volts_30, amps_5,
KORAD_QUIRK_ID_TRAILING | KORAD_QUIRK_SLOW_PROCESSING},
{"Velleman", "PS3005D", "", 1, volts_30, amps_5, 0},

If we want to err on the safe side, I'd wait with merging this until someone with one of those devices tests the changes as well. On the other hand, that can take forever - but I'm not sure if that warrants testing on production.

We do know that the current state does NOT work for the Velleman LABPS3005DN, as reported by @cnf. There might still be common ground, as I believe there may still be options with this:

Yep, it doesn't seem to care about the \n, you just send the normal commands, and then 1 or 0 characters, and then an n, if an \n follows, it doesn't seem to notice much.

But for this I can also not verify if it will break any of the other devices. They seem to be a bit more finicky than I'd like.

Thank you for review and comments, will fix/reply to them separately :)

@Krakonos
Copy link

@MartinJM No worries, thanks for taking the time to get back to us.

I wonder if @cnf could check if the LABPS3005DN works with current master? Based on the conversation (which is pretty long and I admittedly only read the first and last third to see if it got resolved) I'm not clear if it is actually this PR that breaks it.

Also, it might be possible to scan in two phases? I suspect some devices will compare the whole "packet" received (I suspect this is USB serial?u), so if there is anything extra, it would screw things up. On the other hand, I think we can just probe the device twice and see if it responds to the other probe if the first one doesn't match. I'll check at work, we have some Korad PSUs, we might get lucky.

@MartinJM
Copy link
Author

This should still work for the Korad KA3005PS (I'll double-check when I have a dev env for this again)

I tested it, and it still works perfectly fine!

Based on the conversation I'm not clear if it is actually this PR that breaks it.

It was not supported on master either. It needs a literal n character at the end of the messages (where mine needs a newline: \n).

Also, it might be possible to scan in two phases?

I like that idea! Will switch to that instead - marking the PR as WIP in the meantime. That should then also be a solution to the Velleman LABPS3005DN - as that can then be a third phase.

I'll check at work, we have some Korad PSUs, we might get lucky.

Though I am still curious if it still works on the other PSUs, if you don't mind checking. I tend to be curious :)

(which is pretty long and I admittedly only read the first and last third to see if it got resolved)

Hahaha, completely fair :)

@MartinJM MartinJM changed the title Add support for Korad KA3005PS WIP: Add support for Korad KA3005PS Nov 27, 2025
@MartinJM MartinJM marked this pull request as draft November 27, 2025 22:23
@MartinJM MartinJM changed the title WIP: Add support for Korad KA3005PS Add support for Korad KA3005PS Nov 27, 2025
@Krakonos
Copy link

Hi! Good news, bad news.

Good news is, I got a powersupply we can test with. It' Korad KA3003P. The firmware reports KORADKA3005PV2.0 and works on master (identifies, sets output voltage).

[...]
sr: [00:00.012652] serial: Opening serial port '/dev/ttyACM1' (flags 1).
sr: [00:00.015103] serial: Parsing parameters from "9600/8n1".
sr: [00:00.015147] serial: Got params: rate 9600, frame 8/0/1, flow 0, rts -1, dtr -1.
sr: [00:00.015150] serial: Setting serial parameters on port /dev/ttyACM1.
sr: [00:00.015162] serial: DBG: serial_set_params() rate 9600, 8n1
sr: [00:00.015166] serial: Flushing serial port /dev/ttyACM1.
sr: [00:00.015172] korad-kaxxxxp: Want max 48 bytes.
sr: [00:00.015177] korad-kaxxxxp: Sending '*IDN?'.
sr: [00:00.015187] serial: Wrote 5/5 bytes.
sr: [00:00.015195] korad-kaxxxxp: want 48 bytes, timeout/retry: init 60/100, later 13/1.
sr: [00:00.075268] serial: Read 17/48 bytes.
sr: [00:00.088347] korad-kaxxxxp: receive timed out, want 48, received 17.
sr: [00:00.088354] korad-kaxxxxp: got 17 bytes, received: 'KORADKA3005PV2.0'.
sr: [00:00.088358] korad-kaxxxxp: Received: 17, KORADKA3005PV2.0
sr: [00:00.088363] korad-kaxxxxp: Looking up: [KORADKA3005PV2.0].
sr: [00:00.088368] korad-kaxxxxp: Matches generic '[vendor] model [vers] [trail]' pattern.
sr: [00:00.088372] korad-kaxxxxp: Found: [Korad] [KA3005P]
sr: [00:00.088376] korad-kaxxxxp: Found: Korad KA3005P (idx 0).
[...]

Now, the bad news is the added newline breaks the detection:

[...]
sr: [00:00.012693] serial: Opening serial port '/dev/ttyACM1' (flags 1).
sr: [00:00.015128] serial: Parsing parameters from "9600/8n1".
sr: [00:00.015191] serial: Got params: rate 9600, frame 8/0/1, flow 0, rts -1, dtr -1.
sr: [00:00.015198] serial: Setting serial parameters on port /dev/ttyACM1.
sr: [00:00.015232] serial: DBG: serial_set_params() rate 9600, 8n1
sr: [00:00.015242] serial: Flushing serial port /dev/ttyACM1.
sr: [00:00.015252] korad-kaxxxxp: Want max 48 bytes.
sr: [00:00.015262] korad-kaxxxxp: Sending '*IDN?'.
sr: [00:00.015279] serial: Wrote 6/6 bytes.
sr: [00:00.015294] korad-kaxxxxp: want 48 bytes, timeout/retry: init 60/100, later 13/1.
sr: [00:06.022112] korad-kaxxxxp: got 0 bytes, received: ''.
sr: [00:06.022122] korad-kaxxxxp: Received: 0, 
sr: [00:06.022132] korad-kaxxxxp: Unsupported model ID '', aborting.
sr: [00:06.022139] hwdriver: Scan found 0 devices (korad-kaxxxxp).
sr: [00:06.022152] hwdriver: Cleaning up all drivers.
[...]

And I'll let it finish with some good news. I tried retrying the *IDN? query first with newline, second with no newline (although I would recommend the opposite way to keep compatibility in case some other slightly different devices don't like it) and it works. The only issue I found is that timeout is currently set to 60ms and 100 retries, which results in 6s wait time. I dropped it down to 60*5 and it works, my PSU replied within 60ms (I didn't read the serial code too carefully and suspect this is not a coincidence, since the buffer is longer, the serial_read_blocking waits for the timeout time... Not ideal, though probably needed due to no delimiter appears to be sent by the PSU after the model number.

[...]
sr: [00:00.013357] serial: Opening serial port '/dev/ttyACM1' (flags 1).
sr: [00:00.015828] serial: Parsing parameters from "9600/8n1".
sr: [00:00.015894] serial: Got params: rate 9600, frame 8/0/1, flow 0, rts -1, dtr -1.
sr: [00:00.015900] serial: Setting serial parameters on port /dev/ttyACM1.
sr: [00:00.015919] serial: DBG: serial_set_params() rate 9600, 8n1
sr: [00:00.015924] serial: Flushing serial port /dev/ttyACM1.
sr: [00:00.015933] korad-kaxxxxp: Want max 48 bytes.
sr: [00:00.015943] korad-kaxxxxp: Sending '*IDN?'.
sr: [00:00.015957] serial: Wrote 6/6 bytes.
sr: [00:00.015970] korad-kaxxxxp: want 48 bytes, timeout/retry: init 60/5, later 13/1.
sr: [00:00.316906] korad-kaxxxxp: got 0 bytes, received: ''.
sr: [00:00.316919] korad-kaxxxxp: Received: 0, 
sr: [00:00.316928] korad-kaxxxxp: Sending '*IDN?'.
sr: [00:00.316953] serial: Wrote 5/5 bytes.
sr: [00:00.316969] korad-kaxxxxp: want 48 bytes, timeout/retry: init 60/5, later 13/1.
sr: [00:00.377054] serial: Read 17/48 bytes.
sr: [00:00.390143] korad-kaxxxxp: receive timed out, want 48, received 17.
sr: [00:00.390154] korad-kaxxxxp: got 17 bytes, received: 'KORADKA3005PV2.0'.
sr: [00:00.390162] korad-kaxxxxp: Received: 17, KORADKA3005PV2.0
sr: [00:00.390174] korad-kaxxxxp: Looking up: [KORADKA3005PV2.0].
sr: [00:00.390187] korad-kaxxxxp: Matches generic '[vendor] model [vers] [trail]' pattern.
sr: [00:00.390194] korad-kaxxxxp: Found: [Korad] [KA3005P]
sr: [00:00.390201] korad-kaxxxxp: Found: Korad KA3005P (idx 1).
[...]

@Krakonos
Copy link

@MartinJM I also noticed the PR #219 seems to do a similar thing, including the two phase scan. I originally though we could add the PN here too, but it seems to require more stuff there. I suggest we merge this PR first, since it's more up to date, we can then update the other one just to modify the protocol part (if the original author is still interested/somebody has the device for testing).

@MartinJM
Copy link
Author

MartinJM commented Nov 28, 2025

Good news is, I got a powersupply we can test with

Cool, thank you for testing! Yeah a bit sad it didn't work out of the box like this, but good to know the other options.

I also noticed the PR #219 seems to do a similar thing

Oh good find, not sure how I missed that before writing/opening this...

including the two phase scan

I think they just send *IDN? first, and if they don't get a response, then send another \n. So not a full second scan. Interesting idea though, which may work fine - even when also considering the Velleman DN (*IDN? + n\n should work I think). I'll add that in first as it should be a simpler change than a full second (and potentially even third) scan.


EDIT:

The only issue I found is that timeout is currently set to 60ms and 100 retries, which results in 6s wait time. I dropped it down to 60*5 and it works

This is a good point that I hadn't considered yet. Would it work fine if we just send *IDN?n\n*IDN? in a single message? It works for mine, I think it will work for the Velleman DN, so if it also works for yours then that may be the fastest yet complete solution?

@MartinJM
Copy link
Author

@Krakonos I pushed a version that uses *IDN?\n*IDN? to identify the hardware. Works fine on mine, if you still have the Korad KA3003P available, can you please test if that also works fine on that? If it works it would avoid having to wait for a timeout for all devices.

@Krakonos
Copy link

@MartinJM Good attempt, but unfortunately, it doesn't work. The PSU does not reply unless I give at least some delay there. It likely processes a command after some period of inactivity. I have zero idea how somebody thought this is a good idea.

In any case, this works:

diff --git a/src/hardware/korad-kaxxxxp/api.c b/src/hardware/korad-kaxxxxp/api.c
index 7e4f2eb7..515970af 100644
--- a/src/hardware/korad-kaxxxxp/api.c
+++ b/src/hardware/korad-kaxxxxp/api.c
@@ -287,7 +287,9 @@ static GSList *scan(struct sr_dev_driver *di, GSList *options)
         * But the normal ones need an `*IDN?` without anything at the end.
         * So this sends a combination that satisfies these constraints.
         */
-       ret = korad_kaxxxxp_send_cmd(serial, "*IDN?\n*IDN?", false);
+       ret = korad_kaxxxxp_send_cmd(serial, "*IDN?\n", false);
+        g_usleep(40000);
+       ret = korad_kaxxxxp_send_cmd(serial, "*IDN?", false);
        if (ret < 0)
                return NULL;

The value 40000 is the minimum I found working, 30000 already fails. I'd suggest using higher delay Not the most elegant, but might be an alternative. This device is not automatically scanned based on vid/pid (it uses a generic converter), so I don't think it is an issue.

I wonder if the following patch would work with your PSU?

diff --git a/src/hardware/korad-kaxxxxp/api.c b/src/hardware/korad-kaxxxxp/api.c
index 7e4f2eb7..1836af55 100644
--- a/src/hardware/korad-kaxxxxp/api.c
+++ b/src/hardware/korad-kaxxxxp/api.c
@@ -287,7 +287,9 @@ static GSList *scan(struct sr_dev_driver *di, GSList *options)
         * But the normal ones need an `*IDN?` without anything at the end.
         * So this sends a combination that satisfies these constraints.
         */
-       ret = korad_kaxxxxp_send_cmd(serial, "*IDN?\n*IDN?", false);
+       ret = korad_kaxxxxp_send_cmd(serial, "*IDN?", false);
+        g_usleep(40000);
+       ret = korad_kaxxxxp_send_cmd(serial, "\n", false);
        if (ret < 0)
                return NULL;

@MartinJM
Copy link
Author

Good attempt, but unfortunately, it doesn't work.

Cool, thank you for trying!

I wonder if the following patch would work with your PSU?

Yeah the second patch works perfectly fine. The length of the sleep between the two doesn't seem to matter too much. I tried it with up to 10 seconds (g_usleep(10000000);) and it still works fine.

It also works as the following, which could also enable detecting the Velleman DN - if @cnf is still available for testing:

diff --git a/src/hardware/korad-kaxxxxp/api.c b/src/hardware/korad-kaxxxxp/api.c
index 7e4f2eb7..9b26ce7a 100644
--- a/src/hardware/korad-kaxxxxp/api.c
+++ b/src/hardware/korad-kaxxxxp/api.c
@@ -287,7 +287,9 @@ static GSList *scan(struct sr_dev_driver *di, GSList *options)
         * But the normal ones need an `*IDN?` without anything at the end.
         * So this sends a combination that satisfies these constraints.
         */
-       ret = korad_kaxxxxp_send_cmd(serial, "*IDN?\n*IDN?", false);
+       ret = korad_kaxxxxp_send_cmd(serial, "*IDN?", false);
+       g_usleep(500000);
+       ret = korad_kaxxxxp_send_cmd(serial, "n\n", false);
        if (ret < 0)
                return NULL;

The current state of this PR actually doesn't work too well for my device. When closing the connection it seems to restart the serial connection, which is not ideal. With your patch it doesn't do that, so it's even better for my device as well!

@Krakonos
Copy link

Ok, I think we can call that a decent solution then. If @cnf will get in touch to test some of the variants we can tune it a little more. I'm not sure about the "n\n", didn't it require the backslash character? If so, I'd say it should be "\\n\n". In any case. my PSU doesn't care, since it evaluates the command right after the timeout.

On other note, could you please rebase this on top of master and clean up the commits (squash the iterative commits and update commit messages to prefix with "drivername: "? Once this is done, I'd be happy to recommend this for merging.

@Krakonos
Copy link

Also:

The current state of this PR actually doesn't work too well for my device. When closing the connection it seems to restart the serial connection, which is not ideal. With your patch it doesn't do that, so it's even better for my device as well!

I don't see how these changes could affect what happens on the device after the connection closes. But these PSUs are weird.

The only thing I see on mine is it beeps after some inactivity (30s? give or take), but does not seem reset any state.

@MartinJM
Copy link
Author

no \n no /n no \n .... just a single n...
#269 (comment)

Should just be a single n. Attached is a more complete patch for the Velleman based on the earlier work, but I think it makes sense putting that in a new PR at this point.
0001-Add-Velleman-DN-support.patch

On other note, could you please rebase this on top of master and clean up the commits (squash the iterative commits and update commit messages to prefix with "drivername: "? Once this is done, I'd be happy to recommend this for merging.

Will do! I'll remove the draft status when complete.

I don't see how these changes could affect what happens on the device after the connection closes. But these PSUs are weird.

Yeah I'm not sure either. But as long as this version works I'm fine with it :)

@MartinJM MartinJM force-pushed the add_device_korad_ka3005ps branch from 6dd6d90 to 6a8f93c Compare November 28, 2025 13:07
@MartinJM MartinJM force-pushed the add_device_korad_ka3005ps branch from 6a8f93c to a421746 Compare November 28, 2025 13:12
@MartinJM MartinJM marked this pull request as ready for review November 28, 2025 13:14
@Krakonos
Copy link

@MartinJM Great, thanks! I gave it a quick test and all seems good.

@cnf In case you're interested, please try the patch posted above to see if it resolves your issue as well. Tag us here/reach out on the mailing list if so, we'll open and merge a new PR.

@abraxa Please check this PR out, I believe it is good to go for merging.

PS: I wonder if it's possible to flash/cross flash those PSUs to some common decent firmware. These are not the only quirks, I recall some people having more serious functionality related issues...

@cnf
Copy link

cnf commented Nov 30, 2025

@MartinJM Great, thanks! I gave it a quick test and all seems good.

@cnf In case you're interested, please try the patch posted above to see if it resolves your issue as well. Tag us here/reach out on the mailing list if so, we'll open and merge a new PR.

@abraxa Please check this PR out, I believe it is good to go for merging.

PS: I wonder if it's possible to flash/cross flash those PSUs to some common decent firmware. These are not the only quirks, I recall some people having more serious functionality related issues...

Sorry, only just say the messages... let me see what to apply where, and give it a test...

edit: i'm struggling a bit figuring out what patches to apply.

So i am using master, adding https://patch-diff.githubusercontent.com/raw/sigrokproject/libsigrok/pull/269.patch on top of that, and then

diff --git a/src/hardware/korad-kaxxxxp/api.c b/src/hardware/korad-kaxxxxp/api.c
index 7e4f2eb7..9b26ce7a 100644
--- a/src/hardware/korad-kaxxxxp/api.c
+++ b/src/hardware/korad-kaxxxxp/api.c
@@ -287,7 +287,9 @@ static GSList *scan(struct sr_dev_driver *di, GSList *options)
         * But the normal ones need an `*IDN?` without anything at the end.
         * So this sends a combination that satisfies these constraints.
         */
-       ret = korad_kaxxxxp_send_cmd(serial, "*IDN?\n*IDN?", false);
+       ret = korad_kaxxxxp_send_cmd(serial, "*IDN?", false);
+       g_usleep(500000);
+       ret = korad_kaxxxxp_send_cmd(serial, "n\n", false);
        if (ret < 0)
                return NULL;

Is that right? It is failing to apply for me.

@cnf
Copy link

cnf commented Nov 30, 2025

@MartinJM Great, thanks! I gave it a quick test and all seems good.

@cnf In case you're interested, please try the patch posted above to see if it resolves your issue as well. Tag us here/reach out on the mailing list if so, we'll open and merge a new PR.

@abraxa Please check this PR out, I believe it is good to go for merging.

PS: I wonder if it's possible to flash/cross flash those PSUs to some common decent firmware. These are not the only quirks, I recall some people having more serious functionality related issues...

Yes, a not crappy common firmware would be awesome :P I don't know if these are flashable, or if any code is available for them?

@MartinJM
Copy link
Author

edit: i'm struggling a bit figuring out what patches to apply.

The patch linked above ( https://github.com/user-attachments/files/23824054/0001-Add-Velleman-DN-support.patch ) on top of this branch.

PS: I wonder if it's possible to flash/cross flash those PSUs to some common decent firmware. These are not the only quirks, I recall some people having more serious functionality related issues...

Yes, a not crappy common firmware would be awesome :P I don't know if these are flashable, or if any code is available for them?

Could be nice if possible indeed. Though supporting devices out of the box is also good imo.

@cnf
Copy link

cnf commented Nov 30, 2025

edit: i'm struggling a bit figuring out what patches to apply.

The patch linked above ( https://github.com/user-attachments/files/23824054/0001-Add-Velleman-DN-support.patch ) on top of this branch.
ok, so i am, and I am getitng this error:

        > src/hardware/korad-kaxxxxp/protocol.c: In function 'korad_kaxxxxp_set_value':
┃        > src/hardware/korad-kaxxxxp/protocol.c:241:53: error: 'struct dev_context' has no member named 'set_output_enables'; did you mean 'set_output_enabled'?
┃        >   241 |                                 "OUTPUT%1d", (devc->set_output_enables) ? 1 : 0);
┃        >       |                                                     ^~~~~~~~~~~~~~~~~~
┃        >       |                                                     set_output_enabled
┃        >   CC       src/hardware/lecroy-logicstudio/api.lo

i guess it was renamed somewhere, and i'm patching against a wrong version somewhere...

@MartinJM
Copy link
Author

Oh no that's a typo in my patch - my bad. You can change set_output_enables to set_output_enabled to solve that.

@cnf
Copy link

cnf commented Nov 30, 2025

Oh no that's a typo in my patch - my bad. You can change set_output_enables to set_output_enabled to solve that.

yeah, I just figured that out :P

hydra ~ $ sigrok-cli -d korad-kaxxxxp:conn=/dev/tty-CP2102 --scan
The following devices were found:
korad-kaxxxxp:conn=/dev/tty-CP2102 - Velleman LABPS3005DN with 2 channels: V I
hydra ~ $ sigrok-cli -d korad-kaxxxxp:conn=/dev/tty-CP2102 --show
Driver functions:
    Power supply
Scan options:
    conn
    serialcomm
    force_detect
sr: korad-kaxxxxp: Unsupported model ID '', aborting.
No devices found.
zsh: exit 1     sigrok-cli -d korad-kaxxxxp:conn=/dev/tty-CP2102 --show
sigrok-cli --version
sigrok-cli 0.8.0

Libraries and features:
- libsigrok 0.6.0/4:0:0 (rt: 0.6.0/4:0:0).
 - Libs:
  - glib 2.84.4 (rt: 2.84.4/8404:4)
  - zlib 1.3.1
  - libzip 1.11.4
  - minilzo 2.10
  - libserialport 0.1.1/1:0:1 (rt: 0.1.1/1:0:1)
  - libusb-1.0 1.0.29.11953 API 0x0100010b
  - hidapi 0.15.0
  - bluez 5.83
  - libftdi 1.5
  - Host: x86_64-pc-linux-gnu, little-endian.
  - SCPI backends: TCP, serial, USBTMC.
- libsigrokdecode 0.6.0/4:0:0 (rt: 0.6.0/4:0:0).
 - Libs:
  - glib 2.84.4 (rt: 2.84.4/8404:4)
  - Python 3.13.7 / 0x30d07f0 (API 1013, ABI 3)
  - Host: x86_64-pc-linux-gnu, little-endian.

@Krakonos
Copy link

Krakonos commented Dec 2, 2025

@cnf Hi! Would you mind running this with -l 5 ? This looks like you got some response, but the model ID is empty string. I wonder what happened there? However, I suspect we might need to modify the patch:

ret = korad_kaxxxxp_send_cmd(serial, "n\n", false);

... I suggest to try in "\n\n", or maybe we'll need to split it off. It'd be nice if you could play with this code a little bit and figure out what gives you a response and what doesn't. Unfortunately, there are too many combinations that need to be tried out and I'm starting to think we might not be able to auto detect all of them easily.

@cnf
Copy link

cnf commented Dec 2, 2025

@Krakonos sure!

hydra ~ $ sigrok-cli -d korad-kaxxxxp:conn=/dev/tty-CP2102 --scan -l 5
sr: [00:00.000444] log: libsigrok loglevel set to 5.
sr: [00:00.000455] backend: libsigrok 0.6.0/4:0:0.
sr: [00:00.000478] backend: Libs: glib 2.84.4 (rt: 2.84.4/8404:4), zlib 1.3.1, libzip 1.11.4, minilzo 2.10, libserialport 0.1.1/1:0:1 (rt: 0.1.1/1:0:1), libusb-1.0 1.0.29.11953 API 0x0100010b, hidapi 0.15.0, bluez 5.83, libftdi 1.5.
sr: [00:00.000489] backend: Host: x86_64-pc-linux-gnu, little-endian.
sr: [00:00.000496] backend: SCPI backends: TCP, serial, USBTMC.
sr: [00:00.000509] backend: Firmware search paths:
sr: [00:00.000528] backend:  - /home/cnf/.local/share/sigrok-firmware
sr: [00:00.000533] backend:  - /nix/store/ryz4705fca0hszl5vrjrmyhlppwd61xp-libsigrok-unreleased/share/sigrok-firmware
sr: [00:00.000541] backend:  - /usr/share/sigrok-firmware
sr: [00:00.000575] backend:  - /var/lib/flatpak/exports/share/sigrok-firmware
sr: [00:00.000591] backend:  - /home/cnf/.local/share/flatpak/exports/share/sigrok-firmware
sr: [00:00.000608] backend:  - /nix/store/cad0ndrfahnbls1fzrr5kmpf3cvk9ypk-network-manager-applet-1.36.0/share/sigrok-firmware
sr: [00:00.000619] backend:  - /nix/store/nmgh4491415yj8v3pa9kaxgfz5kh1maf-gnome-mimeapps/share/sigrok-firmware
sr: [00:00.000625] backend:  - /nix/store/k4s8sd32q946mgddp7p0rlwral44b6c4-desktops/share/sigrok-firmware
sr: [00:00.000635] backend:  - /home/cnf/.local/share/flatpak/exports/share/sigrok-firmware
sr: [00:00.000646] backend:  - /var/lib/flatpak/exports/share/sigrok-firmware
sr: [00:00.000655] backend:  - /home/cnf/.nix-profile/share/sigrok-firmware
sr: [00:00.000666] backend:  - /nix/profile/share/sigrok-firmware
sr: [00:00.000673] backend:  - /home/cnf/.local/state/nix/profile/share/sigrok-firmware
sr: [00:00.000693] backend:  - /etc/profiles/per-user/cnf/share/sigrok-firmware
sr: [00:00.000699] backend:  - /nix/var/nix/profiles/default/share/sigrok-firmware
sr: [00:00.000713] backend:  - /run/current-system/sw/share/sigrok-firmware
sr: [00:00.000722] backend:  - /nix/store/n6npyz7c05f2bwhf8yidc1z0xnksc6vm-gnome-shell-48.2/share/gsettings-schemas/gnome-shell-48.2/sigrok-firmware
sr: [00:00.001668] backend: Sanity-checking all drivers.
sr: [00:00.001687] backend: Sanity-checking all input modules.
sr: [00:00.001698] backend: Sanity-checking all output modules.
sr: [00:00.002211] backend: Sanity-checking all transform modules.
srd: libsigrokdecode loglevel set to 5.
sr: [00:00.013934] hwdriver: sr_config_list(): key 2147418112 (NULL) sdi (nil) cg NULL -> [uint32 20000, 20001, 20003]
sr: [00:00.014217] serial: Opening serial port '/dev/tty-CP2102' (flags 1).
sr: [00:00.038023] serial: Parsing parameters from "9600/8n1".
sr: [00:00.038200] serial: Got params: rate 9600, frame 8/0/1, flow 0, rts -1, dtr -1.
sr: [00:00.038214] serial: Setting serial parameters on port /dev/tty-CP2102.
sr: [00:00.049261] serial: DBG: serial_set_params() rate 9600, 8n1
sr: [00:00.049336] serial: Flushing serial port /dev/tty-CP2102.
sr: [00:00.049380] korad-kaxxxxp: Want max 48 bytes.
sr: [00:00.049466] korad-kaxxxxp: Sending '*IDN?'.
sr: [00:00.049537] serial: Wrote 5/5 bytes.
sr: [00:00.149678] korad-kaxxxxp: Sending 'n'.
sr: [00:00.149738] serial: Wrote 2/2 bytes.
sr: [00:00.154163] korad-kaxxxxp: want 48 bytes, timeout/retry: init 60/100, later 13/1.
sr: [00:06.169078] korad-kaxxxxp: got 0 bytes, received: ''.
sr: [00:06.169099] korad-kaxxxxp: Received: 0, 
sr: [00:06.169110] korad-kaxxxxp: Unsupported model ID '', aborting.
sr: [00:06.169123] hwdriver: Scan found 0 devices (korad-kaxxxxp).
sr: [00:06.169145] hwdriver: Cleaning up all drivers.
hydra ~ $                                    

though it doesn't respond to scan anymore, not sure what changed

I have tried various combinations (see #269 (comment)) it seems to need <command>(0 or 1 char)n but i can play around some tomorrow.

@cnf
Copy link

cnf commented Dec 7, 2025

LABPS3005DN.patch
ok, so this one pretty reliable works for scan, and show.

of course, it still won't detect my actually set values... maybe this one just needs its own driver? or the unified firmware :P

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants