Ich setze einen Silex USB Device Server ein, um USB-Geräte an meinen virtuellen Windows 10 Host anzubinden. Das funktioniert in der Regel ganz gut, aber bei den virtuellen COM-Ports gibt es leider einige Kandidaten die sporadisch in Fehlerzustände geraten. Das äußert sich dann so, dass der Port im Gerätemanager mit X geflaggt ist und in IPS die entsprechende IO-Instanz sich nicht verbinden lässt („Pfad nicht gefunden“).
Abhilfe schafft dann, im Silex-USB-Tool auf der Maschine per GUI das Gerät einmal zu trennen und wieder zu verbinden.
Das hat mich auf Dauer aber genervt und zum Glück habe ich ein Kommandozeilen-Tool gefunden, mit dem sich das ganze automatisiert durchführen lässt:
http://www.silexamerica.com/uploads/common/sx-virtual-link-win-cui-120-sample.zip
Dazu habe ich ein Skript geschrieben, das die Behebung solcher Probleme automatisiert. Wenn eine der dort eingetragenen Serial Port Instanzen in einen Fehlerstatus gerät, wird automatisch folgendes unternommen:
-Port geschlossen
-Gerät getrennt
-Gerät verbunden
-Port geöffnet
Abschließend wird geprüft, ob die Instanz nun wieder funktioniert.
Man kann eine Mailer-Instanz angeben, die anschließend einen kurzen Bericht absendet.
Bleibt das Problem bestehen so wird es in 15 Minuten erneut versucht.
<?
// Setup instructions:
// - Download DSCon.exe from
// http://www.silexamerica.com/uploads/common/sx-virtual-link-win-cui-120-sample.zip
// - Unzip the files and copy them all to the SX Virtual Link program folder
// (Usually under C:\Program Files\silex technology).
// - Fill in the IP address of your USB host(s) in the table below
// - Execute this script once. A list of device names should be displayed.
// - Fill in the rest of the table (device name => serial port instance ID):
// - Execute this script again to check for configuration errors.
// - Probably should send $mailer_id to a SMTP mailer instance so you get
// notified whenever the script does something on its own.
$device_mappings = array(
'192.168.178.123' /* <-- IP Address of your USB Host goes here!*/ => array(
// below, replace with your own device ID => instance ID mappings!
'FTDI FT232R USB UART' => 0,
'ELV AG ELV FHZ 1300 PC' => 0,
'Arduino (www.arduino.cc) PID [0x0042]' => 0
)
);
$mailer_id = 0;
IPS_SetScriptTimer($_IPS['SELF'], 15 * 60);
// if the script was called by an instance status event, check only the specified
// instance
if($_IPS['SENDER'] == 'StatusEvent') {
$specific_inst_id = $_IPS['INSTANCE'];
} else {
$specific_inst_id = false;
}
$body_short = ''; // will contain a short report only listing PROBLEMS
$body_detailed = ''; // will contain a more detailed report listing ALL devices
if(IPS_SemaphoreEnter("Silex USB Host", 30 * 1000)) {
// try to solve problems if any are detected
$report = fix_problems_all($device_mappings, $specific_inst_id);
// create report
if($report !== false) {
foreach($report as $entry) {
$inst_name = @IPS_GetName($entry['instance_id']);
if($inst_name === false) {
$inst_name = '? (ID ' . $entry['instance_id'] . ')';
}
$cfg_json = @IPS_GetConfiguration($entry['instance_id']);
$port = '?';
if($cfg_json !== false) {
$cfg = json_decode($cfg_json, true);
$port = $cfg['Port'];
}
$ip = $entry['host_ip'];
$dev_full_name = $inst_name . ' (' . $port . ', ' .
$entry['device_name'] . ') @ ' . $ip;
$this_body_entry = $dev_full_name . "\r";
if($entry['problem_found']) {
$this_body_entry .= " Problem found! Status: " . $entry['status_before'] .
"\r Attempting to solve...\r";
foreach($entry['steps'] as $step) {
$this_body_entry .= " Measure \"" . $step['measure'] . "\" => ";
if($step['success']) {
$this_body_entry .= 'SUCCESS';
} else {
$this_body_entry .= 'FAILED';
}
$this_body_entry .= "\r";
}
if($entry['problem_solved']) {
$this_body_entry .= " Problem solved. Status: " . $entry['status_after'] .
"\r";
} else {
$this_body_entry .= " Problem unsolved! Status: " . $entry['status_after'] .
"\r";
}
} else if($entry['success']) {
$this_body_entry .= " Status: " . $entry['status_before'] . "\r";
} else {
IPS_LogMessage(IPS_GetName($_IPS['SELF']), 'Configuration error! Execute script for details.');
}
$this_body_entry .= " Result: " . $entry['result'] . "\r\r";
$body_detailed .= $this_body_entry;
if($entry['problem_found']) {
$body_short .= $this_body_entry;
}
}
}
IPS_SemaphoreLeave("Silex USB Host");
} else {
$body_detailed = "Timeout waiting for other instance to finish!";
}
// if executed in console, set up, display detailed report
if($_IPS['SENDER'] == 'Execute') {
if($body_detailed == "") $body_detailed = "No instances configured.\r\r";
echo $body_detailed;
// get event control's id and check whether a mapping for the supllied serial
// port instance(s) exists on it
$ec_id = IPS_GetInstanceListByModuleID('{ED573B53-8991-4866-B28C-CBE44C59A2DA}')[0];
$ec_cfg_json = IPS_GetConfiguration($ec_id);
$ec_cfg = json_decode($ec_cfg_json, true);
$status_events_list = json_decode($ec_cfg['StatusEvents'], true);
foreach($device_mappings as $ip => $instances) { // for all usb hosts
try{
$devices = get_device_list($ip);
} catch(Exception $e) {
echo $e->getMessage();
continue;
}
echo "//Device names of devices attached to Host " . $ip . ":\r" .
"\"" . $ip . "\" => array(\r";
foreach($devices as $dev_name => $dev_ids) {
echo " \"" . $dev_name . "\" => 0, /* <-- replace with instance ID! */\r";
}
echo ")\r";
$status_events_modified = false;
foreach($instances as $dev_name => $inst_id) { // for each device on this host
$has_status_event = false;
foreach($status_events_list as $status_event_entry) {
if($inst_id == $status_event_entry['DeviceID']) {
if($status_event_entry['ScriptID'] == $_IPS['SELF']) {
$has_status_event = true;
break;
}
}
}
if(!$has_status_event) { // if this instance is not yet in the list, append it
echo "Füge Status-Event für " . IPS_GetName($inst_id) . " hinzu!\r";
$status_events_list[] = array(
'DeviceID' => $inst_id,
'ScriptID' => $_IPS['SELF']
);
$status_events_modified = true; // flag as modified so that new list gets saved
}
}
// if we modified the status events list, save it to the event control's config
if($status_events_modified) {
$status_events_json = json_encode($status_events_list);
IPS_SetProperty($ec_id, 'StatusEvents', $status_events_json);
IPS_ApplyChanges($ec_id);
}
}
} else if($body_short != '') { // if executed by event control or timer
// only send mail when mailer is specified and there is a problem to be reported
if($mailer_id != 0) {
SMTP_SendMail(
$mailer_id,
IPS_GetName($_IPS['SELF']),
$body_short
);
}
IPS_LogMessage(IPS_GetName($_IPS['SELF']), $body_short);
}
// attempts to fix problems for serial port instances connected on all supplied hosts
function fix_problems_all($device_mappings, $specific_inst_id = false) {
$result = array();
foreach($device_mappings as $ip => $instances) {
$result = fix_problems_for_server($ip, $instances, $specific_inst_id, $result);
}
return $result;
}
// attempts to fix problems for serial port instances connected to specified host
function fix_problems_for_server($ip, $instances, $specific_inst_id = false, $result = false) {
try {
$devices = get_device_list($ip);
} catch(Exception $e) {
return false;
}
if($result === false) $result = array();
foreach($instances as $dev_name => $inst_id) {
$this_result = array(
'host_ip' => $ip,
'device_name' => $dev_name,
'instance_id' => $inst_id
);
if($inst_id == 0) continue;
// if a specific instance id has been supplied, only try to fix problems for
// the specified instance!
if($specific_inst_id !== false) {
if($specific_inst_id != $inst_id) {
continue;
}
}
if(!IPS_InstanceExists($inst_id)) {
$this_result['result'] = 'instance not found';
$this_result['problem_found'] = false;
$this_result['problem_solved'] = false;
$this_result['success'] = false;
$result[] = $this_result;
continue;
}
// check whether the device name specified as key in the instances array
// exists within the USB host's device list.
if(!array_key_exists($dev_name, $devices)) {
$this_result['result'] = 'device name not found';
$this_result['problem_found'] = false;
$this_result['problem_solved'] = false;
$this_result['success'] = false;
$result[] = $this_result;
continue;
}
$inst = IPS_GetInstance($inst_id);
$this_result['status_before'] = $inst['InstanceStatus'];
// if instance does not have an error status
if($inst['InstanceStatus'] < 200) {
$this_result['result'] = 'status ok';
$this_result['problem_found'] = false;
$this_result['problem_solved'] = false;
$this_result['success'] = true;
$result[] = $this_result;
continue;
}
$this_result['problem_found'] = true;
$res_steps = array();
// close port
$res_steps[] = array(
'measure' => 'close port',
'success' => (
@IPS_SetProperty($inst_id, 'Open', false) &&
@IPS_ApplyChanges($inst_id)
)
);
$dev_ids = $devices[$dev_name];
foreach($dev_ids as $dev_id) {
// disconnect virtual usb device
$res_steps[] = array(
'measure' => 'disconnect device',
'device_id' => $dev_id,
'success' => disconnect($ip, $dev_id)
);
// pause
IPS_Sleep(1 * 1000);
// connect virtual usb device
$res_steps[] = array(
'measure' => 'connect device',
'device_id' => $dev_id,
'success' => connect($ip, $dev_id)
);
// pause
IPS_Sleep(3 * 1000);
} // foreach
// open port
$res_steps[] = array(
'measure' => 'open port',
'success' => (
@IPS_SetProperty($inst_id, 'Open', true) &&
@IPS_ApplyChanges($inst_id)
)
);
// pause
IPS_Sleep(1 * 1000);
$inst = IPS_GetInstance($inst_id);
$this_result['status_after'] = $inst['InstanceStatus'];
$this_result['steps'] = $res_steps;
// if instance does not have an error status
if($inst['InstanceStatus'] >= 200) {
$this_result['problem_solved'] = false;
$this_result['success'] = false;
$this_result['result'] = 'problem unsolved';
} else {
$this_result['problem_solved'] = true;
$this_result['success'] = true;
$this_result['result'] = 'problem solved';
}
$result[] = $this_result;
} // foreach
return $result;
} // fix_problems
// Connect device attached to USB Host to local system, return true if successful
function connect($ip, $dev_id) {
return dscon($ip, "C", $dev_id);
} // connect
// Disconnect device attached to USB Host from local system, return true on success
function disconnect($ip, $dev_id) {
return dscon($ip, "D", $dev_id);
} // disconnect
// Returns an array of device names => array(device ids). The device ids are
// strings used to reference a device in subsequent calls to dscon().
function get_device_list($ip) {
static $device_list;
if(!isset($device_list)) $device_list = dscon($ip, "L");
return $device_list;
} // get_device_list
// Wrapper function that calls the DSCon.exe utility in order to enumerate
// connected devices, connect or disconnect them from the local system.
function dscon($ip, $cmd, $dev_id = false) {
$shell_script_path = IPS_GetKernelDirEx() . "DSCon.cmd";
$outfile_path = IPS_GetKernelDirEx() . "DSCon_out.txt";
// create wrapper shell script if it doesn't already exist.
if(!file_exists($shell_script_path)) {
$content = "@echo off
" .
"if not exist \"%ProgramFiles%\\silex technology\\SX Virtual Link\\DSCon.exe\" goto error_dscon_missing
" .
"cd \"%ProgramFiles%\\silex technology\\SX Virtual Link\"
" .
"\"%ProgramFiles%\\silex technology\\SX Virtual Link\\DSCon.exe\" %*>" .
$outfile_path . " 2>&1
" .
"goto end
" .
":error_dscon_missing
" .
"echo DSCon.exe is missing!>" . $outfile_path . "
" .
":end
";
file_put_contents($shell_script_path, $content);
}
// remove output file so that we don't accidentally get old results if the
// script call fails
if(file_exists($outfile_path)) unlink($outfile_path);
// call the wrapper shell script
IPS_ExecuteEx(
$shell_script_path,
"/" . $cmd . ' /I' . $ip . ($dev_id !== false ? ' /P'.$dev_id : ''),
false,
true, // wait for it
-1); // run in user session
// get output
if(file_exists($outfile_path)) {
$result = file_get_contents($outfile_path);
// message generated by shell script if DSCon.exe is missing
if($result == "DSCon.exe is missing!
") {
throw new Exception('DSCon.exe is missing. Must be in the SX Virtual Link folder!');
}
} else { // no output file means something went wrong with the shell script
throw new Exception('Error launching shell script "' . $shell_script_path . '"!');
}
// interpret the output from DSCon.exe depending on which command was used
switch($cmd) {
case 'L':
// return an array of $dev_name => array($dev_id, ...)
if($result == "This device server doesn't exist on the network.
") {
throw new Exception('Unable to connect to USB Host.');
}
$lines = explode("
", $result);
$result = array();
foreach($lines as $line) {
if($line != '') {
$fields = explode(" ", $line);
$dev_id = trim($fields[0], " []");
$dev_name = $fields[1];
if(array_key_exists($dev_name, $result)) {
$result[$dev_name][] = $dev_id;
} else {
$result[$dev_name] = array($dev_id);
}
}
}
break;
case 'D':
// return true if disconnecting succeeded
$result = ($result == "Disconnection succeeded.
");
break;
case 'C':
// return true if connecting succeeded
$result = (
($result == "Connection succeeded.
") ||
($result == "One or more devices are already connected.
Please check the input command.
")
);
break;
}
return $result;
} // dscon
?>