diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index df50af9e5a57808a5f3594e3c9bddce046e991d4..fb628f78a6c5523ff07fc9e94975c5d22f9a316e 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -23,7 +23,7 @@ stages:
   stage: package
   # make a docker daemon available for cibuildwheel to use
   tags:
-    - docker-privileged
+    - docker-privileged-xl
   services:
     - name: docker:dind
       entrypoint: ["env", "-u", "DOCKER_HOST"]
diff --git a/src/libChiller/CMakeLists.txt b/src/libChiller/CMakeLists.txt
index 071c840eb0057340f9568927a8b864fa3b183deb..d0b8a15907e5c970b6dbfc1253d4860645b2c731 100644
--- a/src/libChiller/CMakeLists.txt
+++ b/src/libChiller/CMakeLists.txt
@@ -6,6 +6,7 @@ target_sources(Chiller
   HuberChiller.cpp
   SPSRC211.cpp
   PolySciLM.cpp
+  Julabo.cpp
   HuberComGate.cpp
   )
 target_link_libraries(Chiller PRIVATE Com Utils)
diff --git a/src/libChiller/IChiller.cpp b/src/libChiller/IChiller.cpp
index 4a839c35b69769222ea80b711284c51d7d64670e..49b2bdfe331131bc00e7e3961e38fe5e8e578462 100644
--- a/src/libChiller/IChiller.cpp
+++ b/src/libChiller/IChiller.cpp
@@ -26,6 +26,16 @@ float IChiller::getRampRate() {
     return 0.0;
 }
 
+float IChiller::measurePressure() {
+    logger(logWARNING) << "measurePressure is not implemented in this chiller!";
+    return 0.0;
+}
+
+float IChiller::measureFlow() {
+    logger(logWARNING) << "measureFlow is not implemented in this chiller!";
+    return 0.0;
+}
+
 float IChiller::ctof(float t) { return (t * 9.0 / 5.0) + 32.0; }
 
 float IChiller::ftoc(float t) { return (t - 32.0) * 5.0 / 9.0; }
