First step of moving to command list.

This commit is contained in:
Florian Pose
2008-07-21 22:20:23 +00:00
parent 874bfee9da
commit e2bc889ac4
7 changed files with 784 additions and 388 deletions
+3 -1
View File
@@ -34,7 +34,9 @@
bin_PROGRAMS = ethercat bin_PROGRAMS = ethercat
ethercat_SOURCES = \ ethercat_SOURCES = \
Master.cpp Master.h \ MasterDevice.cpp MasterDevice.h \
cmd_alias.cpp \
cmd_config.cpp \
main.cpp main.cpp
ethercat_CXXFLAGS = -I../master -Wall ethercat_CXXFLAGS = -I../master -Wall
File diff suppressed because it is too large Load Diff
+21 -48
View File
@@ -18,72 +18,32 @@ using namespace std;
/****************************************************************************/ /****************************************************************************/
class MasterException: class MasterDeviceException:
public runtime_error public runtime_error
{ {
public: public:
/** Constructor with std::string parameter. */ /** Constructor with std::string parameter. */
MasterException( MasterDeviceException(
const string &s /**< Message. */ const string &s /**< Message. */
): runtime_error(s) {} ): runtime_error(s) {}
/** Constructor with const char pointer parameter. */
MasterException(
const char *s /**< Message. */
): runtime_error(s) {}
}; };
/****************************************************************************/ /****************************************************************************/
class Master class MasterDevice
{ {
public: public:
Master(); MasterDevice();
~Master(); ~MasterDevice();
void setIndex(unsigned int); void setIndex(unsigned int);
enum Verbosity {
Quiet,
Normal,
Verbose
};
void setVerbosity(Verbosity);
void writeAlias(int, bool, const vector<string> &);
void showConfigs();
void outputData(int);
void setDebug(const vector<string> &);
void showDomains(int);
void showMaster();
void listPdos(int);
void listSdos(int);
void sdoDownload(int, const string &, const vector<string> &);
void sdoUpload(int, const string &, const vector<string> &);
void showSlaves(int);
void siiRead(int);
void siiWrite(int, bool, const vector<string> &);
void requestStates(int, const vector<string> &);
void generateXml(int);
protected:
enum Permissions {Read, ReadWrite}; enum Permissions {Read, ReadWrite};
void open(Permissions); void open(Permissions);
void close(); void close();
void writeSlaveAlias(uint16_t, uint16_t);
typedef list<ec_ioctl_config_t> ConfigList;
void showDetailedConfigs(const ConfigList &);
void listConfigs(const ConfigList &);
void outputDomainData(unsigned int);
enum {BreakAfterBytes = 16};
void showDomain(unsigned int);
void listSlavePdos(uint16_t, bool = false);
void listSlaveSdos(uint16_t, bool = false);
void listSlaves(int);
void showSlave(uint16_t);
void generateSlaveXml(uint16_t);
unsigned int slaveCount(); unsigned int slaveCount();
void getMaster(ec_ioctl_master_t *); void getMaster(ec_ioctl_master_t *);
void getConfig(ec_ioctl_config_t *, unsigned int); void getConfig(ec_ioctl_config_t *, unsigned int);
void getConfigPdo(ec_ioctl_config_pdo_t *, unsigned int, uint8_t, void getConfigPdo(ec_ioctl_config_pdo_t *, unsigned int, uint8_t,
@@ -102,17 +62,30 @@ class Master
uint8_t, uint8_t); uint8_t, uint8_t);
void getSdo(ec_ioctl_slave_sdo_t *, uint16_t, uint16_t); void getSdo(ec_ioctl_slave_sdo_t *, uint16_t, uint16_t);
void getSdoEntry(ec_ioctl_slave_sdo_entry_t *, uint16_t, int, uint8_t); void getSdoEntry(ec_ioctl_slave_sdo_entry_t *, uint16_t, int, uint8_t);
void readSii(ec_ioctl_slave_sii_t *);
void writeSii(ec_ioctl_slave_sii_t *);
protected:
#if 0
void outputDomainData(unsigned int);
enum {BreakAfterBytes = 16};
void showDomain(unsigned int);
void listSlavePdos(uint16_t, bool = false);
void listSlaveSdos(uint16_t, bool = false);
void listSlaves(int);
void showSlave(uint16_t);
void generateSlaveXml(uint16_t);
void requestState(uint16_t, uint8_t); void requestState(uint16_t, uint8_t);
static string slaveState(uint8_t); static string slaveState(uint8_t);
static void printRawData(const uint8_t *, unsigned int); static void printRawData(const uint8_t *, unsigned int);
static uint8_t calcSiiCrc(const uint8_t *, unsigned int); static uint8_t calcSiiCrc(const uint8_t *, unsigned int);
#endif
private: private:
enum {DefaultBufferSize = 1024}; //enum {DefaultBufferSize = 1024};
unsigned int index; unsigned int index;
Verbosity verbosity;
int fd; int fd;
}; };
+181
View File
@@ -0,0 +1,181 @@
/*****************************************************************************
*
* $Id$
*
****************************************************************************/
#include <iostream>
#include <iomanip>
#include <sstream>
using namespace std;
#include "globals.h"
/*****************************************************************************/
const char *help_alias =
"[OPTIONS] <ALIAS>\n"
"\n"
"Write the secondary slave address (alias) for either\n"
"one or for multiple slaves.\n"
"\n"
"Arguments:\n"
" ALIAS must be a 16 bit unsigned integer, specified\n"
" either in decimal (no prefix), octal (prefix '0')\n"
" or hexadecimal (prefix '0x').\n"
"\n"
"Command-specific options:\n"
" -s <SLAVE> Write the alias of the slave with the given\n"
" ring position. If this option is not\n"
" specified, the alias of all slaves is set.\n"
" The --force option is required in this\n"
" case.\n";
/*****************************************************************************/
void writeSlaveAlias(uint16_t, uint16_t);
/*****************************************************************************/
/** Writes the Secondary slave address (alias) to the slave's SII.
*/
void command_alias(void)
{
uint16_t alias;
stringstream err, strAlias;
int number;
unsigned int numSlaves, i;
if (commandArgs.size() != 1) {
err << "'" << command << "' takes exactly one argument!";
throw InvalidUsageException(err);
}
strAlias << commandArgs[0];
strAlias
>> resetiosflags(ios::basefield) // guess base from prefix
>> number;
if (strAlias.fail() || number < 0x0000 || number > 0xffff) {
err << "Invalid alias '" << commandArgs[0] << "'!";
throw InvalidUsageException(err);
}
alias = number;
if (slavePosition == -1) {
if (!force) {
err << "This will write the alias addresses of all slaves to "
<< alias << "! Please specify --force to proceed.";
throw ExecutionFailureException(err);
}
masterDev.open(MasterDevice::ReadWrite);
numSlaves = masterDev.slaveCount();
for (i = 0; i < numSlaves; i++) {
writeSlaveAlias(i, alias);
}
} else {
masterDev.open(MasterDevice::ReadWrite);
writeSlaveAlias(slavePosition, alias);
}
}
/*****************************************************************************/
/** Calculates the SII checksum field.
*
* The checksum is generated with the polynom x^8+x^2+x+1 (0x07) and an
* initial value of 0xff (see IEC 61158-6-12 ch. 5.4).
*
* The below code was originally generated with PYCRC
* http://www.tty1.net/pycrc
*
* ./pycrc.py --width=8 --poly=0x07 --reflect-in=0 --xor-in=0xff
* --reflect-out=0 --xor-out=0 --generate c --algorithm=bit-by-bit
*
* \return CRC8
*/
uint8_t calcSiiCrc(
const uint8_t *data, /**< pointer to data */
size_t length /**< number of bytes in \a data */
)
{
unsigned int i;
uint8_t bit, byte, crc = 0x48;
while (length--) {
byte = *data++;
for (i = 0; i < 8; i++) {
bit = crc & 0x80;
crc = (crc << 1) | ((byte >> (7 - i)) & 0x01);
if (bit) crc ^= 0x07;
}
}
for (i = 0; i < 8; i++) {
bit = crc & 0x80;
crc <<= 1;
if (bit) crc ^= 0x07;
}
return crc;
}
/*****************************************************************************/
/** Writes the Secondary slave address (alias) to the slave's SII.
*/
void writeSlaveAlias(
uint16_t slavePosition,
uint16_t alias
)
{
ec_ioctl_slave_sii_t data;
ec_ioctl_slave_t slave;
stringstream err;
uint8_t crc;
masterDev.getSlave(&slave, slavePosition);
if (slave.sii_nwords < 8) {
err << "Current SII contents are too small to set an alias "
<< "(" << slave.sii_nwords << " words)!";
throw ExecutionFailureException(err);
}
// read first 8 SII words
data.slave_position = slavePosition;
data.offset = 0;
data.nwords = 8;
data.words = new uint16_t[data.nwords];
try {
masterDev.readSii(&data);
} catch (MasterDeviceException &e) {
delete [] data.words;
err << "Failed to read SII: " << e.what();
throw ExecutionFailureException(err);
}
// write new alias address in word 4
data.words[4] = cputole16(alias);
// calculate checksum over words 0 to 6
crc = calcSiiCrc((const uint8_t *) data.words, 14);
// write new checksum into first byte of word 7
*(uint8_t *) (data.words + 7) = crc;
// write first 8 words with new alias and checksum
try {
masterDev.writeSii(&data);
} catch (MasterDeviceException &e) {
delete [] data.words;
err << "Failed to read SII: " << e.what();
throw ExecutionFailureException(err);
}
delete [] data.words;
}
/*****************************************************************************/
+237
View File
@@ -0,0 +1,237 @@
/*****************************************************************************
*
* $Id$
*
****************************************************************************/
#include <list>
#include <iostream>
#include <iomanip>
#include <sstream>
using namespace std;
#include "globals.h"
/*****************************************************************************/
const char *help_config =
"[OPTIONS]\n"
"\n"
"\n"
"Command-specific options:\n";
/*****************************************************************************/
struct ConfigInfo {
string alias;
string pos;
string ident;
string att;
string op;
};
typedef list<ec_ioctl_config_t> ConfigList;
void showDetailedConfigs(const ConfigList &configList);
void listConfigs(const ConfigList &configList);
/*****************************************************************************/
bool operator<(const ec_ioctl_config_t &a, const ec_ioctl_config_t &b)
{
return a.alias < b.alias
|| (a.alias == b.alias && a.position < b.position);
}
/*****************************************************************************/
/** Lists the bus configuration.
*/
void command_config(void)
{
ec_ioctl_master_t master;
unsigned int i;
ec_ioctl_config_t config;
ConfigList configList;
masterDev.open(MasterDevice::Read);
masterDev.getMaster(&master);
for (i = 0; i < master.config_count; i++) {
masterDev.getConfig(&config, i);
configList.push_back(config);
}
configList.sort();
if (verbosity == Verbose) {
showDetailedConfigs(configList);
} else {
listConfigs(configList);
}
}
/*****************************************************************************/
/** Lists the complete bus configuration.
*/
void showDetailedConfigs(const ConfigList &configList)
{
ConfigList::const_iterator configIter;
unsigned int j, k, l;
ec_ioctl_config_pdo_t pdo;
ec_ioctl_config_pdo_entry_t entry;
ec_ioctl_config_sdo_t sdo;
for (configIter = configList.begin();
configIter != configList.end();
configIter++) {
cout << "Alias: "
<< dec << configIter->alias << endl
<< "Position: " << configIter->position << endl
<< "Vendor Id: 0x"
<< hex << setfill('0')
<< setw(8) << configIter->vendor_id << endl
<< "Product code: 0x"
<< setw(8) << configIter->product_code << endl
<< "Attached: " << (configIter->attached ? "yes" : "no") << endl
<< "Operational: " << (configIter->operational ? "yes" : "no") << endl;
for (j = 0; j < EC_MAX_SYNC_MANAGERS; j++) {
if (configIter->syncs[j].pdo_count) {
cout << "SM" << dec << j << " ("
<< (configIter->syncs[j].dir == EC_DIR_INPUT
? "Input" : "Output") << ")" << endl;
for (k = 0; k < configIter->syncs[j].pdo_count; k++) {
masterDev.getConfigPdo(&pdo, configIter->config_index, j, k);
cout << " Pdo 0x" << hex
<< setw(4) << pdo.index
<< " \"" << pdo.name << "\"" << endl;
for (l = 0; l < pdo.entry_count; l++) {
masterDev.getConfigPdoEntry(&entry,
configIter->config_index, j, k, l);
cout << " Pdo entry 0x" << hex
<< setw(4) << entry.index << ":"
<< setw(2) << (unsigned int) entry.subindex
<< ", " << dec << (unsigned int) entry.bit_length
<< " bit, \"" << entry.name << "\"" << endl;
}
}
}
}
cout << "Sdo configuration:" << endl;
if (configIter->sdo_count) {
for (j = 0; j < configIter->sdo_count; j++) {
masterDev.getConfigSdo(&sdo, configIter->config_index, j);
cout << " 0x"
<< hex << setfill('0')
<< setw(4) << sdo.index << ":"
<< setw(2) << (unsigned int) sdo.subindex
<< ", " << dec << sdo.size << " byte: " << hex;
switch (sdo.size) {
case 1:
cout << "0x" << setw(2)
<< (unsigned int) *(uint8_t *) &sdo.data;
break;
case 2:
cout << "0x" << setw(4)
<< le16tocpu(*(uint16_t *) &sdo.data);
break;
case 4:
cout << "0x" << setw(8)
<< le32tocpu(*(uint32_t *) &sdo.data);
break;
default:
cout << "???";
}
cout << endl;
}
} else {
cout << " None." << endl;
}
cout << endl;
}
}
/*****************************************************************************/
/** Lists the bus configuration.
*/
void listConfigs(const ConfigList &configList)
{
ConfigList::const_iterator configIter;
stringstream str;
ConfigInfo info;
typedef list<ConfigInfo> ConfigInfoList;
ConfigInfoList list;
ConfigInfoList::const_iterator iter;
unsigned int maxAliasWidth = 0, maxPosWidth = 0,
maxAttWidth = 0, maxOpWidth = 0;
for (configIter = configList.begin();
configIter != configList.end();
configIter++) {
str << dec << configIter->alias;
info.alias = str.str();
str.clear();
str.str("");
str << configIter->position;
info.pos = str.str();
str.clear();
str.str("");
str << hex << setfill('0')
<< "0x" << setw(8) << configIter->vendor_id
<< "/0x" << setw(8) << configIter->product_code;
info.ident = str.str();
str.clear();
str.str("");
str << (configIter->attached ? "attached" : "-");
info.att = str.str();
str.clear();
str.str("");
str << (configIter->operational ? "operational" : "-");
info.op = str.str();
str.clear();
str.str("");
list.push_back(info);
if (info.alias.length() > maxAliasWidth)
maxAliasWidth = info.alias.length();
if (info.pos.length() > maxPosWidth)
maxPosWidth = info.pos.length();
if (info.att.length() > maxAttWidth)
maxAttWidth = info.att.length();
if (info.op.length() > maxOpWidth)
maxOpWidth = info.op.length();
}
for (iter = list.begin(); iter != list.end(); iter++) {
cout << setfill(' ') << right
<< setw(maxAliasWidth) << iter->alias
<< ":" << left
<< setw(maxPosWidth) << iter->pos
<< " "
<< iter->ident
<< " "
<< setw(maxAttWidth) << iter->att << " "
<< setw(maxOpWidth) << iter->op << " "
<< endl;
}
}
/*****************************************************************************/
+89
View File
@@ -0,0 +1,89 @@
/*****************************************************************************
*
* $Id$
*
****************************************************************************/
#include <sys/types.h>
#include <string>
#include <sstream>
#include <vector>
using namespace std;
#include "MasterDevice.h"
/*****************************************************************************/
enum Verbosity {
Quiet,
Normal,
Verbose
};
extern string command;
extern int slavePosition;
extern int domainIndex;
extern vector<string> commandArgs;
extern Verbosity verbosity;
extern string dataTypeStr;
extern bool force;
extern MasterDevice masterDev;
/****************************************************************************/
class InvalidUsageException:
public runtime_error
{
public:
/** Constructor with std::string parameter. */
InvalidUsageException(
const stringstream &s /**< Message. */
): runtime_error(s.str()) {}
};
/****************************************************************************/
class ExecutionFailureException:
public runtime_error
{
public:
/** Constructor with std::string parameter. */
ExecutionFailureException(
const stringstream &s /**< Message. */
): runtime_error(s.str()) {}
};
/*****************************************************************************/
#define swap16(x) \
((uint16_t)( \
(((uint16_t)(x) & 0x00ffU) << 8) | \
(((uint16_t)(x) & 0xff00U) >> 8) ))
#define swap32(x) \
((uint32_t)( \
(((uint32_t)(x) & 0x000000ffUL) << 24) | \
(((uint32_t)(x) & 0x0000ff00UL) << 8) | \
(((uint32_t)(x) & 0x00ff0000UL) >> 8) | \
(((uint32_t)(x) & 0xff000000UL) >> 24) ))
#if __BYTE_ORDER == __LITTLE_ENDIAN
#define le16tocpu(x) x
#define le32tocpu(x) x
#define cputole16(x) x
#define cputole32(x) x
#elif __BYTE_ORDER == __BIG_ENDIAN
#define le16tocpu(x) swap16(x)
#define le32tocpu(x) swap32(x)
#define cputole16(x) swap16(x)
#define cputole32(x) swap32(x)
#endif
/****************************************************************************/
+133 -65
View File
@@ -12,29 +12,93 @@
#include <vector> #include <vector>
using namespace std; using namespace std;
#include "Master.h" #include "globals.h"
/*****************************************************************************/ /*****************************************************************************/
#define DEFAULT_MASTER 0 string binaryBaseName;
unsigned int masterIndex = 0;
static string cmdName; int slavePosition = -1;
static unsigned int masterIndex = DEFAULT_MASTER; int domainIndex = -1;
static int slavePosition = -1; string command;
static int domainIndex = -1;
static string command;
vector<string> commandArgs; vector<string> commandArgs;
static Master::Verbosity verbosity = Master::Normal; Verbosity verbosity = Normal;
string dataTypeStr; string dataTypeStr;
bool force = false; bool force = false;
bool helpRequested = false; bool helpRequested = false;
MasterDevice masterDev;
/*****************************************************************************/
struct Command {
void (*func)(void);
const char *helpString;
int execute(void) const;
void displayHelp(void) const;
};
struct CommandAlias {
const char *name;
const Command *command;
};
/*****************************************************************************/
#define COMMAND(name) \
void command_##name(void); \
extern const char *help_##name; \
const Command cmd_##name = {command_##name, help_##name};
COMMAND(alias);
COMMAND(config);
const CommandAlias commandAliases[] = {
{"alias", &cmd_alias},
{"config", &cmd_config},
{"conf", &cmd_config},
{"cf", &cmd_config},
};
#if 0
} else if (command == "data") {
master.outputData(domainIndex);
} else if (command == "debug") {
master.setDebug(commandArgs);
} else if (command == "domain") {
master.showDomains(domainIndex);
} else if (command == "master") {
master.showMaster();
} else if (command == "pdos") {
master.listPdos(slavePosition);
} else if (command == "sdos") {
master.listSdos(slavePosition);
} else if (command == "sdo_download" || command == "sd") {
master.sdoDownload(slavePosition, dataTypeStr, commandArgs);
} else if (command == "sdo_upload" || command == "su") {
master.sdoUpload(slavePosition, dataTypeStr, commandArgs);
} else if (command == "slave" || command == "slaves"
|| command == "list" || command == "ls") {
master.showSlaves(slavePosition);
} else if (command == "sii_read" || command == "sr") {
master.siiRead(slavePosition);
} else if (command == "sii_write" || command == "sw") {
master.siiWrite(slavePosition, force, commandArgs);
} else if (command == "state") {
master.requestStates(slavePosition, commandArgs);
} else if (command == "xml") {
master.generateXml(slavePosition);
#endif
/*****************************************************************************/ /*****************************************************************************/
void printUsage() void printUsage()
{ {
cerr cerr
<< "Usage: " << cmdName << " <COMMAND> [OPTIONS]" << endl << "Usage: " << binaryBaseName << " <COMMAND> [OPTIONS]" << endl
<< "Commands:" << endl << "Commands:" << endl
<< " alias Write alias addresses." << endl << " alias Write alias addresses." << endl
<< " config Show bus configuration." << endl << " config Show bus configuration." << endl
@@ -52,8 +116,8 @@ void printUsage()
<< " state Request slave states." << endl << " state Request slave states." << endl
<< " xml Generate slave information xmls." << endl << " xml Generate slave information xmls." << endl
<< "Global options:" << endl << "Global options:" << endl
<< " --master -m <master> Index of the master to use. Default: " << " --master -m <master> Index of the master to use. Default: 0"
<< DEFAULT_MASTER << endl << endl
<< " --slave -s <index> Positive numerical ring position," << " --slave -s <index> Positive numerical ring position,"
<< endl << endl
<< " or 'all' for all slaves (default)." << " or 'all' for all slaves (default)."
@@ -67,8 +131,8 @@ void printUsage()
<< " --quiet -q Output less information." << endl << " --quiet -q Output less information." << endl
<< " --verbose -v Output more information." << endl << " --verbose -v Output more information." << endl
<< " --help -h Show this help." << endl << " --help -h Show this help." << endl
<< "Call '" << cmdName << " <COMMAND> --help' for command-specific " << "Call '" << binaryBaseName
<< "help." << endl << " <COMMAND> --help' for command-specific help." << endl
<< "Send bug reports to " << PACKAGE_BUGREPORT << "." << endl; << "Send bug reports to " << PACKAGE_BUGREPORT << "." << endl;
} }
@@ -146,11 +210,11 @@ void getOptions(int argc, char **argv)
break; break;
case 'q': case 'q':
verbosity = Master::Quiet; verbosity = Quiet;
break; break;
case 'v': case 'v':
verbosity = Master::Verbose; verbosity = Verbose;
break; break;
case 'h': case 'h':
@@ -184,60 +248,64 @@ void getOptions(int argc, char **argv)
/****************************************************************************/ /****************************************************************************/
int main(int argc, char **argv) int Command::execute() const
{ {
Master master;
cmdName = basename(argv[0]);
getOptions(argc, argv);
master.setIndex(masterIndex);
master.setVerbosity(verbosity);
try { try {
if (command == "alias") { func();
master.writeAlias(slavePosition, force, commandArgs); } catch (InvalidUsageException &e) {
} else if (command == "config") { cerr << e.what() << endl << endl;
master.showConfigs(); displayHelp();
} else if (command == "data") { return 1;
master.outputData(domainIndex); } catch (ExecutionFailureException &e) {
} else if (command == "debug") {
master.setDebug(commandArgs);
} else if (command == "domain") {
master.showDomains(domainIndex);
} else if (command == "master") {
master.showMaster();
} else if (command == "pdos") {
master.listPdos(slavePosition);
} else if (command == "sdos") {
master.listSdos(slavePosition);
} else if (command == "sdo_download" || command == "sd") {
master.sdoDownload(slavePosition, dataTypeStr, commandArgs);
} else if (command == "sdo_upload" || command == "su") {
master.sdoUpload(slavePosition, dataTypeStr, commandArgs);
} else if (command == "slave" || command == "slaves"
|| command == "list" || command == "ls") {
master.showSlaves(slavePosition);
} else if (command == "sii_read" || command == "sr") {
master.siiRead(slavePosition);
} else if (command == "sii_write" || command == "sw") {
master.siiWrite(slavePosition, force, commandArgs);
} else if (command == "state") {
master.requestStates(slavePosition, commandArgs);
} else if (command == "xml") {
master.generateXml(slavePosition);
} else {
cerr << "Unknown command " << command << "!" << endl;
printUsage();
exit(1);
}
} catch (MasterException &e) {
cerr << e.what() << endl; cerr << e.what() << endl;
exit(1); return 1;
} catch (MasterDeviceException &e) {
cerr << e.what() << endl;
return 1;
} }
return 0; return 0;
}
/****************************************************************************/
void Command::displayHelp() const
{
cerr << binaryBaseName << " " << command << " " << helpString;
}
/****************************************************************************/
int main(int argc, char **argv)
{
int retval = 0;
const CommandAlias *alias;
const CommandAlias *endAlias =
commandAliases + sizeof(commandAliases) / sizeof(CommandAlias);
binaryBaseName = basename(argv[0]);
getOptions(argc, argv);
// search command alias in alias map
for (alias = commandAliases; alias < endAlias; alias++) {
if (command == alias->name)
break;
}
if (alias < endAlias) { // command alias found
if (!helpRequested) {
masterDev.setIndex(masterIndex);
retval = alias->command->execute();
} else {
alias->command->displayHelp();
}
} else { // command not found
cerr << "Unknown command " << command << "!" << endl << endl;
printUsage();
retval = 1;
}
return retval;
} }
/****************************************************************************/ /****************************************************************************/