"Unknown", -1 => "Disabled", 0 => "Unlocked", 1 => "Locked" ); public static function toInt($name) { return array_search($name, self::$enum); } public static function str($int) { return self::$enum[$int]; } }; class PsuCpx400 { private $__componentName = "PsuCpx400"; private $__requestDelayUs = 80000; // We need some delay after every request because else the PSU will choke private $name = "psu_cpx400"; private $debug = false; private $__fd = null; /* Hardware capability */ private $__capabilities = array( "channels" => 2, "v_min" => 0.00, "v_max" => 60.00, "i_min" => 0.01, "i_max" => 20.00 ); private $__info = array ( "host" => "", "port" => "", "lock" => PsuCpx400LockStatus::Unknown, "name" => "", "model" => "", "serial" => "", "version" => "" ); private $__channelsInfo = array ( array( "id" => 1, // Channel ID "enabled" => false, // Output enabled "v" => 0.0, // Voltage "v_inc" => 0.0, // Voltage increment step "v_umin" => 0.0, // Voltage min (user limit) "v_umax" => 0.0, // Current max (user limit) "i" => 0.0, // Current "i_inc" => 0.0, // Current increment step "i_umin" => 0.0, // Current min (user limit) "i_umax" => 0.0, // Current max (user limit) ), array( "id" => 2, // Channel ID "enabled" => false, // Output enabled "v" => 0.0, // Voltage "v_inc" => 0.0, // Voltage increment step "v_umin" => 0.0, // Voltage min (user limit) "v_umax" => 0.0, // Voltage max (user limit) "i" => 0.0, // Current "i_max" => 0.0, // Current max "i_inc" => 0.0, // Current increment step "i_umin" => 0.0, // Current min (user limit) "i_umax" => 0.0, // Current max (user limit) ) ); private function debug($msg) { if ($this->debug) { $msg = str_replace("\r", '\r', $msg); $msg = str_replace("\n", '\n', $msg); DiTestMsg::dbg(trim($msg), $this->__componentName); } } private function err($msg) { DiTestMsg::err(trim($msg), $this->__componentName); } private function warn($msg) { DiTestMsg::warn(trim($msg), $this->__componentName); } private function fail($msg) { DiTestMsg::fail(trim($msg), $this->__componentName); } private function pass($msg) { DiTestMsg::pass(trim($msg), $this->__componentName); } function __construct($host = "psu", $port = 9221, $debug = false) { $this->debug = $debug; $this->__fd = fsockopen($host, $port, $errno, $errstr, 30); if ($this->__fd === FALSE) { $this->fail("$errstr ($errno)"); } else { $this->setInfo("host", $host); $this->setInfo("port", $port); $this->requestInfo(); $this->requestLockStatus(); if ($this->getInfo("lock") != PsuCpx400LockStatus::Unlocked) { $this->warn("PSU not unlocked, ignoring..."); } $this->outputsOff(); $this->reset(); $this->resetLimits(); } } function __destruct() { if ($this->__fd) { $this->resetLimits(); $this->outputsOff(); $this->reset(); fclose($this->__fd); $this->__fd = null; } } function setInfo($key, $value) { $this->__info[$key] = $value; } function getInfo($key) { return $this->__info[$key]; } function getCapability($id) { return $this->__capabilities[$id]; } function setChannelInfo($channel, $key, $value) { $channel = (int)$channel - 1; $this->__channelsInfo[$channel][$key] = $value; $this->debug("[setChannelInfo] channel: $channel, \"$key\" => \"$value\""); } function getChannelInfo($channel, $key) { $channel = (int)$channel - 1; $value = $this->__channelsInfo[(int)$channel][$key]; if ($key == "enabled") { if ($value == true) $value = "ON"; else $value = "OFF"; } $this->debug("[getChannelInfo] channel: $channel, \"$key\" => \"$value\""); return $value; } private function reply() { if ($this->__fd) { $reply = fgets($this->__fd); $this->debug("[reply] \"$reply\""); return $reply; } return ""; } private function request($cmd) { if ($this->__fd) { $cmd .= "\n"; $this->debug("[request] cmd: \"$cmd\""); fwrite($this->__fd, "$cmd"); } } private function requestInfo() { $this->request("*IDN?"); $r = $this->reply(); if (!empty($r)) { $info = explode(",", $r); if (count($info) == 4) { $this->setInfo("name", trim($info[0])); $this->setInfo("model", trim($info[1])); $this->setInfo("serial", trim($info[2])); $this->setInfo("version", trim($info[3])); } else { // TODO something unexpected returned } } } private function requestLockStatus() { $this->request("IFLOCK?"); $r = $this->reply(); if (!empty($r)) { $this->setInfo("lock", (int)trim($r)); } else { $this->setInfo("lock", PsuCpx400LockStatus::Unknown); } } private function isValidChannel($channel) { if ($channel > 0 && $channel <= 2) return true; $this->fail("Invalid channel $channel selected"); return false; } /** * Verify if requested channel voltage is within user and hardware limits */ private function isValidVoltage($channel, $voltage) { // Check channel number if (!($this->isValidChannel($channel))) return false; // User limit $min = $this->getChannelInfo($channel, "v_umin"); $max = $this->getChannelInfo($channel, "v_umax"); if ($voltage >= $min && $voltage <= $max) { $this->debug("valid voltage for channel: $channel, $voltage"); return true; } /* // Hardware limit $min = $this->getCapability('v_min'); $max = $this->getCapability('v_max'); if($voltage >= $min && $voltage <= $max) { $this->debug("invalid voltage for channel: $channel, $voltage"); return true; } */ $this->err("Voltage requested is invalid: $voltage >= $min (min), <= $max (max)"); return false; } public function lock() { $ret = false; $this->request("IFLOCK"); $r = $this->reply(); $this->debug("[lock] " . $r); if (!empty($r)) { (int)$r; if ($r == 1) { $ret = true; $this->setInfo("lock", PsuCpx400LockStatus::Locked); } } return $ret; } public function unlock() { $ret = false; $this->request("IFUNLOCK"); $r = $this->reply(); $this->debug("[unlock] " . $r); if (!empty($r)) { (int)$r; if ($r == 0) { $ret = true; $this->setInfo("lock", PsuCpx400LockStatus::Unlocked); } } return $ret; } public function isLocked() { $this->requestLockStatus(); $lock = $this->getInfo("lock"); if ($lock == PsuCpx400LockStatus::Unlocked) return false; return true; } public function local() { $this->request("LOCAL"); return true; } /* @return true when OK, false otherwise */ public function setVoltage($channel, $value, $retries = 5, $timeout_ms = 100) { $channel = (int)$channel; $value = (float)$value; if (!($this->isValidVoltage($channel, $value))) return false; $v = 0.00; $this->request("V".$channel." $value"); usleep($this->__requestDelayUs); for ($i = 0; $i < $retries; $i++) { $v = $this->getVoltage($channel); if ($v == $value) { $this->debug("[setVoltage] Requested voltage correctly set: \"$v\""); return true; } $this->debug("[setVoltage] Verify try $i, volt: \"$v\""); usleep(1000 * $timeout_ms); } $this->fail("[setVoltage] Requested voltage not correctly set to \"$value\": \"$v\""); return false; } /* Set current for all outputs */ public function setVoltageAll($value) { for ($i = 1; $i < 3; $i++) { $this->setVoltage($i, $value); } } public function getVoltage($channel) { (int)$channel = $channel; // TODO check channel $this->request("V$channel?"); $v = $this->reply(); if (!empty($v)) { $v = explode(" ", $v); $v = (float)$v[1]; $this->debug("[getVoltage] (channel: " . $channel . "): " . $v); $this->setChannelInfo($channel, "v", $v); return $v; } $this->fail("[getVoltage] Unexpected reply \"$v\""); return 0; } public function incVoltage($channel, $verify = false) { $channel = (int)$channel; if (!($this->isValidChannel($channel))) return; // TODO check PSU voltage after increment... if (!$verify) $this->request("INCV".$channel); else $this->request("INCV".$channel."V"); usleep($this->__requestDelayUs); $this->getVoltage($channel); } public function getIncVoltage($channel) { (int)$channel = $channel; // TODO check channel $this->request("DELTAV$channel?"); $v = $this->reply(); if (!empty($v)) { $v = explode(" ", $v); $v = (float)$v[1]; $this->debug("[getIncVoltage] (channel: " . $channel . "):" . $v); $this->setChannelInfo($channel, "v_inc", $v); return $v; } $this->fail("[getIncVoltage] Unexpected reply \"$v\""); return 0; } public function setIncVoltage($channel, $step) { $channel = (int)$channel; $step = (float)$step; if ($step == 0.00) { $this->warn("[setIncVoltage] Step of 0.00V is invalid, setting to minimal 0.01V"); $step = 0.01; } $this->request("DELTAV".$channel." $step"); $this->getIncVoltage($channel); } public function setMinVoltage($channel, $value) { $this->setChannelInfo($channel, "v_umin", $value); } public function setMaxVoltage($channel, $value) { $this->setChannelInfo($channel, "v_umax", $value); } public function getMinVoltage($channel) { return $this->getChannelInfo($channel, "v_umin"); } public function getMaxVoltage($channel) { return $this->getChannelInfo($channel, "v_umax"); } public function setCurrent($channel, $value) { $channel = (int)$channel; $value = (float)$value; $this->request("I$channel $value"); $this->getCurrent($channel); return true; } /* Set current for all outputs */ public function setCurrentAll($value) { for ($i = 1; $i < 3; $i++) { $this->setCurrent($i, $value); } } public function getCurrent($channel) { (int)$channel = $channel; $this->request("I$channel?"); $c = $this->reply(); $this->debug("[getCurrent] reply: " . $c); if (!empty($c)) { $c = explode(" ", $c); $c = (float)$c[1]; $this->debug("[getCurrent] (channel: " . $channel . "):" . $c); $this->setChannelInfo($channel, "i", $c); return $c; } return 0; } public function incCurrent($channel) { $channel = (int)$channel; $this->request("INCI".$channel); $this->getCurrent($channel); } public function getIncCurrent($channel) { (int)$channel = $channel; $this->request("DELTAI$channel?"); $v = $this->reply(); if (!empty($v)) { $v = explode(" ", $v); $v = (float)$v[1]; $this->debug("[getIncCurrent] (channel: " . $channel . "):" . $v); $this->setChannelInfo($channel, "i_inc", $v); return $v; } return 0; } public function setIncCurrent($channel, $step) { $channel = (int)$channel; $step = (float)$step; if ($step == 0.00) { $this->warn("[setIncCurrent] Step of 0.00A is invalid, setting to minimal value of 0.01A"); $step = 0.01; } $this->request("DELTAI".$channel." $step"); $this->getIncCurrent($channel); } public function setMinCurrent($channel, $value) { $this->setChannelInfo($channel, "i_umin", $value); } public function setMaxCurrent($channel, $value) { $this->setChannelInfo($channel, "i_umax", $value); } public function getMinCurrent($channel) { return $this->getChannelInfo($channel, "i_umin"); } public function getMaxCurrent($channel) { return $this->getChannelInfo($channel, "i_umax"); } public function outputOn($channel) { $channel = (int)$channel; $this->request("OP$channel 1"); } public function outputOff($channel) { $channel = (int)$channel; // TODO check channel id $this->request("OP$channel 0"); } /* Enable all outputs */ public function outputsOn() { $this->request("OPALL 1"); } /* Disable all outputs */ public function outputsOff() { $this->request("OPALL 0"); } /** Disable all outputs, set all voltage and current to zero */ public function reset() { $this->outputsOff(); $this->resetLimits(); for ($i = 1; $i < 3; $i++) { $this->setVoltage($i, 0.00); $this->setCurrent($i, 0.01); $this->setIncVoltage($i, 0.01); $this->setIncCurrent($i, 0.01); } } public function resetLimits() { for ($i = 1; $i < 3; $i++) { $this->setMinVoltage($i, $this->getCapability("v_min")); $this->setMaxVoltage($i, $this->getCapability("v_max")); $this->setMinCurrent($i, $this->getCapability("i_min")); $this->setMaxCurrent($i, $this->getCapability("i_max")); $this->setIncVoltage($i, 0.01); $this->setIncCurrent($i, 0.01); } } public function info() { $info = sprintf( "=== PSU CPX400 Status > Host: tcp://%s:%d > Serial: %s Version: %s > Lock: %s > Channel 1: [%s] %.02f V %.02f I (%0.2f Vstep, %0.2f IStep) > Channel 2: [%s] %.02f V %.02f I (%0.2f Vstep, %0.2f IStep) ===\n", $this->getInfo("host"), $this->getInfo("port"), $this->getInfo("serial"), $this->getInfo("version"), PsuCpx400LockStatus::str($this->getInfo("lock")), $this->getChannelInfo(1, "enabled"), $this->getChannelInfo(1, "v"), $this->getChannelInfo(1, "i"), $this->getChannelInfo(1, "v_inc"), $this->getChannelInfo(1, "i_inc"), $this->getChannelInfo(2, "enabled"), $this->getChannelInfo(2, "v"), $this->getChannelInfo(2, "i"), $this->getChannelInfo(2, "v_inc"), $this->getChannelInfo(2, "i_inc") ); return $info; } }