diff --git a/src/libChiller/IChiller.h b/src/libChiller/IChiller.h
index 52741e972a7e512c8312da002da9c9c77905e420..1ec9f165ad89ff7c38b8ca1d481fca5f3178db37 100644
--- a/src/libChiller/IChiller.h
+++ b/src/libChiller/IChiller.h
@@ -53,6 +53,10 @@ class IChiller {
     virtual void setRampRate(float RR);
     //! Return the ramp rate that the chiller is set to in degree/minute
     virtual float getRampRate();
+    //! Return the current pressure of the chiller in PSI
+    virtual float measurePressure();
+    //! Return chiller flow in LPM
+    virtual float measureFlow();
     //! Set the target temperature of the chiller
     /**
      * \param temp The target temperature in Celsius
diff --git a/src/libChiller/Julabo.cpp b/src/libChiller/Julabo.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..4293662cc161a0b8f2a88f07433703615e2f0be9
--- /dev/null
+++ b/src/libChiller/Julabo.cpp
@@ -0,0 +1,59 @@
+#include "Julabo.h"
+
+#include "ChillerRegistry.h"
+REGISTER_CHILLER(Julabo)
+
+Julabo::Julabo(const std::string& name) : IChiller(name, {"Julabo"}) {}
+
+void Julabo::init() {}
+
+void Julabo::turnOn() { m_com->send("out_mode_05 1"); }
+
+void Julabo::turnOff() { m_com->send("out_mode_05 0"); }
+
+void Julabo::setTargetTemperature(float temp) {
+    std::string cmd = "out_sp_00";
+    std::stringstream ss;
+    ss << temp;
+    cmd.append(" " + ss.str());
+    m_com->send(cmd);
+}
+
+float Julabo::getTargetTemperature() {
+    std::string response = m_com->sendreceive("in_sp_00");
+    float temp;
+    if (response.substr(0, 1) == "-")
+        temp = -std::stof(response.substr(1));
+    else
+        temp = std::stof(response);
+    return temp;
+}
+
+float Julabo::measureTemperature() {
+    std::string response = m_com->sendreceive("in_pv_00");
+    float temp;
+    if (response.substr(0, 1) == "-")
+        temp = -std::stof(response.substr(1));
+    else
+        temp = std::stof(response);
+    return temp;
+}
+
+bool Julabo::getStatus() {
+    std::string response = m_com->sendreceive("status");
+    // Return codes:
+    // 01 MANUAL START --> chiller is on and can be operated manually
+    // 02 MANUAL STOP --> chiller is off and can be operated manually
+    // 03 REMOTE START --> chiller is on and can be operated remotely
+    // 04 REMOTE STOP --> chiller is off and can be operated remotely
+    if (response.find("START") != std::string::npos)
+        return true;
+    else if (response.find("STOP") != std::string::npos)
+        return false;
+    else {
+        logger(logDEBUG) << __PRETTY_FUNCTION__
+                         << " -> Unexpected response: " << response;
+        throw std::runtime_error("Unexpected response from chiller");
+        return false;
+    }
+}
diff --git a/src/libChiller/Julabo.h b/src/libChiller/Julabo.h
new file mode 100644
index 0000000000000000000000000000000000000000..cf49f90b0f1083fa663cb541f63bd1ddee2bd870
--- /dev/null
+++ b/src/libChiller/Julabo.h
@@ -0,0 +1,67 @@
+#ifndef Julabo_H
+#define Julabo_H
+
+#include <memory>
+#include <stdexcept>
+#include <string>
+
+#include "IChiller.h"
+#include "Logger.h"
+#include "TextSerialCom.h"
+
+//! \brief Object to interface with a Julabo series chiller
+/**
+ * # Example configuration for a Julabo Chiller:
+ *
+ *   {
+ *     "name": "myJulaboChiller",
+ *     "hw-type": "Chiller",
+ *     "hw-model": "Julabo",
+ *     "communication": {
+ *        "protocol": "TextSerialCom",
+ *        "termination": "\r",
+ *        "returnTermination": "\n",
+ *        "baudrate": "B4800",
+ *        "port": "/dev/ttyUSB0",
+ *        "charsize": "CS7",
+ *        "parityBit": false,
+ *        "flowControl": true
+ *     }
+ *   }
+ *
+ * The operator's manual for these chillers can be found at
+ *  https://www.julabo.com/en-us/products/recirculating-coolers/fl-recirculating-coolers/fl1201
+ */
+class Julabo : public IChiller {
+ public:
+    /**
+     * \param com The serial communication interface connected
+     *   to the chiller
+     */
+    Julabo(const std::string& name);
+    ~Julabo() = default;
+
+    //! Initialize the serial communication channel
+    void init();
+    //! Turn the chiller on
+    void turnOn();
+    //! Turn the chiller off
+    void turnOff();
+    //! Set the target temperature of the chiller
+    /**
+     * \param temp The target temperature in Celsius
+     */
+    void setTargetTemperature(float temp);
+    //! Return the temperature that the chiller is set to in Celsius
+    float getTargetTemperature();
+    //! Return the current temperature of the chiller in Celsius
+    float measureTemperature();
+    //! Get the status of the chiller
+    /**
+     * \return true if chiller is in "run" mode, and false if
+     *   it's in "standby" mode
+     */
+    bool getStatus();
+};
+
+#endif
diff --git a/src/libChiller/python.cpp b/src/libChiller/python.cpp
index 49219ea453168e27d3d72b00ce830dc901482414..5fdaeecac6ddef4ee6eab7b77ec393c19fb17621 100644
--- a/src/libChiller/python.cpp
+++ b/src/libChiller/python.cpp
@@ -7,6 +7,7 @@ namespace py = pybind11;
 
 #include "HuberChiller.h"
 #include "IChiller.h"
+#include "Julabo.h"
 #include "PolySciLM.h"
 
 class PyIChiller : public IChiller {
@@ -53,6 +54,8 @@ void register_chiller(py::module& m) {
         .def("setTargetTemperature", &IChiller::setTargetTemperature)
         .def("getTargetTemperature", &IChiller::getTargetTemperature)
         .def("measureTemperature", &IChiller::measureTemperature)
+        .def("measurePressure", &IChiller::measurePressure)
+        .def("measureFlow", &IChiller::measureFlow)
         .def("getStatus", &IChiller::getStatus);
 
     py::class_<HuberChiller, IChiller, std::shared_ptr<HuberChiller>>(
@@ -65,6 +68,8 @@ void register_chiller(py::module& m) {
         .def("setTargetTemperature", &HuberChiller::setTargetTemperature)
         .def("getTargetTemperature", &HuberChiller::getTargetTemperature)
         .def("measureTemperature", &HuberChiller::measureTemperature)
+        .def("measurePressure", &HuberChiller::measurePressure)
+        .def("measureFlow", &HuberChiller::measureFlow)
         .def("getStatus", &HuberChiller::getStatus)
         .def("getFaultStatus", &HuberChiller::getFaultStatus);
 
@@ -80,4 +85,16 @@ void register_chiller(py::module& m) {
         .def("measureFlow", &PolySciLM::measureFlow)
         .def("getStatus", &PolySciLM::getStatus)
         .def("getFaultStatus", &PolySciLM::getFaultStatus);
+
+    py::class_<Julabo, IChiller, std::shared_ptr<Julabo>>(m, "Julabo")
+        .def(py::init<const std::string&>())
+        .def("init", &Julabo::init)
+        .def("turnOn", &Julabo::turnOn)
+        .def("turnOff", &Julabo::turnOff)
+        .def("setTargetTemperature", &Julabo::setTargetTemperature)
+        .def("getTargetTemperature", &Julabo::getTargetTemperature)
+        .def("measureTemperature", &Julabo::measureTemperature)
+        .def("measurePressure", &Julabo::measurePressure)
+        .def("measureFlow", &Julabo::measureFlow)
+        .def("getStatus", &Julabo::getStatus);
 }
diff --git a/src/libCom/SerialCom.cpp b/src/libCom/SerialCom.cpp
index 3a4aad9d089d3ac9fc3413eff8a5febeac0f302f..c7830605426c4f300f6ec6fdde02ef3ec07835e7 100644
--- a/src/libCom/SerialCom.cpp
+++ b/src/libCom/SerialCom.cpp
@@ -6,6 +6,7 @@
 #include <termios.h>
 #include <unistd.h>
 
+#include <bitset>
 #include <cerrno>
 #include <cstring>
 #include <stdexcept>
@@ -210,9 +211,31 @@ std::string SerialCom::receive() {
     ScopeLock lock(this);
     int n_read = ::read(m_dev, m_tmpbuf, MAX_READ);
 
-    if (n_read >= 0)
-        return std::string(m_tmpbuf, n_read);
-    else
+    if (n_read >= 0) {
+        std::string response = std::string(m_tmpbuf, n_read);
+
+        // Convert response if communication uses charsize different from 8
+        if (m_charsize != mapCHARSIZE.at("CS8")) {
+            std::string decoded_response;
+            for (size_t i = 0; i < response.length(); i++) {
+                unsigned char original =
+                    static_cast<unsigned char>(response[i]);
+                unsigned char fixed;
+                if (m_charsize == mapCHARSIZE.at("CS7"))
+                    fixed = original & 0x7F;
+                else if (m_charsize == mapCHARSIZE.at("CS6"))
+                    fixed = original & 0x6F;
+                else if (m_charsize == mapCHARSIZE.at("CS5"))
+                    fixed = original & 0x5F;
+                else
+                    throw std::runtime_error(
+                        "CharSize not recognized! Unable to decode response");
+                decoded_response.push_back(fixed);
+            }
+            response = decoded_response;
+        }
+        return response;
+    } else
         throw std::runtime_error("Error reading from " + m_port + ": " +
                                  std::strerror(errno));
 }
diff --git a/src/libDevCom/ChecksumException.h b/src/libDevCom/ChecksumException.h
index 94eb6d109702b6c2a24677c181fa131c06783030..07e578a36b23970abc3c7d53b909e92a9b8b8d91 100644
--- a/src/libDevCom/ChecksumException.h
+++ b/src/libDevCom/ChecksumException.h
@@ -1,6 +1,8 @@
 #ifndef CHECKSUMEXCEPTION_H
 #define CHECKSUMEXCEPTION_H
 
+#include <stdint.h>
+
 #include <iostream>
 
 #include "ComException.h"
diff --git a/src/libDevCom/OutOfRangeException.h b/src/libDevCom/OutOfRangeException.h
index 5a6ac1105eeaa981369af79f7173320f1c22f5b6..e2b35b45bf803082675362b6b6181e74a0ad79a6 100644
--- a/src/libDevCom/OutOfRangeException.h
+++ b/src/libDevCom/OutOfRangeException.h
@@ -1,6 +1,8 @@
 #ifndef OUTOFRANGEEXCEPTION_H
 #define OUTOFRANGEEXCEPTION_H
 
+#include <stdint.h>
+
 #include <iostream>
 
 #include "ComException.h"
diff --git a/src/libMeter/CMakeLists.txt b/src/libMeter/CMakeLists.txt
index e11d2151169264f7459e1d5d1738f4b00658d9e6..3629ce973f610677f45e3d85aa8082c3bd004ba7 100644
--- a/src/libMeter/CMakeLists.txt
+++ b/src/libMeter/CMakeLists.txt
@@ -6,10 +6,12 @@ target_sources(Meter
   Keithley2000.cpp
   Keithley199.cpp
   Fluke8842.cpp
+  Fluke45.cpp
   HP3478A.cpp
   PM6680.cpp
   DMM6500.cpp
   KeysightDAQ970A.cpp
+  RigolDM30XX.cpp
   )
 target_link_libraries(Meter PRIVATE Com Utils)
 target_include_directories(Meter PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
diff --git a/src/libMeter/Fluke45.cpp b/src/libMeter/Fluke45.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..e5b36e84698da80258fb3abd9668fa86a3a6d081
--- /dev/null
+++ b/src/libMeter/Fluke45.cpp
@@ -0,0 +1,165 @@
+#include "Fluke45.h"
+
+#include "IMeter.h"
+#include "Logger.h"
+#include "MeterRegistry.h"
+#include "ScopeLock.h"
+REGISTER_METER(Fluke45)
+
+Fluke45::Fluke45(const std::string& name) : IMeter(name, {"Fluke45"}) {}
+
+bool Fluke45::ping(unsigned dev) {
+    std::string result = "";
+    if (dev == 0) {
+        logger(logDEBUG) << "ping the multimeter.....";
+        result = m_com->sendreceive("*IDN?\n");
+    } else {
+        throw std::runtime_error("Other channels not implemented! ");
+    }
+    return !result.empty();
+}
+
+std::string Fluke45::identify() {
+    std::string idn = this->sendreceive("*IDN?");
+    return idn;
+}
+
+void Fluke45::reset() {
+    logger(logDEBUG) << __PRETTY_FUNCTION__ << " -> Initialising: ";
+    this->send("*RST");
+}
+
+std::string Fluke45::GetMode() { return this->sendreceive("FUNC1?"); }
+
+void Fluke45::send(std::string cmd) {
+    ScopeLock lock(m_com);
+    m_com->send("*CLS");
+    logger(logDEBUG2) << __PRETTY_FUNCTION__ << " -> Sending: " << cmd;
+    cmd += "\r\n";
+    m_com->send(cmd);
+    std::this_thread::sleep_for(std::chrono::milliseconds(m_wait));
+}
+
+std::string Fluke45::sendreceive(std::string cmd) {
+    ScopeLock lock(m_com);
+    m_com->send("*CLS");
+    logger(logDEBUG2) << __PRETTY_FUNCTION__ << " -> Sending: " << cmd;
+    cmd += "\r\n";
+    m_com->send(cmd);
+    m_com->send("++read eoi\n\r");
+    std::this_thread::sleep_for(std::chrono::milliseconds(m_wait));
+    std::string buf = m_com->receive();
+    logger(logDEBUG2) << __PRETTY_FUNCTION__ << " -> Received: " << buf;
+    return buf;
+}
+
+std::string Fluke45::GetValue() { return this->sendreceive("VAL1?"); }
+
+double Fluke45::measureDCV(unsigned channel) {
+    ScopeLock lock(m_com);
+    std::string CurrentMode = this->GetMode();
+    if (CurrentMode != "VDC") {
+        logger(logDEBUG2) << "Current Mode is " + CurrentMode +
+                                 ", Reset the meter to VDC";
+        this->reset();
+        this->SetMode(Fluke45Mode::VOLTAGEDC);
+    }
+
+    if (channel > 0)  // use scanner card; not implemented for Fluke 45
+        std::cerr << "Unimplemented channel number: " << channel << std::endl;
+
+    return std::stod(this->GetValue());
+}
+
+double Fluke45::measureDCI(unsigned channel) {
+    ScopeLock lock(m_com);
+    std::string CurrentMode = this->GetMode();
+    if (CurrentMode != "ADC") {
+        logger(logDEBUG2) << "Current Mode is " + CurrentMode +
+                                 ", Reset the meter to ADC";
+        this->reset();
+        this->SetMode(Fluke45Mode::CURRENTDC);
+    }
+
+    if (channel > 0)  // use scanner card; not implemented for Fluke 45
+        std::cerr << "Unimplemented channel number: " << channel << std::endl;
+
+    std::string val = this->GetValue();
+    return std::stod(val);
+}
+
+double Fluke45::measureRES(unsigned channel, bool use4w) {
+    ScopeLock lock(m_com);
+    std::string CurrentMode = this->GetMode();
+    if (CurrentMode != "OHMS") {
+        logger(logDEBUG2) << "Current Mode is " + CurrentMode +
+                                 ", Reset the meter to OHMS";
+        this->reset();
+        this->SetMode(Fluke45Mode::OHMS);
+    }
+
+    if (channel > 0)  // use scanner card; not implemented for Fluke 45
+        std::cerr << "Unimplemented channel number: " << channel << std::endl;
+
+    std::string val = this->GetValue();
+    return std::stod(val);
+}
+
+double Fluke45::measureCAP(unsigned channel) {
+    std::cerr << "Unable to measure capacitance by Fluke 45, exit. "
+              << std::endl;
+}
+
+void Fluke45::SetMode(enum Fluke45Mode mode) {
+    switch (mode) {
+        case Fluke45Mode::VOLTAGEDC:
+            this->send("VDC; AUTO;");
+            break;
+        case Fluke45Mode::VOLTAGEAC:
+            this->send("VAC; AUTO;");
+            break;
+        case Fluke45Mode::CURRENTDC:
+            this->send("ADC; AUTO;");
+            break;
+        case Fluke45Mode::CURRENTAC:
+            this->send("AAC; AUTO;");
+            break;
+        case Fluke45Mode::OHMS:
+            this->send("OHMS; AUTO;");
+            break;
+        default:
+            logger(logERROR) << __PRETTY_FUNCTION__ << " : Unknown mode!";
+            break;
+    }
+}
+
+void Fluke45::checkCompatibilityList() {
+    // get model connected to the meter
+    std::string idn = this->identify();
+
+    // get list of models
+    std::vector<std::string> models = IMeter::getListOfModels();
+
+    if (models.empty()) {
+        logger(logINFO) << "No model identifier implemented for this meter. No "
+                           "check is performed.";
+        return;
+    }
+
+    std::size_t pos = m_name.find("Fluke");
+    std::string brand = m_name.substr(pos, pos + 5);
+    std::string type = m_name.substr(pos + 5, pos + 2);
+
+    for (int i = 0; i < brand.length(); i++) {
+        brand[i] = toupper(brand[i]);
+    }
+
+    for (const std::string& model : models) {
+        if (idn.find(brand) != std::string::npos &&
+            idn.find(type) != std::string::npos)
+            return;
+    }
+
+    logger(logERROR) << "Unknown meter: " << idn;
+    throw std::runtime_error("Unknown meter: " + idn);
+}
diff --git a/src/libMeter/Fluke45.h b/src/libMeter/Fluke45.h
new file mode 100644
index 0000000000000000000000000000000000000000..1df0ec235dc0804ff4de9fba01de98ff281fef2f
--- /dev/null
+++ b/src/libMeter/Fluke45.h
@@ -0,0 +1,67 @@
+#ifndef Fluke45_H
+#define Fluke45_H
+#include <chrono>
+#include <iostream>
+#include <string>
+#include <thread>
+
+#include "IMeter.h"
+
+/*
+ Fluke45 multimeter
+ Author: Haoran Zhao & Emily Thompson
+ Date: Feb 2023
+ Reference 1:
+ Fluke 45 User Manual
+ https://www.testequipmentdepot.com/usedequipment/pdf/45.pdf
+ Reference 2:
+ GPIB usb controller
+ https://prologix.biz/gpib-usb-controller.html
+*/
+
+enum class Fluke45Mode { VOLTAGEDC, VOLTAGEAC, CURRENTDC, CURRENTAC, OHMS };
+
+class Fluke45 : public IMeter {
+ public:
+    // constructor
+    Fluke45(const std::string& name);
+
+    // Reset the multimeter
+    void reset();
+
+    // Ping device
+    bool ping(unsigned dev = 0);
+
+    // Ping device
+    std::string identify();
+
+    // Get the current measurement mode of the multimeter
+    std::string GetMode();
+
+    // Get value on the display
+    std::string GetValue();
+
+    // Set measurement mode (VDC, VAC, ADC, AAC)
+    void SetMode(enum Fluke45Mode);
+
+    // Make measurements
+    double measureDCV(unsigned channel = 0);
+    double measureDCI(unsigned channel = 0);
+    double measureRES(unsigned channel = 0, bool use4w = false);
+    double measureCAP(unsigned channel = 0);
+
+    // Check if device model is supported
+    void checkCompatibilityList();
+
+ private:
+    // Send command string to reader
+    void send(std::string cmd);
+
+    // Send command string to meter and read the output
+    std::string sendreceive(std::string cmd);
+
+    // Brief wait interval, could be used between two successive measurements
+    std::chrono::milliseconds m_wait{900};
+};
+
+#endif
diff --git a/src/libMeter/RigolDM30XX.cpp b/src/libMeter/RigolDM30XX.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..e771c97a90090ee03363f03a59aa274dafc8ec8b
--- /dev/null
+++ b/src/libMeter/RigolDM30XX.cpp
@@ -0,0 +1,87 @@
+#include "RigolDM30XX.h"
+
+#include "IMeter.h"
+#include "Logger.h"
+#include "MeterRegistry.h"
+#include "ScopeLock.h"
+#include "StringUtils.h"
+REGISTER_METER(RigolDM30XX)
+
+RigolDM30XX::RigolDM30XX(const std::string& name)
+    : IMeter(name, {"DM3058", "DM3058E", "DM3068"}) {}
+
+bool RigolDM30XX::ping(unsigned /*dev*/) {
+    std::string result = "";
+    logger(logDEBUG) << "ping the multimeter.....";
+    result = m_com->sendreceive("*IDN?");
+    utils::rtrim(result);
+    if (result.empty()) {
+        throw std::runtime_error("Failed communication with the device");
+    } else {
+        logger(logDEBUG) << result;
+    }
+
+    return !result.empty();
+}
+
+std::string RigolDM30XX::identify() {
+    std::string idn = m_com->sendreceive("*IDN?");
+    return idn;
+}
+
+void RigolDM30XX::autowait() {
+    m_com->send("*OPC");
+    int ESRValue = 0;
+    while ((ESRValue & 1) == 0) {
+        ESRValue = std::stoi(m_com->sendreceive("*ESR?"));
+        std::this_thread::sleep_for(std::chrono::milliseconds(m_wait));
+    }
+}
+
+void RigolDM30XX::send(const std::string& cmd) {
+    logger(logDEBUG) << __PRETTY_FUNCTION__ << " -> Sending: " << cmd;
+    m_com->send("*CLS");
+    m_com->send(cmd);
+    this->autowait();
+}
+
+std::string RigolDM30XX::sendreceive(const std::string& cmd) {
+    m_com->send("*CLS");
+    std::string buf = m_com->sendreceive(cmd);
+    this->autowait();
+    logger(logDEBUG) << __PRETTY_FUNCTION__ << " -> Received: " << buf;
+    utils::rtrim(buf);
+    return buf;
+}
+
+void RigolDM30XX::reset() {
+    logger(logDEBUG) << __PRETTY_FUNCTION__ << " -> Initialising: ";
+    this->send("*RST");
+}
+
+// measure DC voltage with high precision
+// take average of 10 repeatings
+double RigolDM30XX::measureDCV(unsigned channel) {
+    ScopeLock lock(m_com);
+
+    return std::stod(this->sendreceive(":MEAS:VOLT:DC?"));
+}
+
+// measure resistance (2W or 4W)
+// take average of 10 repeatings
+double RigolDM30XX::measureRES(unsigned channel, bool use4w) {
+    std::string n_func = "RES";
+    if (use4w) n_func = "FRES";
+
+    ScopeLock lock(m_com);
+
+    return std::stod(this->sendreceive(":MEAS:" + n_func + "?"));
+}
+
+// measure DC current with high precision
+// take average of 10 repeatings
+double RigolDM30XX::measureDCI(unsigned channel) {
+    ScopeLock lock(m_com);
+
+    return std::stod(this->sendreceive(":MEAS:CURR:DC?"));
+}
diff --git a/src/libMeter/RigolDM30XX.h b/src/libMeter/RigolDM30XX.h
new file mode 100644
index 0000000000000000000000000000000000000000..d13e9a025e6fa0e78b94c11de189b3f44f3c85e5
--- /dev/null
+++ b/src/libMeter/RigolDM30XX.h
@@ -0,0 +1,55 @@
+#ifndef RigolDM30XX_H
+#define RigolDM30XX_H
+#include <chrono>
+#include <iostream>
+#include <string>
+#include <thread>
+
+#include "IMeter.h"
+
+/*
+ RigolDM30XX single-channel multimeter
+ Author: Simon Koch
+ Date: Feb 2024
+ Reference 1 (User Guide, DM3068):
+ https://www.rigol-uk.co.uk/pdf/Rigol-DM3068-User-Guide.pdf
+ Reference 2 (Programmers Guide, DM3058/3058E/3068):
+ https://beyondmeasure.rigoltech.com/acton/attachment/1579/f-003f/0/-/-/-/-/file.pdf
+
+ Based on implementation of Keithley2000
+   - channel argument included only for compatibility with IMeter interface
+*/
+class RigolDM30XX : public IMeter {
+ public:
+    RigolDM30XX(const std::string& name);
+
+    /** ping the device
+     */
+    virtual bool ping(unsigned dev = 0);
+
+    virtual std::string identify();
+
+    virtual void reset();
+
+    /** measure DC voltage (unit: V)
+     */
+    virtual double measureDCV(unsigned channel = 0);
+
+    /** measure DC current (unit: A)
+     */
+    virtual double measureDCI(unsigned channel = 0);
+
+    /* measure resistance (unit: Ohm)
+     */
+    virtual double measureRES(unsigned channel = 0, bool use4w = false);
+
+ private:
+    void send(const std::string& cmd);
+
+    std::string sendreceive(const std::string& cmd);
+    void autowait();
+
+    std::chrono::milliseconds m_wait{10};
+};
+
+#endif
diff --git a/src/libMeter/python.cpp b/src/libMeter/python.cpp
index 4af85efb1c9d21f2cd944d5aa2e82f6ea2f20f4a..21990e69ce91cdcfb2367ceade5c59ebca82ac13 100644
--- a/src/libMeter/python.cpp
+++ b/src/libMeter/python.cpp
@@ -9,11 +9,13 @@
 
 // labRemote
 #include "DMM6500.h"
+#include "Fluke45.h"
 #include "Fluke8842.h"
 #include "ICom.h"
 #include "IMeter.h"
 #include "Keithley2000.h"
 #include "KeysightDAQ970A.h"
+#include "RigolDM30XX.h"
 
 namespace py = pybind11;
 
@@ -152,6 +154,20 @@ void register_meter(py::module& m) {
                                const std::vector<unsigned>&, bool)>(
                                &Keithley2000::measureRES));
 
+    // RigolDM30XX
+    py::class_<RigolDM30XX, IMeter, std::shared_ptr<RigolDM30XX>>(m,
+                                                                  "RigolDM30XX")
+        .def(py::init<const std::string&>())
+        .def("ping", &RigolDM30XX::ping)
+        .def("identify", &RigolDM30XX::identify)
+        .def("reset", &RigolDM30XX::reset)
+        .def("measureDCV", static_cast<double (RigolDM30XX::*)(unsigned)>(
+                               &RigolDM30XX::measureDCV))
+        .def("measureDCI", static_cast<double (RigolDM30XX::*)(unsigned)>(
+                               &RigolDM30XX::measureDCI))
+        .def("measureRES", static_cast<double (RigolDM30XX::*)(unsigned, bool)>(
+                               &RigolDM30XX::measureRES));
+
     // KeysightDAQ970A
     py::class_<KeysightDAQ970A, IMeter, std::shared_ptr<KeysightDAQ970A>>
         daq970a(m, "KeysightDAQ970A");
@@ -195,6 +211,19 @@ void register_meter(py::module& m) {
         .value("Maximum", KeysightDAQ970A::Statistic::Maximum)
         .value("PeakToPeak", KeysightDAQ970A::Statistic::PeakToPeak);
 
+    // Fluke45
+    py::class_<Fluke45, IMeter, std::shared_ptr<Fluke45>>(m, "Fluke45")
+        .def(py::init<const std::string&>())
+        .def("ping", &Fluke45::ping)
+        .def("identify", &Fluke45::identify)
+        .def("reset", &Fluke45::reset)
+        .def("measureDCV",
+             static_cast<double (Fluke45::*)(unsigned)>(&Fluke45::measureDCV))
+        .def("measureDCI",
+             static_cast<double (Fluke45::*)(unsigned)>(&Fluke45::measureDCI))
+        .def("measureRES", static_cast<double (Fluke45::*)(unsigned, bool)>(
+                               &Fluke45::measureRES));
+
     // Fluke8842
     py::class_<Fluke8842, IMeter, std::shared_ptr<Fluke8842>>(m, "Fluke8842")
         .def(py::init<const std::string&>())
diff --git a/src/libPS/CMakeLists.txt b/src/libPS/CMakeLists.txt
index ecdf435fe8e58dfb324268fdfca1a1583e2385b5..94d8662ae16209fa0b56cbf3e3d98596360113f6 100644
--- a/src/libPS/CMakeLists.txt
+++ b/src/libPS/CMakeLists.txt
@@ -15,12 +15,16 @@ target_sources(PS
   DT54xxPs.cpp
   DT5471NPs.cpp
   DT5472NPs.cpp
+  DT8033NPs.cpp
+  IsegPs.cpp
+  IsegSHR20xxPs.cpp
   SorensenPs.cpp
   RigolDP832.cpp
   Keithley24XX.cpp
   Keithley22XX.cpp
   RS_HMP4040.cpp
   RS_HMP2020.cpp
+  RS_NGP804.cpp
   Tenma72133XX.cpp
   Tenma722XXX.cpp
   TTIPs.cpp
diff --git a/src/libPS/DT5471NPs.cpp b/src/libPS/DT5471NPs.cpp
index a883af092beb8b01f8c0c1ba5e85e4d6be1fba51..ae4962cd8b0f48d1592faaab02c91e8b8d42b944 100644
--- a/src/libPS/DT5471NPs.cpp
+++ b/src/libPS/DT5471NPs.cpp
@@ -11,4 +11,4 @@
 REGISTER_POWERSUPPLY(DT5471NPs)
 
 DT5471NPs::DT5471NPs(const std::string& name)
-    : DT54xxPs(name, {"DT5471"}, Polarity::Negative, 51e-6) {}
+    : DT54xxPs(name, {"DT5471"}, IPowerSupply::Polarity::Negative, 51e-6) {}
diff --git a/src/libPS/DT5472NPs.cpp b/src/libPS/DT5472NPs.cpp
index d3edbb977fb4c98a28a984c4a8efb4a5094101b9..7bdc3e6a094115548b10c28ecc09f3ebcb1e1702 100644
--- a/src/libPS/DT5472NPs.cpp
+++ b/src/libPS/DT5472NPs.cpp
@@ -11,4 +11,4 @@
 REGISTER_POWERSUPPLY(DT5472NPs)
 
 DT5472NPs::DT5472NPs(const std::string& name)
-    : DT54xxPs(name, {"DT5472"}, Polarity::Negative, 105e-6) {}
+    : DT54xxPs(name, {"DT5472"}, IPowerSupply::Polarity::Negative, 105e-6) {}
diff --git a/src/libPS/DT54xxPs.cpp b/src/libPS/DT54xxPs.cpp
index 10505b57fe34d2fafd548e8965402722340a180a..0e0e519ea192cc7a90ab085d7aad6ad4b697d126 100644
--- a/src/libPS/DT54xxPs.cpp
+++ b/src/libPS/DT54xxPs.cpp
@@ -12,8 +12,8 @@
 REGISTER_POWERSUPPLY(DT54xxPs)
 
 DT54xxPs::DT54xxPs(const std::string& name,
-                   const std::vector<std::string>& models, Polarity output,
-                   double imaxl)
+                   const std::vector<std::string>& models,
+                   IPowerSupply::Polarity output, double imaxl)
     : IPowerSupply(name, models), m_output(output), m_imaxl(imaxl) {}
 
 void DT54xxPs::reset() {
@@ -38,7 +38,7 @@ bool DT54xxPs::ping() {
 void DT54xxPs::checkCompatibilityList() {
     IPowerSupply::checkCompatibilityList();
 
-    Polarity pol = polarity();
+    IPowerSupply::Polarity pol = polarity();
     if (pol != m_output) throw std::runtime_error("Wrong polarity detected");
 }
 
@@ -160,16 +160,16 @@ uint16_t DT54xxPs::status(unsigned channel) {
     return std::stoi(command("MON", "STAT")) & 0xFFFF;
 }
 
-DT54xxPs::Polarity DT54xxPs::polarity(unsigned channel) {
+IPowerSupply::Polarity DT54xxPs::polarity(unsigned channel) {
     if (channel != 1)
         throw std::runtime_error(
             "Set the channel to 1 for single channel power-supply");
 
     std::string polstr = command("MON", "POLARITY");
     if (polstr == "-")
-        return Polarity::Negative;
+        return IPowerSupply::Polarity::Negative;
     else
-        return Polarity::Positive;
+        return IPowerSupply::Polarity::Positive;
 }
 
 void DT54xxPs::setIMonRange(IMonRange range, unsigned channel) {
@@ -239,11 +239,11 @@ std::string DT54xxPs::command(const std::string& cmd, const std::string& par,
 }
 
 double DT54xxPs::checkPolarity(double input) {
-    if (m_output == Polarity::Negative && input > 0)
+    if (m_output == IPowerSupply::Polarity::Negative && input > 0)
         throw std::runtime_error(
             "Specified positive output value for a power supply that only "
             "supports negative output.");
-    if (m_output == Polarity::Positive && input < 0)
+    if (m_output == IPowerSupply::Polarity::Positive && input < 0)
         throw std::runtime_error(
             "Specified negative output value for a power supply that only "
             "supports positive output.");
@@ -252,5 +252,5 @@ double DT54xxPs::checkPolarity(double input) {
 }
 
 double DT54xxPs::convertPolarity(double value) {
-    return (m_output == Polarity::Negative) ? -value : value;
+    return (m_output == IPowerSupply::Polarity::Negative) ? -value : value;
 }
diff --git a/src/libPS/DT54xxPs.h b/src/libPS/DT54xxPs.h
index 55949e91951cf97ec0f37e207432f50c28f2b02a..a0350bddad530fc8a488eae497db56660f6d0b9e 100644
--- a/src/libPS/DT54xxPs.h
+++ b/src/libPS/DT54xxPs.h
@@ -48,11 +48,6 @@ class DT54xxPs : public IPowerSupply {
         CalError = (1 << 13)     // 1 : Calibration Error
     };
 
-    /**
-     * Polarity of the power supply output
-     */
-    enum Polarity { Positive, Negative };
-
     /**
      * Range of the current monitor
      */
@@ -69,7 +64,8 @@ class DT54xxPs : public IPowerSupply {
      */
     DT54xxPs(const std::string& name,
              const std::vector<std::string>& models = {},
-             Polarity output = Polarity::Positive, double imaxl = 105e-6);
+             IPowerSupply::Polarity output = IPowerSupply::Polarity::Positive,
+             double imaxl = 105e-6);
     ~DT54xxPs() = default;
 
     /** \name Communication
@@ -198,7 +194,7 @@ class DT54xxPs : public IPowerSupply {
      *
      * @return polarity of the channel
      */
-    Polarity polarity(unsigned channel = 1);
+    IPowerSupply::Polarity polarity(unsigned channel = 1);
 
     /**
      * Set the current monitoring range
@@ -271,7 +267,7 @@ class DT54xxPs : public IPowerSupply {
 
     //! \brief Specify whether power supply outputs a negative voltage (N vs P
     //! model)
-    Polarity m_output = Polarity::Positive;
+    IPowerSupply::Polarity m_output = IPowerSupply::Polarity::Positive;
 
     //! \brief Specify maximum current [A] for low IMon range
     double m_imaxl = 105e-6;
diff --git a/src/libPS/DT8033NPs.cpp b/src/libPS/DT8033NPs.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..b99f413087395cc699e2f2d8902b6e36d26044d9
--- /dev/null
+++ b/src/libPS/DT8033NPs.cpp
@@ -0,0 +1,175 @@
+#include "DT8033NPs.h"
+
+#include <algorithm>
+#include <thread>
+
+#include "Logger.h"
+#include "ScopeLock.h"
+#include "TextSerialCom.h"
+
+// Register power supply
+#include "PowerSupplyRegistry.h"
+
+REGISTER_POWERSUPPLY(DT8033NPs)
+
+DT8033NPs::DT8033NPs(const std::string& name)
+    : IPowerSupply(name, {"DT8033"}) {}
+
+void DT8033NPs::reset() {
+    command("SET", "OFF");
+    command("SET", "BDCLR");
+
+    std::string result = identify();
+    if (result.empty())
+        throw std::runtime_error("No communication after reset.");
+}
+
+std::string DT8033NPs::identify() {
+    command("SET", "BDCLR");
+    std::string idn = command("MON", "BDNAME");
+    return idn;
+}
+
+bool DT8033NPs::ping() {
+    std::string result = command("MON", "BDNAME");
+    return !result.empty();
+}
+
+void DT8033NPs::checkCompatibilityList() {
+    IPowerSupply::checkCompatibilityList();
+}
+
+void DT8033NPs::turnOn(unsigned channel) {
+    ScopeLock lock(m_com);
+    command("SET", "ON", channel);
+    waitRamp();
+}
+
+void DT8033NPs::turnOff(unsigned channel) {
+    ScopeLock lock(m_com);
+    command("SET", "OFF", channel);
+    waitRamp();
+}
+
+void DT8033NPs::setCurrentLevel(double cur, unsigned channel) {
+    command("SET", "ISET", channel, std::to_string(cur));
+}
+
+double DT8033NPs::getCurrentLevel(unsigned channel) {
+    return std::stod(command("MON", "ISET", channel)) / 1e6;
+}
+
+void DT8033NPs::setCurrentProtect(double maxcur, unsigned channel) {
+    setCurrentLevel(maxcur, channel);
+}
+
+double DT8033NPs::getCurrentProtect(unsigned channel) {
+    return std::fabs(getCurrentLevel(channel));
+}
+
+double DT8033NPs::measureCurrent(unsigned channel) {
+    return std::stod(command("MON", "IMON", channel)) / 1e6;
+}
+
+void DT8033NPs::setVoltageLevel(double volt, unsigned channel) {
+    command("SET", "VSET", channel, std::to_string(checkPolarity(volt)));
+    waitRamp();
+}
+
+double DT8033NPs::getVoltageLevel(unsigned channel) {
+    return std::stod(command("MON", "VSET", channel));
+}
+
+void DT8033NPs::setVoltageProtect(double maxvolt, unsigned channel) {
+    setVoltageLevel(maxvolt, channel);
+}
+
+double DT8033NPs::getVoltageProtect(unsigned channel) {
+    return std::fabs(getVoltageLevel(channel));
+}
+
+double DT8033NPs::measureVoltage(unsigned channel) {
+    return std::stod(command("MON", "VMON", channel));
+}
+
+uint16_t DT8033NPs::status(unsigned channel) {
+    return std::stoi(command("MON", "STAT", channel)) & 0xFFFF;
+}
+
+IPowerSupply::Polarity DT8033NPs::polarity(unsigned channel) {
+    std::string polstr = command("MON", "POLARITY", channel);
+    if (polstr == "-")
+        return IPowerSupply::Polarity::Negative;
+    else
+        return IPowerSupply::Polarity::Positive;
+}
+
+void DT8033NPs::setIMonRange(IMonRange range, unsigned channel) {
+    command("SET", "IMRANGE", channel,
+            (range == IMonRange::Low) ? "LOW" : "HIGH");
+}
+
+DT8033NPs::IMonRange DT8033NPs::getIMonRange(unsigned channel) {
+    return (command("MON", "IMRANGE", channel) == "LOW") ? IMonRange::Low
+                                                         : IMonRange::High;
+}
+
+void DT8033NPs::waitRamp(unsigned channel) {
+    do {
+        std::this_thread::sleep_for(std::chrono::seconds(1));
+        logger(logDEBUG) << __PRETTY_FUNCTION__
+                         << " -> ramping: " << measureVoltage(channel) << "V";
+    } while (status(channel) & (Status::RampingUp | Status::RampingDown));
+}
+
+std::string DT8033NPs::command(const std::string& cmd, const std::string& par,
+                               unsigned channel, const std::string& value) {
+    // Build command
+    std::string tosend = "$CMD:" + cmd;
+    if (channel != 99) tosend += ",CH:" + std::to_string(channel);
+    tosend += ",PAR:" + par;
+    if (!value.empty()) tosend += ",VAL:" + value;
+
+    // Send command and receive response
+    std::string resp = m_com->sendreceive(tosend);
+
+    // Parse response
+    if (resp.empty()) throw "DT8033: No response :(";
+
+    std::string retvalue;
+    std::string cmdvalue;
+
+    std::string token;
+    std::stringstream ss(resp);
+    while (std::getline(ss, token, ',')) {
+        size_t seppos = token.find(':');
+        if (seppos == std::string::npos) continue;  // Not a valid part
+        if (token.substr(0, seppos) == "VAL") {     // This is the value part!
+            retvalue = token.substr(seppos + 1);
+        } else if (token.substr(0, seppos) ==
+                   "#CMD") {  // This is the value part!
+            cmdvalue = token.substr(seppos + 1);
+        }
+    }
+
+    if (cmdvalue.empty())
+        throw std::runtime_error("DT8033: No CMD in return statement :(");
+
+    if (cmdvalue == "ERR")
+        throw std::runtime_error("DT8033: CMD shows an error :(");
+
+    return retvalue;
+}
+
+double DT8033NPs::checkPolarity(double input) {
+    if (m_output == IPowerSupply::Polarity::Negative && input > 0)
+        throw std::runtime_error(
+            "Specified positive output value for a power supply that only "
+            "supports negative output.");
+    if (m_output == IPowerSupply::Polarity::Positive && input < 0)
+        throw std::runtime_error(
+            "Specified negative output value for a power supply that only "
+            "supports positive output.");
+
+    return std::fabs(input);
+}
diff --git a/src/libPS/DT8033NPs.h b/src/libPS/DT8033NPs.h
new file mode 100644
index 0000000000000000000000000000000000000000..e80977ce083ecf784924217c3ab47df142948d0c
--- /dev/null
+++ b/src/libPS/DT8033NPs.h
@@ -0,0 +1,257 @@
+#ifndef DT8033NPS_H
+#define DT8033NPS_H
+
+#include <chrono>
+#include <memory>
+#include <string>
+
+#include "IPowerSupply.h"
+
+//! \brief Base implementation for the CAEN DT8033NPs power supplies
+/**
+ * CAEN DT8033NPs USB High Voltage Power Supplies.
+ *
+ *  Example configuration for a DT8033N PS:
+ *
+ *   "name": "myDT8033NPs",
+ *   "hw-type": "PS",
+ *   "hw-model": "DT8033NPs",
+ *   "communication": {
+ *      "protocol" : "TextSerialCom",
+ *      "termination" : "\r\n",
+ *      "baudrate" : "B9600",
+ *      "port": "/dev/ttyACM1"
+ *   }
+ *
+ * [Programming Manual](https://www.caen.it/products/dt8033/)
+ */
+class DT8033NPs : public IPowerSupply {
+ public:
+    /**
+     * Interpretation of status bits.
+     * The value is the bitmask to select the bit in status() return value.
+     */
+    enum Status {
+        On = (1 << 0),           // 1 : ON 0 : OFF
+        RampingUp = (1 << 1),    // 1 : Channel Ramping UP
+        RampingDown = (1 << 2),  // 1 : Channel Ramping DOWN
+        OVC = (1 << 3),          // 1 : Over current
+        OVV = (1 << 4),          // 1 : Over voltage
+        UNV = (1 << 5),          // 1 : Under voltage
+        MAXV = (1 << 6),         // 1 : VOUT in MAXV protection
+        Trip = (1 << 7),         // 1 : Current generator
+        OVT = (1 << 8),          // 1 : Over temperature
+        Disabled = (1 << 10),    // 1 : Ch disabled
+        Kill = (1 << 11),        // 1 : Ch in KILL
+        Interlock = (1 << 12),   // 1 : Ch in INTERLOCK
+        CalError = (1 << 13)     // 1 : Calibration Error
+    };
+
+    /**
+     * Range of the current monitor
+     */
+    enum IMonRange { High, Low };
+
+    //! \brief Constructor that configures checks for a specific collection of
+    //! models
+    /**
+     * @param name `IPowerSupply` instance name
+     * @param models List of supported model names (see
+     * `IPowerSupply::checkCompatibilityList`)
+     * @param output The output voltage is negative
+     * @param imaxl Maximum current [A] for low IMon range
+     */
+    DT8033NPs(const std::string& name);
+    ~DT8033NPs() = default;
+
+    /** \name Communication
+     * @{
+     */
+
+    virtual bool ping();
+
+    virtual std::string identify();
+
+    /**
+     * In addition to the standard model check from `IPowerSupply`, this also
+     * checks that the `output` setting is correct.
+     */
+    virtual void checkCompatibilityList();
+
+    /** @} */
+
+    /** \name Power Supply Control
+     * @{
+     */
+
+    virtual void reset();
+
+    /** \brief Turn on power supply
+     *
+     * Block until power supply finishes ramping.
+     *
+     * @param channel channel, if any
+     */
+    virtual void turnOn(unsigned channel);
+
+    /** \brief Turn off power supply
+     *
+     * Block until power supply finishes rampdown.
+     * @param channel channel, if any
+     */
+    virtual void turnOff(unsigned channel);
+
+    /** @} */
+
+    /** \name Current Control and Measurement
+     * @{
+     */
+
+    /**
+     * Calls `setCurrentProtect` with `cur` as the maximum level.
+     */
+    virtual void setCurrentLevel(double cur, unsigned channel = 1);
+    virtual double getCurrentLevel(unsigned channel = 1);
+
+    //! \brief Set current protection
+    /**
+     * The current monitor range is also automatically set to match up
+     * with the maximum current (`maxcur`). If `maxcur` is below the maximum
+     * of the low IMon range (`imaxl`, see constructor), then low Imon range
+     * is used. Otherwise the high IMon range is used.
+     *
+     * @param maxcur maximum current (absolute value) [A]
+     * @param channel channel (if any)
+     */
+    virtual void setCurrentProtect(double maxcur, unsigned channel = 1);
+
+    //! \brief Get current protection
+    /**
+     * @param channel channel (if any)
+     * @return maximum current (absolute value) [A]
+     */
+    virtual double getCurrentProtect(unsigned channel = 1);
+    virtual double measureCurrent(unsigned channel = 1);
+
+    /** @} */
+
+    /** \name Voltage Control and Measurement
+     * @{
+     */
+
+    //! \brief Set output voltage level.
+    /**
+     * For the N-type power supplies, the voltage level should be
+     * specified as negative. This function uses the `output`
+     * property to validate the input.
+     *
+     * @param volt voltage [V]
+     * @param channel channel (if any)
+     */
+    virtual void setVoltageLevel(double volt, unsigned channel = 1);
+    virtual double getVoltageLevel(unsigned channel = 1);
+
+    //! \brief Set voltage protection
+    /**
+     * @param maxvolt maximum voltage (absolute value) [V]
+     * @param channel channel (if any)
+     */
+    virtual void setVoltageProtect(double maxvolt, unsigned channel = 1);
+
+    //! \brief Get voltage protection
+    /**
+     * @param channel channel (if any)
+     * @return maximum voltage (absolute value) [V]
+     */
+    virtual double getVoltageProtect(unsigned channel = 1);
+
+    virtual double measureVoltage(unsigned channel = 1);
+
+    /** @} */
+
+    /** \name Model-specific functionality
+     * @{
+     */
+
+    /**
+     * Return the status of the Power Supply.
+     * Use with Status enum to interpret bits.
+     *
+     * @param channel Channel to query
+     *
+     * @return status bits
+     */
+    uint16_t status(unsigned channel = 1);
+
+    /**
+     * Return the polarity of the power supply.
+     *
+     * @param channel Channel to query
+     *
+     * @return polarity of the channel
+     */
+    IPowerSupply::Polarity polarity(unsigned channel = 1);
+
+    /**
+     * Set the current monitoring range
+     *
+     * @param range to set
+     */
+    void setIMonRange(IMonRange range = Low, unsigned channel = 1);
+
+    /**
+     * Get the current monitoring range
+     *
+     * @return currently set range
+     */
+    IMonRange getIMonRange(unsigned channel = 1);
+
+    //! \brief Wait for voltage ramp to complete
+    /**
+     * Monitors the status of the power supply every
+     * second until the ramp up and down bits are zero.
+     *
+     * The monitoring starts by waiting for 1 second for
+     * the status register to be updated.
+     */
+    void waitRamp(unsigned channel = 1);
+
+    /** @} */
+
+ private:
+    //! \brief Build a command string and parse the response
+    /**
+     * Throws an exception if any of the following errors are detected:
+     * - no response
+     * - returned CMD value is ERR
+     *
+     * @param cmd CMD value
+     * @param par PAR value
+     * @param value VAL value (if empty, not appended)
+     *
+     * @return The returned VAL value.
+     */
+    std::string command(const std::string& cmd, const std::string& par,
+                        unsigned channel = 99, const std::string& value = "");
+
+    //! \brief Check that the input (voltage or current) has the right sign and
+    //! convert to absolute
+    /**
+     * A `std::runtime_error` is thrown if a wrong sign is supplied, as
+     * determine by the `m_output` setting.
+     *
+     * The conversion to absolute value is necessary for the power supplies
+     * command protocol.
+     *
+     * @param `input` Input value to check and convert.
+     *
+     * @return Absolute value of `input`
+     */
+    double checkPolarity(double input);
+
+    //! \brief Specify whether power supply outputs a negative voltage (N vs P
+    //! model)
+    IPowerSupply::Polarity m_output = IPowerSupply::Polarity::Negative;
+};
+
+#endif  // DT8033NPS_H
diff --git a/src/libPS/IPowerSupply.h b/src/libPS/IPowerSupply.h
index 5cf14258beeec54ee90394e393d2e97e843729f0..d601dfa3e4872ca56c93fbf477b978c66823a806 100644
--- a/src/libPS/IPowerSupply.h
+++ b/src/libPS/IPowerSupply.h
@@ -2,6 +2,7 @@
 #define IPOWERSUPPLY_H
 
 #include <nlohmann/json.hpp>
+#include <stdexcept>
 #include <string>
 
 #include "ICom.h"
@@ -222,6 +223,21 @@ class IPowerSupply {
 
     /** @} */
 
+    /** An exception to be thrown when an operation is executed which
+     * would trigger a polarity change while to power supply is turned on.
+     * Implemented in order for application to explicitly catch these types
+     * of errors and implement fault handling.
+     */
+    struct polarity_error : public std::runtime_error {
+        polarity_error(std::string const& what = "")
+            : std::runtime_error(what) {}
+    };
+
+    /**
+     * Polarity of the power supply output
+     */
+    enum class Polarity : int { Positive, Negative };
+
  protected:
     /** Communication */
     std::shared_ptr<ICom> m_com = nullptr;
diff --git a/src/libPS/IsegPs.cpp b/src/libPS/IsegPs.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..65b3fe7da0ab0315a1ba7cb72a9c99f4fba78056
--- /dev/null
+++ b/src/libPS/IsegPs.cpp
@@ -0,0 +1,219 @@
+#include "IsegPs.h"
+
+#include <algorithm>
+#include <chrono>
+#include <cmath>
+#include <thread>
+
+#include "Logger.h"
+#include "ScopeLock.h"
+#include "StringUtils.h"
+#include "TextSerialCom.h"
+
+// Register power supply
+#include "PowerSupplyRegistry.h"
+REGISTER_POWERSUPPLY(IsegPs)
+
+IsegPs::IsegPs(const std::string& name, std::vector<std::string> models,
+               unsigned maxChannels)
+    : IPowerSupply(name, models), m_maxChannels(maxChannels) {}
+
+bool IsegPs::ping() {
+    std::string result = sendreceive("*IDN?");
+    return !result.empty();
+}
+
+void IsegPs::reset() {
+    send("*RST");
+    if (!ping()) throw std::runtime_error("No communication after reset.");
+}
+
+std::string IsegPs::identify() {
+    std::string idn = sendreceive("*IDN?");
+    return idn;
+}
+
+void IsegPs::turnOn(unsigned channel) { send(":VOLT ON,", channel); }
+
+void IsegPs::turnOff(unsigned channel) { send(":VOLT OFF,", channel); }
+
+void IsegPs::setCurrentLevel(double cur, unsigned channel) {
+    send(":CURR " + std::to_string(cur) + ",", channel);
+}
+
+double IsegPs::getCurrentLevel(unsigned channel) {
+    std::string const recv = sendreceive(":READ:CURR:NOM?", channel);
+    return std::stod(utils::trim_last_char(recv));  // remove the A from the end
+}
+
+void IsegPs::setCurrentProtect(double maxcur, unsigned channel) {
+    logger(logWARNING) << "setCurrentProtect() not implemented for this PS. "
+                          "Manual adjustment at PS required.";
+}
+
+double IsegPs::getCurrentProtect(unsigned channel) {
+    std::string const recv = sendreceive(":READ:CURR:LIM?", channel);
+    return std::stod(utils::trim_last_char(recv));
+}
+
+double IsegPs::measureCurrent(unsigned channel) {
+    std::string const recv = sendreceive("MEAS:CURR?", channel);
+    return std::stod(utils::trim_last_char(recv));  // remove the A from the end
+}
+
+void IsegPs::setVoltageLevel(double volt, unsigned channel) {
+    auto channelOn = isOn(channel);
+    auto PSPolarity = getPolarity(channel);  // the current polarity set
+    auto ReqPolarityPos =
+        !std::signbit(volt);  // is requested volt not negative
+    if (!ReqPolarityPos && (PSPolarity == IPowerSupply::Polarity::Positive)) {
+        if (channelOn) {
+            throw IPowerSupply::polarity_error(
+                "Attempting to switch to negative from positive polarity while "
+                "channel is on");
+        } else {
+            setPolarity(IPowerSupply::Polarity::Negative, channel);
+            // need to wait before sending the new voltage, if no wait, the
+            // voltage will not be set correctly
+            std::this_thread::sleep_for(std::chrono::seconds(1));
+        }
+    } else if (ReqPolarityPos &&
+               (PSPolarity == IPowerSupply::Polarity::Negative)) {
+        if (channelOn) {
+            throw IPowerSupply::polarity_error(
+                "Attempting to switch to positive from negative polarity while "
+                "channel is on");
+        } else {
+            setPolarity(IPowerSupply::Polarity::Positive, channel);
+            // need to wait before sending the new voltage, if no wait, the
+            // voltage will not be set correctly
+            std::this_thread::sleep_for(std::chrono::seconds(1));
+        }
+    }
+    send(":VOLTAGE " + std::to_string(volt) + ",", channel);
+}
+
+double IsegPs::getVoltageLevel(unsigned channel) {
+    std::string const recv = sendreceive(":READ:VOLTAGE?", channel);
+    return std::stod(utils::trim_last_char(recv));  // remove the V from the end
+}
+
+void IsegPs::setVoltageProtect(double maxvolt, unsigned channel) {
+    logger(logWARNING) << "setVoltageProtect() not implemented for this PS. "
+                          "Manual adjustment at PS required.";
+}
+
+double IsegPs::getVoltageProtect(unsigned channel) {
+    std::string const recv = sendreceive(":READ:VOLTAGE:LIM?", channel);
+    return std::stod(utils::trim_last_char(recv));  // remove the V from the end
+}
+
+double IsegPs::measureVoltage(unsigned channel) {
+    std::string const recv = sendreceive(":MEAS:VOLT?", channel);
+    return std::stod(utils::trim_last_char(recv));  // remove the V from the end
+}
+
+void IsegPs::send(const std::string& cmd) {
+    std::string opcreply = sendreceive(cmd + ";*OPC?");
+    utils::rtrim(opcreply);
+    if (opcreply != "1")
+        throw std::runtime_error("IsegPs::send: *OPC? check failed");
+}
+
+void IsegPs::send(const std::string& cmd, unsigned channel) {
+    if (m_maxChannels > 0) {  // In-range channel check
+        if (channel > m_maxChannels)
+            throw std::runtime_error("Invalid channel: " +
+                                     std::to_string(channel));
+    }
+    std::string retCmd = cmd;
+    if (m_maxChannels != 1) retCmd += " (@" + std::to_string(channel) + ")";
+    send(retCmd);
+}
+
+IPowerSupply::Polarity IsegPs::getPolarity(unsigned channel) {
+    if (m_maxChannels > 0) {  // In-range channel check
+        if (channel > m_maxChannels)
+            throw std::runtime_error("Invalid channel: " +
+                                     std::to_string(channel));
+    }
+    std::string cmd = ":CONF:OUTP:POL? (@" + std::to_string(channel) + ")";
+    std::string recv = sendreceive(cmd);
+    if (recv == "p")
+        return IPowerSupply::Polarity::Positive;
+    else if (recv == "n")
+        return IPowerSupply::Polarity::Negative;
+    else
+        throw std::runtime_error(
+            "IsegPs::getPolarityPos: could not determine polarity");
+}
+
+void IsegPs::setPolarity(IPowerSupply::Polarity polarity, unsigned channel) {
+    if (m_maxChannels > 0) {  // In-range channel check
+        if (channel > m_maxChannels)
+            throw std::runtime_error("Invalid channel: " +
+                                     std::to_string(channel));
+    }
+    turnOff(channel);
+    std::string cmd;
+    if (polarity == IPowerSupply::Polarity::Positive)
+        cmd = ":CONF:OUTP:POL p, (@" + std::to_string(channel) + ")";
+    else if (polarity == IPowerSupply::Polarity::Negative)
+        cmd = ":CONF:OUTP:POL n, (@" + std::to_string(channel) + ")";
+    else
+        throw std::runtime_error("Invalid polarity");
+    send(cmd);
+}
+
+bool IsegPs::isOn(unsigned channel) {
+    if (m_maxChannels > 0) {  // In-range channel check
+        if (channel > m_maxChannels)
+            throw std::runtime_error("Invalid channel: " +
+                                     std::to_string(channel));
+    }
+    std::string const cmd = ":READ:VOLT:ON? (@" + std::to_string(channel) + ")";
+    auto recv = sendreceive(cmd);
+    if (recv == "1")
+        return true;
+    else if (recv == "0")
+        return false;
+    else
+        throw std::runtime_error(
+            "IsegPs::isOn: could not determine if channel is on");
+}
+
+std::string IsegPs::sendreceive(const std::string& cmd) {
+    ScopeLock lock(m_com);
+    auto rec = m_com->sendreceive(cmd);
+    // In case the IsegSHR20xxPs uses a serial communication via USB/Serial its
+    // responses are echoed to validate communication integrity in these cases,
+    // the result looks like: <CMD><Term><RES><Term>, the ultimate termination
+    // is already stripped by the Com implementation, here we need to split the
+    // <CMD> and <RES> and validate that the read back <CMD> is the one we sent
+    auto text_serial_port = dynamic_cast<TextSerialCom*>(m_com.get());
+    if (text_serial_port) {
+        auto term_seq = text_serial_port->returnTermination();
+        auto rec_split = utils::split(rec, term_seq);
+        if (cmd != rec_split.at(0))
+            throw std::runtime_error(
+                "IsegPs::sendreceive: readback of command failed!");
+        if (rec_split.size() != 2)
+            throw std::runtime_error(
+                "IsegPs::sendreceive: received invalid reaback!");
+        return rec_split.at(1);
+    } else {
+        return rec;
+    }
+}
+
+std::string IsegPs::sendreceive(const std::string& cmd, unsigned channel) {
+    if (m_maxChannels > 0) {  // In-range channel check
+        if (channel > m_maxChannels)
+            throw std::runtime_error("Invalid channel: " +
+                                     std::to_string(channel));
+    }
+    std::string retCmd = cmd;
+    if (m_maxChannels != 1) retCmd += " (@" + std::to_string(channel) + ")";
+
+    return sendreceive(retCmd);
+}
diff --git a/src/libPS/IsegPs.h b/src/libPS/IsegPs.h
new file mode 100644
index 0000000000000000000000000000000000000000..0a3687b1daeab5cb62a857af6fe96912a1e8e295
--- /dev/null
+++ b/src/libPS/IsegPs.h
@@ -0,0 +1,171 @@
+#ifndef ISEGPS_H
+#define ISEGPS_H
+
+#include <chrono>
+#include <memory>
+#include <string>
+
+#include "IPowerSupply.h"
+
+/**
+ * Base implemetation for iseg power supplies that share
+ * a very similar command set.
+ *
+ * Options are possible to add additional for error checking on
+ *  - channel checking (maximum number of channels, 0 means no check is
+ * performed)
+ */
+class IsegPs : public IPowerSupply {
+ public:
+    /**
+     * @param name Name of the power supply
+     * @param models List of supported models (empty means no check is
+     * performed)
+     * @param maxChannels Maximum number of channels in the power supply (0
+     * means no maximum)
+     */
+    IsegPs(const std::string& name, std::vector<std::string> models = {},
+           unsigned maxChannels = 0);
+    ~IsegPs() = default;
+
+    /** \name Communication
+     * @{
+     */
+
+    virtual bool ping();
+
+    /** @} */
+
+    /** \name Power Supply Control
+     * @{
+
+    /**
+     * Returns the model identifier of the PS connected
+     *
+     */
+    virtual std::string identify();
+
+    virtual void reset();
+
+    /** Turn on specific channel
+     *
+     * @param channel the channel to turn on
+     */
+    virtual void turnOn(unsigned channel);
+
+    /** Turn off specific channel
+     *
+     * @param channel the channel to turn off
+     */
+    virtual void turnOff(unsigned channel);
+
+    /**
+     * Returns true if the channel's output is on, and false otherwise
+     *
+     * @param channel the channel to query on state
+     */
+    virtual bool isOn(unsigned channel);
+
+    /** @} */
+
+    /** \name Current Control and Measurement
+     * @{
+     */
+
+    virtual void setCurrentLevel(double cur, unsigned channel = 0);
+    virtual double getCurrentLevel(unsigned channel = 0);
+    virtual void setCurrentProtect(double maxcur, unsigned channel = 0);
+    virtual double getCurrentProtect(unsigned channel = 0);
+    virtual double measureCurrent(unsigned channel = 0);
+
+    /** @} */
+
+    /** \name Voltage Control and Measurement
+     * @{
+     */
+
+    /**
+     * Will set the voltage to the requested value for a given channel. Will
+     * throw an exception if the channel is powered on and a polarity switch is
+     * requested. Takes the signedness of the passed argument in order to
+     * determine the polarity, i.e. -0 and 0 produce a different polarity
+     *
+     * @param volt the voltage to set
+     * @param channel the channel to set the voltage
+     */
+    virtual void setVoltageLevel(double volt, unsigned channel = 0);
+    virtual double getVoltageLevel(unsigned channel = 0);
+    virtual void setVoltageProtect(double maxvolt, unsigned channel = 0);
+    virtual double getVoltageProtect(unsigned channel = 0);
+    virtual double measureVoltage(unsigned channel = 0);
+
+    /** @} */
+
+    /** \name Polarity Control and Measurement
+     * @{
+     */
+
+    virtual IPowerSupply::Polarity getPolarity(unsigned channel = 0);
+    virtual void setPolarity(IPowerSupply::Polarity polarity,
+                             unsigned channel = 0);
+
+    /** @} */
+
+ protected:
+    //\brief Send power supply command and wait for operation complete.
+    /**
+     * Sends `cmd` to the power supply followed up `*OPC?`. Then blocks until
+     * a response is received. This prevents the program from terminating
+     * before the program is processed.
+     *
+     * An error is thrown if no response to `*OPC?` is seen.
+     *
+     * \param cmd Power supply command to transmit.
+     */
+    void send(const std::string& cmd);
+
+    //! \brief Add channel set to power supply send
+    /**
+     * Adds `" (@"+channel+")"` to the end of the actual command. This is
+     * done without injecting `*OPC?`.
+     *
+     * The rest is achieved using `IsegPs::send(constd std::string& cmd)`.
+     *
+     * \param cmd Power supply command to transmit.
+     * \param channel Target channel
+     */
+    void send(const std::string& cmd, unsigned channel);
+
+    //\brief Send power supply command and return response.
+    /**
+     * Wrapper around `ICom::sendreceive`.
+     *
+     * \param cmd Power supply command to transmit.
+     *
+     * \return Parameter response.
+     */
+    std::string sendreceive(const std::string& cmd);
+
+    //! \brief Add channel set to power supply sendreceive
+    /**
+     * Adds `" (@"+channel+")"` to the end of the actual command. This is
+     * done without injecting `*OPC?`.
+     *
+     * The rest is achieved using `IsegPs::sendreceive(constd std::string&
+     * cmd)`.
+     *
+     * \param cmd Power supply command to transmit.
+     * \param channel Target channel
+     *
+     * \return Response for `cmd`
+     */
+    std::string sendreceive(const std::string& cmd, unsigned channel);
+
+ private:
+    /** PS limitations @{ */
+
+    //! Maximum number of channels, 0 means unlimited
+    uint32_t m_maxChannels = 0;
+};
+
+#endif
diff --git a/src/libPS/IsegSHR20xxPs.cpp b/src/libPS/IsegSHR20xxPs.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..c470281c76706941454b0faab3466d3d3c72b087
--- /dev/null
+++ b/src/libPS/IsegSHR20xxPs.cpp
@@ -0,0 +1,11 @@
+#include "IsegSHR20xxPs.h"
+
+#include "StringUtils.h"
+#include "TextSerialCom.h"
+
+// Register power supply
+#include "PowerSupplyRegistry.h"
+REGISTER_POWERSUPPLY(IsegSHR20xxPs)
+
+IsegSHR20xxPs::IsegSHR20xxPs(const std::string& name)
+    : IsegPs(name, {"SR020020"}, 2) {}
\ No newline at end of file
diff --git a/src/libPS/IsegSHR20xxPs.h b/src/libPS/IsegSHR20xxPs.h
new file mode 100644
index 0000000000000000000000000000000000000000..0149fe02578c105526b64425623e0cc5f3d44f82
--- /dev/null
+++ b/src/libPS/IsegSHR20xxPs.h
@@ -0,0 +1,22 @@
+#ifndef ISEGSHR20XXPS_H
+#define ISEGSHR20XXPS_H
+
+#include <string>
+
+#include "IsegPs.h"
+
+/**
+ * Implementation for the [ISEG SHR 20XX Dual Channel Output HV Power
+ * Supplies](https://iseg-hv.com/en/products/detail/SHR).
+ * The dual-channel ISEG power supplies appear to support the default [ISEG SHR
+ * programming
+ * model](https://iseg-hv.com/download/SOFTWARE/isegSCPI/SCPI_Programmers_Guide_en.pdf),
+ * but this has not been checked.
+ */
+class IsegSHR20xxPs : public IsegPs {
+ public:
+    IsegSHR20xxPs(const std::string& name);
+    ~IsegSHR20xxPs() = default;
+};
+
+#endif  // ISEGSHR20XXPS_H
diff --git a/src/libPS/RS_NGP804.cpp b/src/libPS/RS_NGP804.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..4d9fa630df527736833eba95154b75100d334961
--- /dev/null
+++ b/src/libPS/RS_NGP804.cpp
@@ -0,0 +1,12 @@
+#include "RS_NGP804.h"
+
+#include <algorithm>
+#include <thread>
+
+#include "Logger.h"
+
+// Register power supply
+#include "PowerSupplyRegistry.h"
+REGISTER_POWERSUPPLY(RS_NGP804)
+
+RS_NGP804::RS_NGP804(const std::string& name) : SCPIPs(name, {"NGP804"}, 4) {}
diff --git a/src/libPS/RS_NGP804.h b/src/libPS/RS_NGP804.h
new file mode 100644
index 0000000000000000000000000000000000000000..f6b4f1918c9faad9fa8161aaed2a4f9ec7ca100f
--- /dev/null
+++ b/src/libPS/RS_NGP804.h
@@ -0,0 +1,24 @@
+#ifndef RS_NGP804_H
+#define RS_NGP804_H
+
+#include <chrono>
+#include <memory>
+#include <string>
+
+#include "SCPIPs.h"
+#include "SerialCom.h"
+
+/** \brief ROHDE&SCHWARZ NGP804
+ * Implementation for the ROHDE&SCHWARZ NGP804 power supply.
+ *
+ * [Programming
+ * Manual](https://scdn.rohde-schwarz.com/ur/pws/dl_downloads/pdm/cl_manuals/user_manual/5601_5610_01/NGP800_User_Manual_en_11.pdf)
+ *
+ */
+class RS_NGP804 : public SCPIPs {
+ public:
+    RS_NGP804(const std::string& name);
+    ~RS_NGP804() = default;
+};
+
+#endif
diff --git a/src/libPS/TTIXXXSPPs.cpp b/src/libPS/TTIXXXSPPs.cpp
index 2ac101ad424942a9a6814450f12cb8c1daaac8c2..1abd8958f969b37924accc7ce91e797e60b7dda3 100644
--- a/src/libPS/TTIXXXSPPs.cpp
+++ b/src/libPS/TTIXXXSPPs.cpp
@@ -10,4 +10,4 @@
 REGISTER_POWERSUPPLY(TTIXXXSPPs)
 
 TTIXXXSPPs::TTIXXXSPPs(const std::string& name)
-    : TTIPs(name, {"TSX1820P"}, 1) {}
+    : TTIPs(name, {"TSX1820P", "CPX400SP"}, 1) {}
diff --git a/src/libPS/TTIXXXTPPs.cpp b/src/libPS/TTIXXXTPPs.cpp
index aec28418255f4f9a2201cc479cb048027bc7868a..434ce07dd70e9d11d6edc67302b267005fa29056 100644
--- a/src/libPS/TTIXXXTPPs.cpp
+++ b/src/libPS/TTIXXXTPPs.cpp
@@ -10,4 +10,4 @@
 REGISTER_POWERSUPPLY(TTIXXXTPPs)
 
 TTIXXXTPPs::TTIXXXTPPs(const std::string& name)
-    : TTIPs(name, {"MX180TP", "QL355TP"}, 3) {}
+    : TTIPs(name, {"MX100TP", "MX180TP", "QL355TP"}, 3) {}
diff --git a/src/libPS/python.cpp b/src/libPS/python.cpp
index 75ba925113d857d1a8344016a3daf10c7f82917d..1e866c49de6a83c00e37b480ae3a41d3e69083f6 100644
--- a/src/libPS/python.cpp
+++ b/src/libPS/python.cpp
@@ -9,13 +9,17 @@
 #include "DT5471NPs.h"
 #include "DT5472NPs.h"
 #include "DT54xxPs.h"
+#include "DT8033NPs.h"
 #include "IPowerSupply.h"
+#include "IsegPs.h"
+#include "IsegSHR20xxPs.h"
 #include "Keithley22XX.h"
 #include "Keithley24XX.h"
 #include "PowerSupplyChannel.h"
 #include "PowerSupplyRegistry.h"
 #include "RS_HMP2020.h"
 #include "RS_HMP4040.h"
+#include "RS_NGP804.h"
 #include "RigolDP832.h"
 #include "SCPIPs.h"
 #include "SorensenPs.h"
@@ -252,7 +256,7 @@ void register_ps(py::module &m) {
 
     py_DT54xxPs
         .def(py::init<const std::string &, const std::vector<std::string> &,
-                      DT54xxPs::Polarity, double>())
+                      IPowerSupply::Polarity, double>())
         .def("status", &DT54xxPs::status)
         .def("polarity", &DT54xxPs::polarity)
         .def("setIMonRange", &DT54xxPs::setIMonRange)
@@ -274,8 +278,8 @@ void register_ps(py::module &m) {
         .value("CalError", DT54xxPs::Status::CalError);
 
     py::enum_<DT54xxPs::Polarity>(py_DT54xxPs, "Polarity")
-        .value("Positive", DT54xxPs::Polarity::Positive)
-        .value("Negative", DT54xxPs::Polarity::Negative);
+        .value("Positive", IPowerSupply::Polarity::Positive)
+        .value("Negative", IPowerSupply::Polarity::Negative);
 
     py::enum_<DT54xxPs::IMonRange>(py_DT54xxPs, "IMonRange")
         .value("High", DT54xxPs::IMonRange::High)
@@ -287,6 +291,21 @@ void register_ps(py::module &m) {
     py::class_<DT5472NPs, DT54xxPs, std::shared_ptr<DT5472NPs>>(m, "DT5472NPs")
         .def(py::init<const std::string &>());
 
+    py::class_<DT8033NPs, PyPS<DT8033NPs>, IPowerSupply,
+               std::shared_ptr<DT8033NPs>>(m, "DT8033NPs")
+        .def(py::init<const std::string &>());
+
+    py::class_<IsegPs, PyPS<IsegPs>, IPowerSupply, std::shared_ptr<IsegPs>>(
+        m, "IsegPs")
+        .def(py::init<const std::string &, std::vector<std::string> &,
+                      unsigned>(),
+             py::arg("name"), py::arg("models") = py::list(),
+             py::arg("maxChannels") = 0);
+
+    py::class_<IsegSHR20xxPs, IsegPs, std::shared_ptr<IsegSHR20xxPs>>(
+        m, "IsegSHR20xxPs")
+        .def(py::init<const std::string &>());
+
     py::class_<Keithley22XX, PyPS<Keithley22XX>, IPowerSupply,
                std::shared_ptr<Keithley22XX>>(m, "Keithley22XX")
         .def(py::init<const std::string &>());
@@ -305,6 +324,9 @@ void register_ps(py::module &m) {
     py::class_<RS_HMP2020, SCPIPs, std::shared_ptr<RS_HMP2020>>(m, "RS_HMP2020")
         .def(py::init<const std::string &>());
 
+    py::class_<RS_NGP804, SCPIPs, std::shared_ptr<RS_NGP804>>(m, "RS_NGP804")
+        .def(py::init<const std::string &>());
+
     py::class_<RigolDP832, SCPIPs, std::shared_ptr<RigolDP832>>(m, "RigolDP832")
         .def(py::init<const std::string &>());
 
diff --git a/src/libUtils/StringUtils.h b/src/libUtils/StringUtils.h
index b510ed08f837dd31d2c7577a692fc346026e34f2..2ef2b6df77b3990f23d7dbac80b7007b19ac9137 100644
--- a/src/libUtils/StringUtils.h
+++ b/src/libUtils/StringUtils.h
@@ -3,6 +3,7 @@
 
 #include <algorithm>
 #include <iomanip>
+#include <sstream>
 #include <string>
 #include <vector>
 
@@ -85,6 +86,11 @@ static inline std::string to_string_with_precision(const T a_value,
     return out.str();
 }
 
+static inline std::string trim_last_char(std::string str) {
+    str.pop_back();
+    return str;
+}
+
 };  // namespace utils
 
 #endif  // STRINGUTILS_H
diff --git a/src/tools/TfromNTC.cpp b/src/tools/TfromNTC.cpp
index af81556395a34c71afb4cc9eb7f5693c9a615797..86b3fc4c7eeb652734719c2fc0d927f0c434faa1 100644
--- a/src/tools/TfromNTC.cpp
+++ b/src/tools/TfromNTC.cpp
@@ -168,10 +168,14 @@ int main(int argc, char* argv[]) {
         // Setup utility to convert NTC resistance to temperature.
         // Note: We read R_ntc directly from a meter, so no need to setup
         // ADCDevice. We had to expose RtoC in NTCSensor class.
-        // Default parameters
-        float ntc_para_A = 0.8676453371787721E-3;
-        float ntc_para_B = 2.541035850140508E-4;
-        float ntc_para_C = 1.868520310774293E-7;
+        // Default parameters for B57230V2103F260 which is the DCS NTC on the
+        // ITkPix v1.1 quad flex Datasheet
+        // https://www.tdk-electronics.tdk.com/inf/50/db/ntc/NTC_SMD_Standard_series_0402.pdf
+        // Parameters obtained using fit
+        // https://gitlab.cern.ch/kbai/ntc-param-fitter
+        float ntc_para_A = 8.76745581E-4;
+        float ntc_para_B = 2.53169819E-4;
+        float ntc_para_C = 1.86485152E-7;
         // Set the ntc parameters if provided
         if (params.size() == 3) {
             logger(logDEBUG)
diff --git a/src/tools/chiller.cpp b/src/tools/chiller.cpp
index a39cd77b7c6a664fab781c97643918f6848e5001..d4d4eec80994bc7085ab7ddc21b052726a497088 100644
--- a/src/tools/chiller.cpp
+++ b/src/tools/chiller.cpp
@@ -31,6 +31,10 @@ void usage(char* argv[]) {
     std::cerr << " meas-temp        Get reading of current temperature in "
                  "degrees-Celsius"
               << std::endl;
+    std::cerr << " meas-pressure        Get reading of current pressure in PSI"
+              << std::endl;
+    std::cerr << " meas-flow        Get reading of current flow in LPM"
+              << std::endl;
     std::cerr << " turn-on          Turn on the chiller" << std::endl;
     std::cerr << " turn-off         Turn off the chiller" << std::endl;
     std::cerr << "List of options:" << std::endl;
@@ -172,6 +176,16 @@ int main(int argc, char* argv[]) {
         std::cout << chiller->measureTemperature();
         if (logIt::loglevel >= logDEBUG) std::cout << " deg-C";
         std::cout << std::endl;
+    } else if (command == "meas-pressure") {
+        if (logIt::loglevel >= logDEBUG) std::cout << "Measured-pressure: ";
+        std::cout << chiller->measurePressure();
+        if (logIt::loglevel >= logDEBUG) std::cout << " PSI";
+        std::cout << std::endl;
+    } else if (command == "meas-flow") {
+        if (logIt::loglevel >= logDEBUG) std::cout << "Measured-flow: ";
+        std::cout << chiller->measureFlow();
+        if (logIt::loglevel >= logDEBUG) std::cout << " LPM";
+        std::cout << std::endl;
     } else if (command == "turn-on") {
         logger(logDEBUG) << "Turning chiller ON";
         chiller->turnOn();