Skip to content

cs150server: MATLAB/Python-CS150 Bridge -- Control Konica-Minolta CS-150/160 from MATLAB/Python seamlessly via a tiny C# command server.

License

Notifications You must be signed in to change notification settings

hiroshiban/cs150server

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

13 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

README on cs150server

Created : "2025-10-07 11:33:12 ban"
Last Update: "2025-10-08 13:55:41 ban"






cs150server

This repository provides tiny but useful (hopefully) tools to control a Konica-Minolta CS-150/160 colorimeter from MATLAB/Python. After the update of the colorimeter from CS-100A to CS-150, Konica-Minolta discontinued to provide low-level libraries to communicate with the colorimeter through a simple serial communication protocol. Instead, the manufacture now provides a bit higher-level C# libraries only. This update may be useful in some specific situations, while it makes the communications from the external (non-C#) programs, such as MATLAB and Python, difficult. Therefore, I developed simple tools to control the CS-150/160 colorimeter from MATLAB/Python via a tiny C# command server.

NOTE: if you copy cs150.m and cs150server directory to ~/subfunctions/colorimeter directory of my inhouse software package, Mcalibrator2, you can use cs-150 with Mcalibrato2 on MATLAB.

The repository contains only original source codes: a MATLAB client (cs150.m), a Python client (cs150.py), and a lightweight C# command server (cs150server.exe) that you build locally.

No Konica-Minolta vendor DLLs or SDK files are distributed here, following the license and EULA provided by the manufacture. You must obtain the official LC‑MISDK from Konica-Minolta website, and follow their license terms.

Konica-Minolta’s LC‑MISDK is a .NET SDK for CS-150/160 (and LS-150) luminance/color meters. Direct loading of the C# DLLs, for instance, by calling the AddAssenbly() function inside MATLAB would stack for its complicated dependencies on the external libraries. To overcome this difficulty, this project inserts a minimal C# server between MATLAB/Python and the SDK; MATLAB/Python sends text commands over stdin/stdout, and the server returns measured values as CSV.

LC‑MISDK officially lists CS-150/160 (and LS-150/160) as compatible instruments and supports building both 32‑bit and 64‑bit applications.

Note on CS‑200: The CS‑200 uses a separate USB driver package with its own DLL reference and communication spec. That driver is 32‑bit only (apps must be 32‑bit even on 64‑bit OS). If you need CS‑200, please implement a dedicated backend (separate branch/target).

back to the menu

[MATLAB(cs150.m)/Python(cs150.py)] ⇄ [stdin/stdout] ⇄ [C# server:cs150server.exe] ⇄ [LC-MISDK] ⇄ [USB driver] ⇄ [CS-150/160]

  • MATLAB sends textual commands: CONNECT, INTEG 0.5, MEASURE, …
  • Server calls LC‑MISDK and returns SUCCESS,<Lv(cd/m^2)>,<x>,<y> or ERROR,<reason>.
  • Tested on Windows 10/11.

back to the menu

  • OS: Windows 10/11
  • .NET: .NET Framework 4.6.1+ (4.7–4.8 recommended)
  • IDE: Visual Studio 2022 (Community is fine)
  • MATLAB: R2022a+ (tested, for stable .NET interop and System.Diagnostics.Process)
  • SDK: LC‑MISDK for CS‑150/CS‑160 (download and extract ZIP; follow the Reference Manual). LC‑MISDK supports building 32‑bit and 64‑bit apps and lists CS‑150/CS‑160 / LS‑150/LS‑160 as compatible.

If you need CS‑200: Use the official CS‑200 USB Driver package. It’s a 32‑bit driver; your app must be x86 even on 64‑bit Windows.

back to the menu

this_repo/
├─ cs150.m (a MATLAB class which communicate with cs150server)
├─ cs150.py (a Python class which communicate with cs150server)
├─ cs150server_csharp/ # C# .NET Framework Console App (this repo)
│ ├─ cs150server.cs (source code)
│ ├─ cs150server.csproj
│ └─ (other .cs files)
└─ docs/
└─ images/ # screen shots etc

back to the menu

Since this repository can not contain any DLLs distributed by Konica-Minolta and the built exexutable, I will leave step-by-step instructions on how to build the cs150server.exe as below.

back to the menu

  1. Open the "Device Manager" by right-clicking the Windows START icon. Then, open the "Ports (COM & LPT)" tab.

driver_01

  1. Select the "USB Serial Device (COM ***)" item, and right click it. Then, select the "Update driver."

driver_02

  1. Select the "Browse my computer for drivers."

driver_03

  1. Set the cs150 driver in LC-MISDK distributed by Konica-Minolta, and select "Next."

driver_04

  1. Press the "Install" button.

driver_05

  1. Once done, the driver will be updated. Please check the name "KONICA MINOLTA, INC." is found in the driver provider section.

driver_06

Now, we are ready for building cs150server.exe.

back to the menu

  1. Create a .NET Framework Console App in Visual Studio (name it as 'cs150server'). Specifically, select "Create a new project" and then select "Console App (.NET Framework)." Please don't select just a simple "Console App" without .NET Framework. Finally, please set the project name, locations etc in the following input window.

server_01 server_02 server_03

Then, please copy "nupkg" and "packages" directories to the generated cs150server directory.

The generated project directory.

server_09

After copying the "nupkg" and "packages" directories.
server_10

  1. The generated windows will look as below.

server_04

  1. Then, install the required libraries using the "nupkg" package system (= adding references to the LC-MISDK assemblies distributed by Konica-Minolta). Firstly, please right-click the "cs150server" shown on the upper right panel. Then, in the popup window, please select the "Manage NuGet Packages..."

server_05

  1. Then, install the LC-MISDK package. After the procedure 3. above, the "NuGet Packages" managing window will be shown as below.

server_06

Please select the "gear" icon on the upper right corner of the main window. Then, the "Option" window will be shown as below. In this window, please press "+" button and add the source by selecting the "nuget" directoy just copied to the project in the 1. procedure above.

server_07 server_11

Once added, the nuget path will be shown in the "Option " window as below.

server_08 server_12

If you confirm the directory is properly added as a source, please press the "OK" button and close the window.

Next, in the "NuGet Packages" managing window, select the "package source" to install LC-MISDK. You can find the "LC-MISDK" name on the left panel. Then, please press "Install" button on the right panel. Please select "OK" and "I Accept" in the following panels.

server_13 server_14 server_15

Once the dependent packages are installed, all the references will be added in the right project panel.

server_16

The contents of the project directory (cs150server) after this step will look as below.

server_17

  1. Next, let's write the server source code (but actually what you need to do is just to copy...)

Please select the Program.cs file from the right panel.

server_18

Then, please overwrite Program.cs by copying and pasting the contents of cs150server.cpp in this repository.

server_19

  1. Finally, you are ready to build the "cs150server.exe."

Please select the "build" in the manu bar (see the figure above). Then, from the sub menu, please select "Build Solution."

server_20

If no error is found, the executable will be build in ~/bin/(Release|Debug)/ directory. The dependent libraries will be also copied to the same directory.

server_21

The contens of the ~/bin/Release directory after successfully build the source.

server_22

  1. You can now communicate with CS-150 via MATLAB and Python using the built server program.

To do this, for instance, with MATLAB, please rename "(Release|Debug)" directory as "cs150server" and place this with cs150.m (or cs150.py in Python case) as below.

server_23

If you need to customize the subroutine, please refer the LC-MISDK documents and modify the source of CS150.m or CS150.py.

server_24

back to the menu

// SPDX-License-Identifier: BSD-2-Clause
/**
 * @file    cs150server.cs
 * @brief   A tiny stdio command server to operate Konica Minolta CS‑150/CS‑160
 *          from MATLAB and Python (bridging MATLAB/Python and LC‑MISDK through
 *          an external process).
 *
 * @details
 *  This program inserts a minimal out‑of‑process server between MATLAB (`cs150.m`)
 *  and Konica Minolta’s LC‑MISDK to avoid in‑process DLL/.NET loading issues and
 *  improve robustness. It reads one text command per line from stdin and writes
 *  CSV responses to stdout.
 *
 *  [Licensing & Redistribution]
 *   - This repository ships only original code. No vendor SDK/DLL/manuals are included.
 *     Obtain LC‑MISDK legitimately and follow the vendor’s license terms.
 *   - Do NOT commit or redistribute vendor DLLs with this source tree.
 *
 *  [Supported instruments]
 *   - Default backend targets CS‑150/CS‑160 (via LC‑MISDK).
 *   - CS‑200 requires a different driver/DLL and is out of scope here (can be added as
 *     a separate backend/target).
 *
 *  [Command protocol (MATLAB ⇄ server)]
 *   - CONNECT            -> SUCCESS,Connected to CS-150
 *   - INTEG <sec>|AUTO   -> SUCCESS,Integration time set
 *   - MEASURE            -> SUCCESS,<Lv(cd/m^2)>,<x>,<y>
 *   - DISCONNECT         -> SUCCESS,Disconnected (or silent)
 *   - EXIT               -> (process terminates)
 *   * Non-'SUCCESS' responses indicate errors.
 *   * Numbers are emitted with dot decimal (InvariantCulture semantics).
 *
 *  [Build requirements (example)]
 *   - Windows 10/11
 *   - MSVC / Visual Studio 2019+ (C++17 or later recommended)
 *   - If using C++/CLI to call .NET APIs: enable /clr and reference .NET Framework 4.6.1+
 *   - Place vendor DLLs next to the executable at runtime only; keep them out of the repo
 *   - Suggested flags: /std:c++17 /W4 /permissive- /utf-8 /EHsc /DUNICODE /D_UNICODE
 *
 *  [Design notes]
 *   - Single-threaded line-processing loop on stdin; emits machine-readable stdout only.
 *   - Exit code 0 on success; 1 on fatal initialization failures.
 *   - Operational logs (if any) should go to stderr to keep stdout parseable.
 *
 *  @author   (Your Name / Affiliation)
 *  @version  (e.g., 1.0.0)
 *  @date     (YYYY-MM-DD)
 *  @license  MIT for the original code. Vendor SDKs/drivers remain under their own licenses.
 *  @see      README.md (setup, caveats, extensions)
 *  @note     Unofficial project; provided “as is,” without warranty of any kind.
 *
 *
 * Created    : "2025-09-30 15:02:28 ban"
 * Last Update: "2025-10-07 14:26:46 ban"
 */

using System;
using Konicaminolta;

namespace cs150server
{
    class Program
    {
        // holding the sdk and connection status as class members
        private static LightColorMISDK sdk;
        private static bool isConnected = false;

        static void Main(string[] args)
        {
            // when this execute is launched, sdk is initialized following the standard procedure and contexts.
            try
            {
                sdk = LightColorMISDK.GetInstance();
            }
            catch (Exception ex)
            {
                // terminating the execute when initialization is failed.
                Console.Error.WriteLine("FATAL: SDK GetInstance failed. " + ex.Message);
                return;
            }

            // infinite loop to wait for the command input(s) from MATLAB
            string line;
            while ((line = Console.ReadLine()) != null)
            {
                // separate the input string by space
                string[] parts = line.Split(new[] { ' ' }, 2, StringSplitOptions.RemoveEmptyEntries);
                if (parts.Length == 0) continue; // ignore an empty line

                string command = parts[0].ToUpper(); // commande (convert to upper case)
                string argument = parts.Length > 1 ? parts[1] : null; // parameter (if any)

                // select the operation based on the input command.
                switch (command)
                {
                    case "CONNECT":
                        HandleConnect();
                        break;

                    case "MEASURE":
                        HandleMeasure();
                        break;

                    case "INTEG":
                        HandleSetIntegrationTime(argument);
                        break;

                    case "BACKLIGHTON":
                        HandleBackLightON();
                        break;

                    case "BACKLIGHTOFF":
                        HandleBackLightOFF();
                        break;

                    case "DISCONNECT":
                        HandleDisconnect();
                        break;

                    case "EXIT":
                        HandleDisconnect();
                        return;

                    default:
                        Console.WriteLine("ERROR,Unknown command");
                        break;
                }
            }
        }

        private static void HandleConnect()
        {
            if (isConnected)
            {
                Console.WriteLine("SUCCESS,Already connected");
                return;
            }
            try
            {
                ReturnMessage ret = sdk.Connect();
                if (ret.errorCode == ErrorDefine.KmSuccess)
                {
                    isConnected = true;
                    Console.WriteLine("SUCCESS,Connected to CS-150");
                }
                else
                {
                    Console.WriteLine("ERROR," + ret.errorCode.ToString());
                }
            }
            catch (Exception ex)
            {
                // capture the error related to the SimpleInjector
                Console.WriteLine("ERROR," + ex.Message);
            }
        }

        private static void HandleMeasure()
        {
            if (!isConnected)
            {
                Console.WriteLine("ERROR,Not connected");
                return;
            }

            // simple xyY measurement
            var ret = sdk.Measure();
            if (ret.errorCode != ErrorDefine.KmSuccess)
            {
                Console.WriteLine("ERROR,Measure command failed");
                return;
            }

            MeasStatus state;
            do
            {
                System.Threading.Thread.Sleep(100);
                ret = sdk.PollingMeasurement(out state);
                if (ret.errorCode != ErrorDefine.KmSuccess)
                {
                    Console.WriteLine("ERROR,Polling failed");
                    return;
                }
            } while (state == MeasStatus.Measuring);

            Lvxy lvxyValue = new Lvxy(LuminanceUnit.cdm2);
            ret = sdk.ReadLatestData(lvxyValue);
            if (ret.errorCode != ErrorDefine.KmSuccess)
            {
                Console.WriteLine("ERROR,ReadLatestData failed");
                return;
            }

            // output the measurement result as a string with "," as a delimiter
            Console.WriteLine($"SUCCESS,{lvxyValue.Lv},{lvxyValue.x},{lvxyValue.y}");
        }

        private static void HandleSetIntegrationTime(string argument)
        {
            if (!isConnected)
            {
                Console.WriteLine("ERROR,Not connected");
                return;
            }

            if (string.IsNullOrEmpty(argument))
            {
                Console.WriteLine("ERROR,Integration time value is missing. Use 'INTEG AUTO' or 'INTEG <seconds>'.");
                return;
            }

            // MeasurementTime object
            MeasurementTime inputTime = new MeasurementTime();

            // whether the parameter is "AUTO" or not.
            if (argument.Equals("AUTO", StringComparison.OrdinalIgnoreCase))
            {
                inputTime.MeasTimeMode = MeasTimeMode.Auto;
            }
            else
            {
                // if not "AUTO", convert it to an integer
                if (double.TryParse(argument, out double timeInSeconds))
                {
                    inputTime.MeasTimeMode = MeasTimeMode.Manual;
                    inputTime.ManualMeasurementTime = timeInSeconds;
                }
                else
                {
                    // failed to convert the input parameter to an integer
                    Console.WriteLine("ERROR,Invalid time value. It must be 'AUTO' or a number.");
                    return;
                }
            }

            // set integration time
            var ret = sdk.SetMeasurementTime(inputTime);
            if (ret.errorCode == ErrorDefine.KmSuccess)
            {
                Console.WriteLine("SUCCESS,Integration time set");
            }
            else
            {
                Console.WriteLine("ERROR,Failed to set integration time with code: " + ret.errorCode.ToString());
            }
        }

        private static void HandleBackLightON()
        {
            if (!isConnected)
            {
                Console.WriteLine("ERROR,Not connected");
                return;
            }

            // Set the backlight on
            BackLightMode mode = BackLightMode.On;
            var ret = sdk.SetBackLightOnOff(mode);
            if (ret.errorCode == ErrorDefine.KmSuccess)
            {
                Console.WriteLine("SUCCESS, Backlight ON");
            }
            else
            {
                Console.WriteLine("ERROR," + ret.errorCode.ToString());
            }
        }

        private static void HandleBackLightOFF()
        {
            if (!isConnected)
            {
                Console.WriteLine("ERROR,Not connected");
                return;
            }

            // Set the backlight off
            BackLightMode mode = BackLightMode.Off;
            var ret = sdk.SetBackLightOnOff(mode);
            if (ret.errorCode == ErrorDefine.KmSuccess)
            {
                Console.WriteLine("SUCCESS, Backlight OFF");
            }
            else
            {
                Console.WriteLine("ERROR," + ret.errorCode.ToString());
            }
        }

        private static void HandleDisconnect()
        {
            if (isConnected)
            {
                sdk.DisConnect(0); // disconnection
                isConnected = false;
            }
        }
    }
}

back to the menu

Command Format Example response
Connect CONNECT SUCCESS,Connected to CS-150
Set integration time INTEG <seconds> SUCCESS,Integration time set
Set auto integration INTEG AUTO SUCCESS,Integration time set
Single measurement MEASURE SUCCESS,<Lv(cd/m^2)>,<x>,<y>
Disconnect DISCONNECT SUCCESS,Disconnected (or silent)
Quit server EXIT (process exits)

Notes:
Decimal is dot (.). The server uses InvariantCulture.
Any non‑SUCCESS line should be treated as an error.

back to the menu

Place cs150.m next to a cs150server/ directory that contains your built cs150server.exe (plus any SDK-managed dependencies).

your_project/
├─ cs150.m
└─ cs150server/
└─ cs150server.exe # you have to built this locally
└─ all dependent DLLs

back to the menu

classdef cs150 < handle
  % A class to manipulate Konica Minolta CS-150
  % by launching and controlling a custom-made C# server executable.
  % This class and the following methods will work only on the "Windows" OS.
  %
  % [usage]
  % % launching server and connect to the colorimeter
  % photometer = cs150();
  % photometer.gen_port();
  %
  % % setting the integration time
  % photometer.set_integration_time(0.5);
  %
  % % run measurement
  % [Y, x, y] = photometer.measure();
  % fprintf('Measurement with 0.5s integration:\n');
  % fprintf('  Luminance: %.4f cd/m^2, xy: (%.4f, %.4f)\n\n', Y, x, y);
  %
  % % close the server
  % clear photometer;
  %
  %
  % Created    : "2025-09-25 12:40:30 ban"
  % Last Update: "2025-10-06 14:38:44 ban"

  properties (SetAccess = private)
    port_name='COM1';  % a dummy variable for consistency with the other functions.
    rscom=[];          % a dummy variable for consistency with the other functions.
    process=[];      % Handle for the external C# process
    inputStream=[];  % Stream to send commands to the C# server
    outputStream=[]; % Stream to receive results from the C# server
    init_flg=false;
  end

  methods
    % constructor
    function obj=cs150(port_name)
      disp('Starting CS-150 measurement server...');

      % setting a path to cs150server.exe
      serverDir=fileparts(mfilename('fullpath'));
      serverPath=fullfile(serverDir,'cs150server','cs150server.exe');

      if ~exist(serverPath,'file')
        error('cs150server.exe not found at the expected location: %s', serverPath);
      end

      try
        % launch the external exe file by using .NET Process class.
        psInfo=System.Diagnostics.ProcessStartInfo(serverPath);
        psInfo.UseShellExecute=false;
        psInfo.RedirectStandardInput=true;  % redirecting the standard input
        psInfo.RedirectStandardOutput=true; % redirecting the standard output
        psInfo.CreateNoWindow=true;         % madking the console window invisible

        obj.process=System.Diagnostics.Process.Start(psInfo);

        % get streams for sending/receiving commands
        obj.inputStream=obj.process.StandardInput;
        obj.outputStream=obj.process.StandardOutput;

        % wait until the server is launched.
        pause(1.0);
        disp('Measurement server started.');

        if nargin==1 && ~isempty(port_name)
          obj.port_name=port_name;
        end

      catch ME
        error('Failed to start Cs150Server.exe. %s', ME.message);
      end
    end

    % destructor
    function delete(obj)
      if ~isempty(obj.process) && ~obj.process.HasExited
        disp('Shutting down measurement server...');
        % terminating command
        obj.inputStream.WriteLine('EXIT');
        % waiting for the termination of the prcesess
        obj.process.WaitForExit(3000); % for 3 sec
        obj.process.Close();
        disp('Server shut down.');

        % Here, we should unload the .NET assemby...However, accourding to MathWorks support site...
        % Rebooting is the only solution to this problem for the current Matlab Version (8.1.0.604, R2013a):
        % "The ability to unload an assembly is not available in MATLAB at this point of time. This may be
        % addressed in one of the future releases. Currently, to work around this issue, restart MATLAB."
        %
        % Therefore, we don't do anything
      end
    end

    % connect
    function obj=gen_port(obj,port_name)
      if nargin>1 && ~isempty(port_name), obj.port_name=port_name; end
      if obj.init_flg, disp('Already connected.'); return; end

      % sending 'CONNECT' command
      obj.inputStream.WriteLine('CONNECT');
      % receiving the reaction from the server
      response=char(obj.outputStream.ReadLine());

      parts=strsplit(response,',');
      if strcmp(parts{1},'SUCCESS')
        obj.init_flg=true;
        fprintf('Successfully connected: %s\n', parts{2});
      else
        error('Connection failed: %s', response);
      end
    end

    % reset a serial port connection
    function obj=reset_port(obj)
      obj.rscom=[];
      obj.init_flg=0;
      obj.disconnect();
    end

    % initialize CS-150
    function [obj,check,integtime]=initialize(obj,integtime)
      if nargin<2 || isempty(integtime), integtime=0.4; end

      % Sets the integration time of the CS-150.
      %
      % arugments:
      %   time: Can be a number (in seconds) for manual mode,
      %         or the string 'auto' for automatic mode.

      if ~obj.init_flg
        error('Not connected. Call connect() first.');
      end

      % making command string
      if isnumeric(integtime) && integtime > 0
        % command example: "INTEG 2"
        command = sprintf('INTEG %f',integtime);
      elseif ischar(integtime) && strcmpi(integtime,'auto')
        command = 'INTEG AUTO';
      else
        error('Invalid input. Time must be a positive number or the string ''auto''.');
      end

      % sending the generated command
      obj.inputStream.WriteLine(command);
      % receiving the reaction from the server
      response=char(obj.outputStream.ReadLine());

      % check the response
      if ~startsWith(response,'SUCCESS')
        check = 1;
        error('Failed to set integration time: %s', response);
      else
        check = 0;
        disp('Integration time set successfully.');
      end
    end

    % measure
    function [qq,Y,x,y,obj]=measure(obj,integtime)
      if nargin<2 || isempty(integtime), integtime=0.4; end

      if ~obj.init_flg
        error('Not connected. Call connect() first.');
      end

      % sending the 'MEASURE' command
      obj.inputStream.WriteLine('MEASURE');
      % receiving the reaction from the server
      response=char(obj.outputStream.ReadLine());

      parts=strsplit(response, ',');
      if strcmp(parts{1},'SUCCESS')
        Y=str2double(parts{2});
        x=str2double(parts{3});
        y=str2double(parts{4});
        qq=0;
      else
        Y=NaN; x=NaN; y=NaN;
        error('Measurement failed: %s', response);
        qq=1;
      end
    end

    % backlight
    function backlight(obj,mode)
      if nargin<2 || isempty(mode), mode=1; end

      if ~obj.init_flg
        error('Not connected. Call connect() first.');
      end

      % sending the 'MEASURE' command
      if mode==1
        obj.inputStream.WriteLine('BACKLIGHTON');
      else % if mode==0
        obj.inputStream.WriteLine('BACKLIGHTOFF');
      end

      % receiving the reaction from the server
      response=char(obj.outputStream.ReadLine());
      % check the response
      if ~startsWith(response,'SUCCESS')
        error('Failed to set backlight: %s', response);
      else
        disp('backlight set successfully.');
      end
    end

    % disconnect
    function disconnect(obj)
      if obj.init_flg
        obj.inputStream.WriteLine('DISCONNECT');
        obj.init_flg = false;
        obj.rscom=[];
        disp('Disconnected.');
      end
    end

  end % methods

end % classdef cs150

back to the menu

ph = cs150();                  % starts the server
ph.gen_port();                 % connects the instrument, the name is to match with the Mcalibrator2 conventions.
ph.set_integration_time(0.5);  % or: ph.set_integration_time('auto')
[qq,Y,x,y] = ph.measure();     % measurement
fprintf('Lv=%.4f cd/m^2, xy=(%.4f, %.4f)\n', Y, x, y);
clear ph                       % server exits automatically

back to the menu

Please place cs150.py next to a cs150server/ directory that contains your-built cs150server.exe (plus any SDK-managed dependencies).

your_project/
├─ cs150.py
└─ cs150server/
└─ cs150server.exe # you have to built this locally
└─ all dependent DLLs

back to the menu

import subprocess
import os
import time
import sys
from typing import Tuple

class CS150:
    """
    Python client to control Konica-Minolta CS-150/160.
    It runs Cs150Server.exe made of C# in the background and operates via inter-process communication.
    """

    def __init__(self, server_dir: str = 'cs150server'):
        """
        Initialize an instance of CS150 and start the C# server process.
        Args:
            server_dir (str): Relative path to the folder where the server exe is stored, as seen from cs150.py.
        """
        print("Starting CS-150 measurement server...")

        server_path = os.path.join(os.path.dirname(__file__), server_dir, 'cs150server.exe')

        if not os.path.exists(server_path):
            raise FileNotFoundError(f"Server executable not found at: {server_path}")

        # Flag to hide the console window in Windows
        CREATE_NO_WINDOW = 0x08000000

        # Start C# server as a sub-process
        if sys.version_info >= (3, 7):
            # Python 3.7 and later: text=True
            self.process = subprocess.Popen(
                [server_path],
                stdin=subprocess.PIPE,
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE,
                text=True,
                bufsize=1,
                creationflags=CREATE_NO_WINDOW
            )
        else:
            # Python 3.6 and before: universal_newlines=True, not text=True
            self.process = subprocess.Popen(
                [server_path],
                stdin=subprocess.PIPE,
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE,
                universal_newlines=True,
                bufsize=1,
                creationflags=CREATE_NO_WINDOW
            )

        time.sleep(1.5) # Wait a few moments for the server to fully boot up.

        if self.process.poll() is not None:
            # If the server terminates immediately after startup
            error_output = self.process.stderr.read()
            raise RuntimeError(f"Failed to start server. It exited with code {self.process.returncode}. Error: {error_output}")

        print("Measurement server started successfully.")
        self.is_connected = False

    def _send_command(self, command: str) -> str:
        """Internal method: send a command to the server and read one line of response"""
        if self.process.poll() is not None:
            raise ConnectionError("Server process has terminated unexpectedly.")

        self.process.stdin.write(command + '\n')
        self.process.stdin.flush()
        response = self.process.stdout.readline().strip()
        return response

    def connect(self):
        """Building a connection to the colorimeter"""
        if self.is_connected:
            print("Already connected.")
            return

        response = self._send_command('CONNECT')
        if response.startswith('SUCCESS'):
            self.is_connected = True
            print(f"Successfully connected: {response}")
        else:
            raise ConnectionError(f"Connection failed: {response}")

    def measure(self) -> Tuple[float, float, float]:
        """Performs a measurement and returns a tuple of (Y, x, y)."""
        if not self.is_connected:
            raise ConnectionError("Not connected. Call connect() first.")

        response = self._send_command('MEASURE')
        parts = response.split(',')

        if parts[0] == 'SUCCESS':
            Y = float(parts[1])
            x = float(parts[2])
            y = float(parts[3])
            return Y, x, y
        else:
            raise RuntimeError(f"Measurement failed: {response}")

    def set_integration_time(self, time_val):
        """
        Sets the integration time.
        Args:
            time_val: number (in seconds) or string 'auto'.
        """
        if not self.is_connected:
            raise ConnectionError("Not connected. Call connect() first.")

        if isinstance(time_val, (int, float)) and time_val > 0:
            command = f"INTEG {time_val}"
        elif isinstance(time_val, str) and time_val.lower() == 'auto':
            command = "INTEG AUTO"
        else:
            raise ValueError("Invalid input. Time must be a positive number or the string 'auto'.")

        response = self._send_command(command)
        if not response.startswith('SUCCESS'):
            raise RuntimeError(f"Failed to set integration time: {response}")
        else:
            print("Integration time set successfully.")

    def close(self):
        """Safely terminates the server process."""
        print("Shutting down measurement server...")
        if self.process.poll() is None:
            try:
                self._send_command('EXIT')
                self.process.wait(timeout=5)
            except (subprocess.TimeoutExpired, BrokenPipeError):
                self.process.kill()
        print("Server shut down.")

    def __enter__(self):
        """Methods to support 'with' syntax"""
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        """Automatically call close at the end of 'with' syntax"""
        self.close()

back to the menu

from cs150 import CS150
import time

print("--- Example: Basic and Safe Measurement with the cs150.py class ---")

try:
    # Using the 'with' syntax is safe because the server will
    # automatically terminate if an error occurs
    with CS150() as photometer:
        # 1. Establishing a connection
        photometer.connect()

        # 2. Setting the integration time as 0.5 sec
        photometer.set_integration_time(0.5)

        # 3. Measurement
        Y, x, y = photometer.measure()
        print(f"\nMeasurement Result (0.5s):")
        print(f"  Luminance (Y) = {Y:.4f} cd/m^2")
        print(f"  CIE 1931 (x, y) = ({x:.4f}, {y:.4f})\n")

        # 4. Setting the integration time as "auto"
        photometer.set_integration_time('auto')

        # 5. Re-measurment
        Y_auto, x_auto, y_auto = photometer.measure()
        print(f"Measurement Result (Auto):")
        print(f"  Luminance (Y) = {Y_auto:.4f} cd/m^2")
        print(f"  CIE 1931 (x, y) = ({x_auto:.4f}, {y_auto:.4f})")

except (FileNotFoundError, ConnectionError, RuntimeError, ValueError) as e:
    print(f"\nAn error occurred: {e}")

back to the menu

  • "Not connected" / connect errors Please verify USB driver and cables; follow the LC‑MISDK Reference Manual for setup steps (included in the SDK ZIP).
  • Windows 11 The LC‑MISDK page lists Windows 11 Pro among supported OS.
  • Bitness mismatch Build x64 for CS-150/160 with LC‑MISDK. For CS‑200, the official page notes the USB driver is 32‑bit only; build x86 apps.
  • Decimal commas The server always outputs dot decimals; MATLAB’s str2double will parse these regardless of the OS locale.

back to the menu

  • Continuous measurement: Add MEASURE_CONT/STOP commands and stream results.
  • Other color spaces: LC‑MISDK exposes alternate output types (e.g., u′v′, XYZ); use the appropriate data class with ReadLatestData(...). (See Reference Manual in the SDK ZIP.)
  • CS-200 backend: Implement a separate build target that calls the CS‑200 DLL APIs from the official driver package (32‑bit).

back to the menu

  • LC-MISDK (official download & docs): system requirements, supported instruments (CS-150/160, LS-150/160), 32/64-bit app support. LC-MISDK.
  • CS-200 USB Driver (official page): 32‑bit driver; DLL reference & communication spec for writing your own software. cs-200
  • CS-150 product page (specs/overview): Konica Minolta Sensing Americas. cs-150

back to the menu


cs150server -- MATLAB/Python–CS150 Bridge -- Control Konica-Minolta CS-150/160 from MATLAB/Python seamlessly via a tiny C# command server.

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

* Redistributions of source code must retain the above copyright  
  notice, this list of conditions and the following disclaimer.  
* Redistributions in binary form must reproduce the above copyright  
  notice, this list of conditions and the following disclaimer in  
  the documentation and/or other materials provided with the distribution  

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

The views and conclusions contained in the software and documentation are those of the authors and should not be interpreted as representing official policies, either expressed or implied, of the FreeBSD Project.

Konica Minolta SDKs/drivers/manuals are not included and are governed by their respective licenses/EULAs.

back to the menu

About

cs150server: MATLAB/Python-CS150 Bridge -- Control Konica-Minolta CS-150/160 from MATLAB/Python seamlessly via a tiny C# command server.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published