├── .gitignore ├── README.md ├── cli ├── go.sh ├── mysql2sqlite.sh ├── ppmac ├── ppmac_plugin.py ├── tune │ ├── pyramid.txt │ └── ramp.txt ├── userphase_util.c ├── userservo_util.c └── util_makefile ├── fast_gather ├── Makefile └── gather_server.c ├── misc ├── building_kernel_modules.txt ├── dac_read │ ├── Makefile │ ├── dac_read.c │ ├── dac_read.h │ └── dac_read_util.c ├── pos ├── position_gui.py ├── tp2pp.py └── utility_test │ ├── Makefile │ └── test.c ├── ppmac ├── __init__.py ├── clock.py ├── completer.py ├── config.py ├── const.py ├── fast_gather.py ├── gather.py ├── gather_types.py ├── hardware.py ├── pp_comm.py ├── tune.py └── util.py ├── project ├── Makefile ├── base.cfg ├── bgcplc_makefile ├── global definitions.pmh ├── load_project.sh ├── make_project.py ├── plcs.plc ├── subprog1.pmc └── upload_config.sh └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[cod] 2 | 3 | # C extensions 4 | *.so 5 | 6 | # Packages 7 | *.egg 8 | *.egg-info 9 | dist 10 | build 11 | eggs 12 | parts 13 | bin 14 | var 15 | sdist 16 | develop-eggs 17 | .installed.cfg 18 | lib 19 | lib64 20 | 21 | # Installer logs 22 | pip-log.txt 23 | 24 | # Unit test / coverage reports 25 | .coverage 26 | .tox 27 | nosetests.xml 28 | 29 | # Translations 30 | *.mo 31 | 32 | # Mr Developer 33 | .mr.developer.cfg 34 | .project 35 | .pydevproject 36 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ppmac 2 | ===== 3 | 4 | Some Power PMAC Linux/Python tools 5 | 6 | | Path | Description | 7 | | -------- | ---------------------------------------------------------------- | 8 | | cli/ | IPython command line interface for Power PMAC | 9 | | ppmac/ | Python package with various utilities (gather, tune, etc.) | 10 | | project/ | Project creation/loading tools | 11 | | misc/ | Miscellaneous | 12 | | fast_gather/ | Raw gather data over TCP (C server, Python client) | 13 | -------------------------------------------------------------------------------- /cli/go.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Change to the directory this script is in 4 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 5 | 6 | cd $DIR 7 | 8 | # Start up ipython with the ppmac_plugin extension, and use the ppmac profile 9 | ipython --ext ppmac_plugin --profile ppmac -c "%autocall 2" -i 10 | -------------------------------------------------------------------------------- /cli/mysql2sqlite.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ** from https://gist.github.com/esperlu/943776 3 | 4 | # Converts a mysqldump file into a Sqlite 3 compatible file. It also extracts the MySQL `KEY xxxxx` from the 5 | # CREATE block and create them in separate commands _after_ all the INSERTs. 6 | 7 | # Awk is chosen because it's fast and portable. You can use gawk, original awk or even the lightning fast mawk. 8 | # The mysqldump file is traversed only once. 9 | 10 | # Usage: $ ./mysql2sqlite mysqldump-opts db-name | sqlite3 database.sqlite 11 | # Example: $ ./mysql2sqlite --no-data -u root -pMySecretPassWord myDbase | sqlite3 database.sqlite 12 | 13 | # Thanks to and @artemyk and @gkuenning for their nice tweaks. 14 | 15 | mysqldump --compatible=ansi --skip-extended-insert --compact "$@" | \ 16 | 17 | awk ' 18 | 19 | BEGIN { 20 | FS=",$" 21 | print "PRAGMA synchronous = OFF;" 22 | print "PRAGMA journal_mode = MEMORY;" 23 | print "BEGIN TRANSACTION;" 24 | } 25 | 26 | # CREATE TRIGGER statements have funny commenting. Remember we are in trigger. 27 | /^\/\*.*CREATE.*TRIGGER/ { 28 | gsub( /^.*TRIGGER/, "CREATE TRIGGER" ) 29 | print 30 | inTrigger = 1 31 | next 32 | } 33 | 34 | # The end of CREATE TRIGGER has a stray comment terminator 35 | /END \*\/;;/ { gsub( /\*\//, "" ); print; inTrigger = 0; next } 36 | 37 | # The rest of triggers just get passed through 38 | inTrigger != 0 { print; next } 39 | 40 | # Skip other comments 41 | /^\/\*/ { next } 42 | 43 | # Print all `INSERT` lines. The single quotes are protected by another single quote. 44 | /INSERT/ { 45 | gsub( /\\\047/, "\047\047" ) 46 | gsub(/\\n/, "\n") 47 | gsub(/\\r/, "\r") 48 | gsub(/\\"/, "\"") 49 | gsub(/\\\\/, "\\") 50 | gsub(/\\\032/, "\032") 51 | print 52 | next 53 | } 54 | 55 | # Print the `CREATE` line as is and capture the table name. 56 | /^CREATE/ { 57 | print 58 | if ( match( $0, /\"[^\"]+/ ) ) tableName = substr( $0, RSTART+1, RLENGTH-1 ) 59 | } 60 | 61 | # Replace `FULLTEXT KEY` or any other `XXXXX KEY` except PRIMARY by `KEY` 62 | /^ [^"]+KEY/ && !/^ PRIMARY KEY/ { gsub( /.+KEY/, " KEY" ) } 63 | 64 | # Get rid of field lengths in KEY lines 65 | / KEY/ { gsub(/\([0-9]+\)/, "") } 66 | 67 | # Print all fields definition lines except the `KEY` lines. 68 | /^ / && !/^( KEY|\);)/ { 69 | gsub( /AUTO_INCREMENT|auto_increment/, "" ) 70 | gsub( /(CHARACTER SET|character set) [^ ]+ /, "" ) 71 | gsub( /DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP|default current_timestamp on update current_timestamp/, "" ) 72 | gsub( /(COLLATE|collate) [^ ]+ /, "" ) 73 | gsub(/(ENUM|enum)[^)]+\)/, "text ") 74 | gsub(/(SET|set)\([^)]+\)/, "text ") 75 | gsub(/UNSIGNED|unsigned/, "") 76 | gsub(/" [^ ]*(INT|int)[^ ]*/, "\" integer") 77 | if (prev) print prev "," 78 | prev = $1 79 | } 80 | 81 | # `KEY` lines are extracted from the `CREATE` block and stored in array for later print 82 | # in a separate `CREATE KEY` command. The index name is prefixed by the table name to 83 | # avoid a sqlite error for duplicate index name. 84 | /^( KEY|\);)/ { 85 | if (prev) print prev 86 | prev="" 87 | if ($0 == ");"){ 88 | print 89 | } else { 90 | if ( match( $0, /\"[^"]+/ ) ) indexName = substr( $0, RSTART+1, RLENGTH-1 ) 91 | if ( match( $0, /\([^()]+/ ) ) indexKey = substr( $0, RSTART+1, RLENGTH-1 ) 92 | key[tableName]=key[tableName] "CREATE INDEX \"" tableName "_" indexName "\" ON \"" tableName "\" (" indexKey ");\n" 93 | } 94 | } 95 | 96 | # Print all `KEY` creation lines. 97 | END { 98 | for (table in key) printf key[table] 99 | print "END TRANSACTION;" 100 | } 101 | ' 102 | exit 0 103 | -------------------------------------------------------------------------------- /cli/ppmac: -------------------------------------------------------------------------------- 1 | ../ppmac -------------------------------------------------------------------------------- /cli/tune/pyramid.txt: -------------------------------------------------------------------------------- 1 | open prog%(prog)d close 2 | 3 | close all buffers 4 | open prog %(prog)d 5 | L10=0 6 | 7 | dwell 1.0 8 | gather.enable=2 9 | linear 10 | inc 11 | TS0 12 | TA%(accel)f 13 | TS%(scurve)f 14 | F%(velocity)f 15 | while(L10 < %(iterations)d) 16 | { 17 | X(%(distance)f) 18 | dwell %(dwell)f 19 | L10=L10+1 20 | } 21 | 22 | L10=0 23 | while(L10 < %(iterations)d) 24 | { 25 | X(-%(distance)f) 26 | dwell %(dwell)f 27 | L10=L10+1 28 | } 29 | dwell0 30 | gather.enable=0 31 | close 32 | -------------------------------------------------------------------------------- /cli/tune/ramp.txt: -------------------------------------------------------------------------------- 1 | open prog%(prog)d close 2 | 3 | close all buffers 4 | open prog %(prog)d 5 | L10=0 6 | 7 | dwell 1.0 8 | gather.enable=2 9 | linear 10 | inc 11 | TS0 12 | TA1 13 | TA%(accel)f 14 | TS%(scurve)f 15 | F%(velocity)f 16 | while(L10 < %(iterations)d) 17 | { 18 | X%(distance)f 19 | dwell%(dwell)f 20 | X-%(distance)f 21 | L10=L10+1 22 | } 23 | dwell0 24 | gather.enable=0 25 | close 26 | -------------------------------------------------------------------------------- /cli/userphase_util.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | extern struct SHM *pshm; 5 | 6 | bool find_isr_function(const char *functionName, unsigned long *addr) 7 | { 8 | FILE *fp; 9 | char *tail; 10 | char cmd[64]; 11 | char result[128]; 12 | 13 | 14 | if (!functionName) { 15 | printf("function_name not specified\n"); 16 | return false; 17 | } 18 | if (!pshm) { 19 | printf("shm not initialized\n"); 20 | return false; 21 | } 22 | 23 | strcpy(cmd, "cat /proc/kallsyms | grep -w "); 24 | strcat(cmd, functionName); 25 | 26 | fp = popen(cmd, "r"); 27 | if (!fp) { 28 | printf("Unable to open cat\n"); 29 | return false; 30 | } 31 | 32 | while(fgets(result, 127, fp)) { 33 | } 34 | 35 | pclose(fp); 36 | 37 | // if result == cmd, we didn't get a response 38 | if (strcmp(result, cmd) == 0) { 39 | printf("Address not found (no response)\n"); 40 | return false; 41 | } 42 | 43 | tail = strchr(result, ' '); 44 | *addr = strtoul(&result[0], &tail, 16); 45 | if ((*addr) == 0) { 46 | printf("Address not found\n"); 47 | } 48 | 49 | return ((*addr) != 0); 50 | } 51 | 52 | bool disable_isr(unsigned char isr) 53 | { 54 | struct timespec time; 55 | 56 | time.tv_sec = 0; 57 | time.tv_nsec = 10000000; 58 | 59 | if (!pshm) { 60 | printf("Disable ISR failed pshm==NULL\n"); 61 | return false; 62 | } 63 | 64 | pshm->Motor[isr].PhaseCtrl = 0; // stop executing user phase interrupt 65 | nanosleep(&time, NULL); // wait 10ms (arbitrary) for ISR to stop executing 66 | return true; 67 | } 68 | 69 | bool enable_isr(unsigned char isr) 70 | { 71 | if (!pshm) 72 | return false; 73 | 74 | pshm->Motor[isr].PhaseCtrl = 1; // start executing phase code 75 | 76 | return true; 77 | } 78 | 79 | bool load_isr_function_from_addr(unsigned long addr, unsigned char isr) 80 | { 81 | // no need to validate ISR because maximum value of unsigned char is 255 82 | 83 | if (!disable_isr(isr)) 84 | return false; 85 | 86 | pshm->Motor[isr].UserPhase = (PUserCtrl) addr; 87 | pshm->UserAlgo.PhaseAddr[isr] = addr; 88 | printf("Loaded OK\n"); 89 | return true; 90 | } 91 | 92 | int load_isr_function(const char *functionName, unsigned char isr) 93 | { 94 | unsigned long addr; 95 | 96 | if (!functionName || functionName[0] == '\0') 97 | return false; 98 | 99 | if (!find_isr_function(functionName, &addr)) 100 | return false; 101 | 102 | printf("Got address to %s: %lx\n", functionName, addr); 103 | return load_isr_function_from_addr(addr, isr); 104 | } 105 | 106 | int main(int argc, char *argv[]) 107 | { 108 | int initialized=0; 109 | int err; 110 | unsigned char motor; 111 | char *function_name; 112 | unsigned long addr; 113 | 114 | if (argc < 3) { 115 | goto printusage; 116 | } 117 | 118 | motor = (unsigned char)atoi(argv[2]); 119 | printf("Motor: %d\n", motor); 120 | 121 | if ((err = InitLibrary()) != 0) { 122 | abort(); 123 | } 124 | initialized = 1; 125 | pshm = GetSharedMemPtr(); 126 | 127 | if (!strcmp(argv[1], "-l")) { 128 | printf("Loading ISR function\n"); 129 | if (argc < 4) 130 | goto printusage; 131 | 132 | function_name = argv[3]; 133 | if (function_name[0] == '$' && strlen(function_name) > 1) { 134 | addr = (int)strtol(&function_name[1], NULL, 16); 135 | printf("Address: %lx\n", addr); 136 | err = load_isr_function_from_addr(addr, motor); 137 | } else { 138 | printf("Function name: %s\n", function_name); 139 | err = load_isr_function(function_name, motor); 140 | } 141 | if (!err) 142 | printf("Load ISR function from addr failed\n"); 143 | 144 | } else if (!strcmp(argv[1], "-e")) { 145 | if (!(err = enable_isr(motor))) { 146 | printf("Enable ISR returned: %d\n", err); 147 | } 148 | } else if (!strcmp(argv[1], "-d")) { 149 | if (!(err = disable_isr(motor))) { 150 | printf("Disable ISR failed\n"); 151 | } 152 | } else { 153 | goto printusage; 154 | } 155 | 156 | CloseLibrary(); 157 | return !err; 158 | 159 | printusage: 160 | printf("User phase loading tool\n"); 161 | printf("%s [-l/-e/-d] motor [function_name]\n", argv[0]); 162 | printf("Examples:\n"); 163 | printf(" Load function on motor 1: %s -l 1 function_name\n", argv[0]); 164 | printf(" Enable motor phase: %s -e 1\n", argv[0]); 165 | printf(" Disable motor phase: %s -d 1\n", argv[0]); 166 | if (initialized) 167 | CloseLibrary(); 168 | 169 | return 0; 170 | 171 | } 172 | -------------------------------------------------------------------------------- /cli/userservo_util.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | extern struct SHM *pshm; 5 | 6 | bool find_isr_function(const char *functionName, unsigned long *addr) 7 | { 8 | FILE *fp; 9 | char *tail; 10 | char cmd[64]; 11 | char result[128]; 12 | 13 | 14 | if (!functionName) { 15 | printf("function_name not specified\n"); 16 | return false; 17 | } 18 | if (!pshm) { 19 | printf("shm not initialized\n"); 20 | return false; 21 | } 22 | 23 | strcpy(cmd, "cat /proc/kallsyms | grep -w "); 24 | strcat(cmd, functionName); 25 | 26 | fp = popen(cmd, "r"); 27 | if (!fp) { 28 | printf("Unable to open cat\n"); 29 | return false; 30 | } 31 | 32 | while(fgets(result, 127, fp)) { 33 | } 34 | 35 | pclose(fp); 36 | 37 | // if result == cmd, we didn't get a response 38 | if (strcmp(result, cmd) == 0) { 39 | printf("Address not found (no response)\n"); 40 | return false; 41 | } 42 | 43 | tail = strchr(result, ' '); 44 | *addr = strtoul(&result[0], &tail, 16); 45 | if ((*addr) == 0) { 46 | printf("Address not found\n"); 47 | } 48 | 49 | return ((*addr) != 0); 50 | } 51 | 52 | bool disable_isr(unsigned char isr) 53 | { 54 | struct timespec time; 55 | 56 | time.tv_sec = 0; 57 | time.tv_nsec = 10000000; 58 | 59 | if (!pshm) { 60 | printf("Disable ISR failed pshm==NULL\n"); 61 | return false; 62 | } 63 | 64 | pshm->Motor[isr].ServoCtrl = 0; // stop executing user Servo interrupt 65 | nanosleep(&time, NULL); // wait 10ms (arbitrary) for ISR to stop executing 66 | return true; 67 | } 68 | 69 | bool enable_isr(unsigned char isr) 70 | { 71 | if (!pshm) 72 | return false; 73 | 74 | pshm->Motor[isr].ServoCtrl = 1; // start executing Servo code 75 | 76 | return true; 77 | } 78 | 79 | bool load_isr_function_from_addr(unsigned long addr, unsigned char isr) 80 | { 81 | // no need to validate ISR because maximum value of unsigned char is 255 82 | 83 | if (!disable_isr(isr)) 84 | return false; 85 | 86 | pshm->UserAlgo.ServoCtrlAddr[isr] = (unsigned int)addr; 87 | // pshm->Motor[isr].Ctrl = (PUserCtrl) addr; 88 | printf("Loaded OK\n"); 89 | return true; 90 | } 91 | 92 | int load_isr_function(const char *functionName, unsigned char isr) 93 | { 94 | unsigned long addr; 95 | 96 | if (!functionName || functionName[0] == '\0') 97 | return false; 98 | 99 | if (!find_isr_function(functionName, &addr)) 100 | return false; 101 | 102 | printf("Got address to %s: %lx\n", functionName, addr); 103 | return load_isr_function_from_addr(addr, isr); 104 | } 105 | 106 | int main(int argc, char *argv[]) 107 | { 108 | int initialized=0; 109 | int err; 110 | unsigned char motor; 111 | char *function_name; 112 | unsigned long addr; 113 | 114 | if (argc < 3) { 115 | goto printusage; 116 | } 117 | 118 | motor = (unsigned char)atoi(argv[2]); 119 | printf("Motor: %d\n", motor); 120 | 121 | if ((err = InitLibrary()) != 0) { 122 | abort(); 123 | } 124 | initialized = 1; 125 | pshm = GetSharedMemPtr(); 126 | 127 | if (!strcmp(argv[1], "-l")) { 128 | printf("Loading ISR function\n"); 129 | if (argc < 4) 130 | goto printusage; 131 | 132 | function_name = argv[3]; 133 | if (function_name[0] == '$' && strlen(function_name) > 1) { 134 | addr = (int)strtol(&function_name[1], NULL, 16); 135 | printf("Address: %lx\n", addr); 136 | err = load_isr_function_from_addr(addr, motor); 137 | } else { 138 | printf("Function name: %s\n", function_name); 139 | err = load_isr_function(function_name, motor); 140 | } 141 | if (!err) 142 | printf("Load ISR function from addr failed\n"); 143 | 144 | } else if (!strcmp(argv[1], "-e")) { 145 | if (!(err = enable_isr(motor))) { 146 | printf("Enable ISR returned: %d\n", err); 147 | } 148 | } else if (!strcmp(argv[1], "-d")) { 149 | if (!(err = disable_isr(motor))) { 150 | printf("Disable ISR failed\n"); 151 | } 152 | } else { 153 | goto printusage; 154 | } 155 | 156 | CloseLibrary(); 157 | return !err; 158 | 159 | printusage: 160 | printf("User servo loading tool\n"); 161 | printf("%s [-l/-e/-d] number [function_name]\n", argv[0]); 162 | printf("Examples:\n"); 163 | printf(" Load function to UserAlgo.ServoCtrlAddr 1: %s -l 1 function_name\n", argv[0]); 164 | // printf(" Use UserAlgo.ServoCtrlAddr[1] on motor 3: %s -m 3 1\n", argv[0]); 165 | printf(" Enable motor 3 servo: %s -e 3\n", argv[0]); 166 | printf(" Disable motor 3 servo: %s -d 3\n", argv[0]); 167 | if (initialized) 168 | CloseLibrary(); 169 | 170 | return 0; 171 | 172 | } 173 | -------------------------------------------------------------------------------- /cli/util_makefile: -------------------------------------------------------------------------------- 1 | # Utility (non-realtime, general purpose ppmac) program building template 2 | # Modified from http://forums.deltatau.com/showthread.php?tid=1207 3 | # 4 | # All source (.c) files 5 | SRCS = %(source_files)s 6 | OBJS = $(SRCS:.c=.o) 7 | PROG = %(output_name)s 8 | 9 | # Cross compiler toolchain 10 | ARCH=powerpc 11 | CC=g++ 12 | 13 | INCLUDE := -I$(PWD) -I/opt/ppmac/libppmac -I/opt/ppmac/rtpmac 14 | 15 | CFLAGS := -O0 -g3 -Wall -fmessage-length=0 -mhard-float -funsigned-char -D_REENTRANT -D__XENO__ 16 | 17 | LDFLAGS := -L/opt/ppmac/libppmac -L/usr/local/xenomai/lib 18 | 19 | LIBS := -lrt -lpthread -lpthread_rt -ldl -lppmac 20 | 21 | WRAP := -Wl,-rpath,/opt/ppmac/rtppmac \ 22 | -Wl,-rpath,/opt/ppmac/libppmac \ 23 | -Wl,-rpath,/usr/local/xenomai/lib \ 24 | -Wl,--wrap,shm_open \ 25 | -Wl,--wrap,pthread_create \ 26 | -Wl,--wrap,pthread_setschedparam \ 27 | -Wl,--wrap,pthread_getschedparam \ 28 | -Wl,--wrap,pthread_yield \ 29 | -Wl,--wrap,sched_yield \ 30 | -Wl,--wrap,pthread_kill \ 31 | -Wl,--wrap,sem_init \ 32 | -Wl,--wrap,sem_destroy \ 33 | -Wl,--wrap,sem_post \ 34 | -Wl,--wrap,sem_timedwait \ 35 | -Wl,--wrap,sem_wait \ 36 | -Wl,--wrap,sem_trywait \ 37 | -Wl,--wrap,sem_getvalue \ 38 | -Wl,--wrap,sem_open \ 39 | -Wl,--wrap,sem_close \ 40 | -Wl,--wrap,sem_unlink \ 41 | -Wl,--wrap,clock_getres \ 42 | -Wl,--wrap,clock_gettime \ 43 | -Wl,--wrap,clock_settime \ 44 | -Wl,--wrap,clock_nanosleep \ 45 | -Wl,--wrap,nanosleep \ 46 | -Wl,--wrap,pthread_mutexattr_init \ 47 | -Wl,--wrap,pthread_mutexattr_destroy \ 48 | -Wl,--wrap,pthread_mutexattr_gettype \ 49 | -Wl,--wrap,pthread_mutexattr_settype \ 50 | -Wl,--wrap,pthread_mutexattr_getprotocol \ 51 | -Wl,--wrap,pthread_mutexattr_setprotocol \ 52 | -Wl,--wrap,pthread_mutexattr_getpshared \ 53 | -Wl,--wrap,pthread_mutexattr_setpshared \ 54 | -Wl,--wrap,pthread_mutex_init \ 55 | -Wl,--wrap,pthread_mutex_destroy \ 56 | -Wl,--wrap,pthread_mutex_lock \ 57 | -Wl,--wrap,pthread_mutex_trylock \ 58 | -Wl,--wrap,pthread_mutex_timedlock\ 59 | -Wl,--wrap,pthread_mutex_unlock \ 60 | -Wl,--wrap,pthread_condattr_init \ 61 | -Wl,--wrap,pthread_condattr_destroy \ 62 | -Wl,--wrap,pthread_condattr_getclock \ 63 | -Wl,--wrap,pthread_condattr_setclock \ 64 | -Wl,--wrap,pthread_condattr_getpshared \ 65 | -Wl,--wrap,pthread_condattr_setpshared \ 66 | -Wl,--wrap,pthread_cond_init \ 67 | -Wl,--wrap,pthread_cond_destroy \ 68 | -Wl,--wrap,pthread_cond_wait \ 69 | -Wl,--wrap,pthread_cond_timedwait \ 70 | -Wl,--wrap,pthread_cond_signal \ 71 | -Wl,--wrap,pthread_cond_broadcast \ 72 | -Wl,--wrap,mq_open \ 73 | -Wl,--wrap,mq_close \ 74 | -Wl,--wrap,mq_unlink \ 75 | -Wl,--wrap,mq_getattr \ 76 | -Wl,--wrap,mq_setattr \ 77 | -Wl,--wrap,mq_send \ 78 | -Wl,--wrap,mq_timedsend \ 79 | -Wl,--wrap,mq_receive \ 80 | -Wl,--wrap,mq_timedreceive \ 81 | -Wl,--wrap,mq_notify \ 82 | -Wl,--wrap,open \ 83 | -Wl,--wrap,socket \ 84 | -Wl,--wrap,close \ 85 | -Wl,--wrap,ioctl \ 86 | -Wl,--wrap,read \ 87 | -Wl,--wrap,write \ 88 | -Wl,--wrap,recvmsg \ 89 | -Wl,--wrap,sendmsg \ 90 | -Wl,--wrap,recvfrom \ 91 | -Wl,--wrap,sendto \ 92 | -Wl,--wrap,recv \ 93 | -Wl,--wrap,send \ 94 | -Wl,--wrap,getsockopt \ 95 | -Wl,--wrap,setsockopt \ 96 | -Wl,--wrap,bind \ 97 | -Wl,--wrap,connect \ 98 | -Wl,--wrap,listen \ 99 | -Wl,--wrap,accept \ 100 | -Wl,--wrap,getsockname \ 101 | -Wl,--wrap,getpeername \ 102 | -Wl,--wrap,shutdown \ 103 | -Wl,--wrap,timer_create \ 104 | -Wl,--wrap,timer_delete \ 105 | -Wl,--wrap,timer_settime \ 106 | -Wl,--wrap,timer_getoverrun \ 107 | -Wl,--wrap,timer_gettime \ 108 | -Wl,--wrap,ftruncate \ 109 | -Wl,--wrap,ftruncate64 \ 110 | -Wl,--wrap,close \ 111 | -Wl,--wrap,shm_open \ 112 | -Wl,--wrap,shm_unlink \ 113 | -Wl,--wrap,mmap \ 114 | -Wl,--wrap,mmap64 \ 115 | -Wl,--wrap,munmap \ 116 | -Wl,--wrap,select 117 | 118 | all: $(PROG) 119 | 120 | $(PROG): $(OBJS) 121 | @echo "Linking object files with output." 122 | @$(CC) -o $(PROG) $(OBJS) $(LDFLAGS) $(LIBS) $(WRAP) 123 | @echo "Linking complete." 124 | @echo "Cleaning up build directory." 125 | @rm *.o 126 | 127 | %%.o: %%.c 128 | @echo "Starting compilation." 129 | $(CC) $(CFLAGS) $(INCLUDE) -c $< 130 | @echo "Compilation complete." 131 | 132 | clean:: 133 | @$(RM) *.out *.o 134 | -------------------------------------------------------------------------------- /fast_gather/Makefile: -------------------------------------------------------------------------------- 1 | # Utility (non-realtime, general purpose ppmac) program building template 2 | # Modified from http://forums.deltatau.com/showthread.php?tid=1207 3 | # 4 | # All source (.c) files 5 | SRCS = gather_server.c 6 | OBJS = $(SRCS:.c=.o) 7 | PROG = gather_server 8 | 9 | # Cross compiler toolchain 10 | ARCH=powerpc 11 | CC=g++ 12 | 13 | INCLUDE := -I$(PWD) -I/opt/ppmac/libppmac -I/opt/ppmac/rtpmac 14 | 15 | CFLAGS := -O0 -g3 -Wall -fmessage-length=0 -mhard-float -funsigned-char -D_REENTRANT -D__XENO__ 16 | 17 | LDFLAGS := -L/opt/ppmac/libppmac -L/usr/local/xenomai/lib 18 | 19 | LIBS := -lrt -lpthread -lpthread_rt -ldl -lppmac 20 | 21 | WRAP := -Wl,-rpath,/opt/ppmac/rtppmac \ 22 | -Wl,-rpath,/opt/ppmac/libppmac \ 23 | -Wl,-rpath,/usr/local/xenomai/lib \ 24 | -Wl,--wrap,shm_open \ 25 | -Wl,--wrap,pthread_create \ 26 | -Wl,--wrap,pthread_setschedparam \ 27 | -Wl,--wrap,pthread_getschedparam \ 28 | -Wl,--wrap,pthread_yield \ 29 | -Wl,--wrap,sched_yield \ 30 | -Wl,--wrap,pthread_kill \ 31 | -Wl,--wrap,sem_init \ 32 | -Wl,--wrap,sem_destroy \ 33 | -Wl,--wrap,sem_post \ 34 | -Wl,--wrap,sem_timedwait \ 35 | -Wl,--wrap,sem_wait \ 36 | -Wl,--wrap,sem_trywait \ 37 | -Wl,--wrap,sem_getvalue \ 38 | -Wl,--wrap,sem_open \ 39 | -Wl,--wrap,sem_close \ 40 | -Wl,--wrap,sem_unlink \ 41 | -Wl,--wrap,clock_getres \ 42 | -Wl,--wrap,clock_gettime \ 43 | -Wl,--wrap,clock_settime \ 44 | -Wl,--wrap,clock_nanosleep \ 45 | -Wl,--wrap,nanosleep \ 46 | -Wl,--wrap,pthread_mutexattr_init \ 47 | -Wl,--wrap,pthread_mutexattr_destroy \ 48 | -Wl,--wrap,pthread_mutexattr_gettype \ 49 | -Wl,--wrap,pthread_mutexattr_settype \ 50 | -Wl,--wrap,pthread_mutexattr_getprotocol \ 51 | -Wl,--wrap,pthread_mutexattr_setprotocol \ 52 | -Wl,--wrap,pthread_mutexattr_getpshared \ 53 | -Wl,--wrap,pthread_mutexattr_setpshared \ 54 | -Wl,--wrap,pthread_mutex_init \ 55 | -Wl,--wrap,pthread_mutex_destroy \ 56 | -Wl,--wrap,pthread_mutex_lock \ 57 | -Wl,--wrap,pthread_mutex_trylock \ 58 | -Wl,--wrap,pthread_mutex_timedlock\ 59 | -Wl,--wrap,pthread_mutex_unlock \ 60 | -Wl,--wrap,pthread_condattr_init \ 61 | -Wl,--wrap,pthread_condattr_destroy \ 62 | -Wl,--wrap,pthread_condattr_getclock \ 63 | -Wl,--wrap,pthread_condattr_setclock \ 64 | -Wl,--wrap,pthread_condattr_getpshared \ 65 | -Wl,--wrap,pthread_condattr_setpshared \ 66 | -Wl,--wrap,pthread_cond_init \ 67 | -Wl,--wrap,pthread_cond_destroy \ 68 | -Wl,--wrap,pthread_cond_wait \ 69 | -Wl,--wrap,pthread_cond_timedwait \ 70 | -Wl,--wrap,pthread_cond_signal \ 71 | -Wl,--wrap,pthread_cond_broadcast \ 72 | -Wl,--wrap,mq_open \ 73 | -Wl,--wrap,mq_close \ 74 | -Wl,--wrap,mq_unlink \ 75 | -Wl,--wrap,mq_getattr \ 76 | -Wl,--wrap,mq_setattr \ 77 | -Wl,--wrap,mq_send \ 78 | -Wl,--wrap,mq_timedsend \ 79 | -Wl,--wrap,mq_receive \ 80 | -Wl,--wrap,mq_timedreceive \ 81 | -Wl,--wrap,mq_notify \ 82 | -Wl,--wrap,open \ 83 | -Wl,--wrap,socket \ 84 | -Wl,--wrap,close \ 85 | -Wl,--wrap,ioctl \ 86 | -Wl,--wrap,read \ 87 | -Wl,--wrap,write \ 88 | -Wl,--wrap,recvmsg \ 89 | -Wl,--wrap,sendmsg \ 90 | -Wl,--wrap,recvfrom \ 91 | -Wl,--wrap,sendto \ 92 | -Wl,--wrap,recv \ 93 | -Wl,--wrap,send \ 94 | -Wl,--wrap,getsockopt \ 95 | -Wl,--wrap,setsockopt \ 96 | -Wl,--wrap,bind \ 97 | -Wl,--wrap,connect \ 98 | -Wl,--wrap,listen \ 99 | -Wl,--wrap,accept \ 100 | -Wl,--wrap,getsockname \ 101 | -Wl,--wrap,getpeername \ 102 | -Wl,--wrap,shutdown \ 103 | -Wl,--wrap,timer_create \ 104 | -Wl,--wrap,timer_delete \ 105 | -Wl,--wrap,timer_settime \ 106 | -Wl,--wrap,timer_getoverrun \ 107 | -Wl,--wrap,timer_gettime \ 108 | -Wl,--wrap,ftruncate \ 109 | -Wl,--wrap,ftruncate64 \ 110 | -Wl,--wrap,close \ 111 | -Wl,--wrap,shm_open \ 112 | -Wl,--wrap,shm_unlink \ 113 | -Wl,--wrap,mmap \ 114 | -Wl,--wrap,mmap64 \ 115 | -Wl,--wrap,munmap \ 116 | -Wl,--wrap,select 117 | 118 | all: $(PROG) 119 | 120 | $(PROG): $(OBJS) 121 | @echo "Linking object files with output." 122 | @$(CC) -o $(PROG) $(OBJS) $(LDFLAGS) $(LIBS) $(WRAP) 123 | @echo "Linking complete." 124 | @echo "Cleaning up build directory." 125 | @rm *.o 126 | 127 | $(OBJS): $(SRCS) 128 | @echo "Starting compilation." 129 | $(CC) $(CFLAGS) $(INCLUDE) -c $< 130 | @echo "Compilation complete." 131 | 132 | clean:: 133 | @$(RM) *.out *.o 134 | -------------------------------------------------------------------------------- /fast_gather/gather_server.c: -------------------------------------------------------------------------------- 1 | /* 2 | * (relatively) fast gather data server 3 | * - a simple forking TCP server that sends raw Power PMAC gather data 4 | * 5 | * Usage: gather_server [port] 6 | * Default port is 2332 7 | * 8 | * (Largely based - rather, copied - on beej's networking guide, 9 | * the source of which is in the public domain) 10 | * 11 | * Author: K Lauer (klauer@bnl.gov) 12 | */ 13 | 14 | // vi: sw=4 ts=4 15 | 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include // Power PMAC-specific 29 | 30 | #define DEFAULT_PORT "2332" 31 | #define BACKLOG 1 // how many pending connections queue will hold 32 | 33 | // Input buffer size 34 | const int BUF_SIZE = 100; 35 | 36 | // Gather types as strings 37 | #define N_GATHER_TYPES 8 38 | const char *gather_type_str[] = { 39 | "uint32", 40 | "int32", 41 | "uint24", 42 | "int24", 43 | "float", 44 | "double", 45 | "ubits", 46 | "sbits" 47 | }; 48 | 49 | // Gather types that aren't in the enum can be processed with these: 50 | // (see notes below) 51 | const unsigned int start_mask = 0xF800; 52 | const unsigned int bit_count_mask = 0x07FF; 53 | /* 54 | from http://forums.deltatau.com/archive/index.php?thread-933.html : 55 | Undocumented gather types: 56 | When Gather.Type[i] is not in the range 0 to 5, it contains a code specifying what part of a 57 | 32-bit integer register the element specified by Gather.Addr[i] occupies. That is, when 58 | Gather.Addr[i] is set in the Script environment to the address of a partial-word element, 59 | Power PMAC automatically sets Gather.Type[i] to this code. 60 | 61 | Note that this code does not affect the gathered value, which will always be the full 32-bit 62 | register. Rather, it can be used to isolate the desired portion of this 32-bit value. 63 | 64 | Gather.Type[i] is a 16-bit value. The high 5 bits (11-15) specify the starting (low) bit number 65 | of the partial-word element in the 32-bit word. The low 11 bits (0-10) specify how many bits are 66 | used. The values of interest are: 67 | 68 | 1 bit: $7c6 69 | 2 bits: $786 70 | 3 bits: $746 71 | 4 bits: $706 72 | 8 bits: $606 73 | 12 bits: $506 74 | 16 bits: $407 75 | 76 | So for Motor[x].AmpEna, Gather.Type is set to 26566 ($67c6). This means 1 bit ($7c6) starting 77 | at bit 12 (6*2 + 0). 78 | 79 | A value of 50694 ($c606) means 8 bits ($606) starting at bit 24 (c*2 + 0). 80 | 81 | More generally, the value in bits 6-10 is 32 minus the number of bits in the element. 82 | */ 83 | 84 | // Ensures that the full buffer is sent 85 | int send_all(int s, const char *buf, unsigned int len) 86 | { 87 | unsigned int total = 0; // how many bytes we've sent 88 | unsigned int bytesleft = len; // how many we have left to send 89 | int n; 90 | 91 | while(total < len) { 92 | n = send(s, buf+total, bytesleft, 0); 93 | if (n == -1) { 94 | break; 95 | } 96 | 97 | total += n; 98 | bytesleft -= n; 99 | } 100 | 101 | //*len = total; // return number actually sent here 102 | 103 | return n==-1?-1:0; // return -1 on failure, 0 on success 104 | } 105 | 106 | // Send a simple string to the client 107 | int send_str(int client, const char *str) { 108 | if (!str) 109 | return -1; 110 | 111 | return send_all(client, str, strlen(str)); 112 | } 113 | 114 | // Send a string to the client with the packet length header 115 | int send_str_packet(int client, const char *str) { 116 | if (!str) 117 | return -1; 118 | 119 | unsigned int length = strlen(str); 120 | 121 | send_all(client, (char*)&length, sizeof(unsigned int)); 122 | return send_all(client, str, length); 123 | } 124 | 125 | // Send the type information for each gathered item to the client 126 | // If phase is set, gathered phase information will be sent, 127 | // otherwise gathered servo information will be sent. 128 | bool send_types(int client, bool phase) { 129 | GATHER *gather; 130 | gather = &pshm->Gather; 131 | unsigned char items; 132 | unsigned short *types; 133 | unsigned int buf_len; 134 | 135 | if (phase) { 136 | items = gather->PhaseItems; 137 | types = gather->PhaseType; 138 | } else { 139 | items = gather->Items; 140 | types = &gather->Type[0]; 141 | } 142 | 143 | buf_len = sizeof(items) + sizeof(unsigned short) * items + 1; 144 | 145 | printf("client %d types request. items=%d buffer length=%d (phase=%d)\n", client, items, buf_len, phase); 146 | send_all(client, (char*)&buf_len, sizeof(unsigned int)); 147 | send_str(client, "T"); 148 | send_all(client, (char*)&items, sizeof(unsigned char)); 149 | send_all(client, (char*)types, sizeof(unsigned short) * items); 150 | 151 | return (items > 0); 152 | } 153 | 154 | // Send the gathered raw data to the client 155 | // If phase is set, gathered phase data will be sent, 156 | // otherwise gathered servo data will be sent. 157 | void send_data(int client, bool phase) { 158 | GATHER *gather; 159 | gather = &pshm->Gather; 160 | int line_length=0; 161 | unsigned int buf_len, samples, *buffer; 162 | unsigned short *types; 163 | unsigned char items; 164 | 165 | if (phase) { 166 | items = gather->PhaseItems; 167 | types = gather->PhaseType; 168 | samples = gather->PhaseSamples; 169 | buffer = gather->PhaseBuffer; 170 | line_length = gather->PhaseLineLength << 2; 171 | } else { 172 | items = gather->Items; 173 | types = gather->Type; 174 | samples = gather->Samples; 175 | buffer = gather->Buffer; 176 | line_length = gather->LineLength << 2; 177 | } 178 | 179 | buf_len = sizeof(unsigned int) + (line_length * samples) + 1; 180 | 181 | printf("client %d data request. items=%d samples=%d bytes/line=%d buffer length=%d (phase=%d)\n", 182 | client, items, gather->Samples, line_length, buf_len, phase); 183 | 184 | send_all(client, (char*)&buf_len, sizeof(unsigned int)); 185 | send_str(client, "D"); 186 | send_all(client, (char*)&samples, sizeof(unsigned int)); 187 | send_all(client, (char*)buffer, (line_length * samples)); 188 | } 189 | 190 | // Strip off CR/LF from the client buffer 191 | void strip_buffer(char buf[], int buf_size) { 192 | int i; 193 | for (i = 0; i < buf_size; i++) { 194 | if (buf[i] == '\n' || buf[i] == '\r') { 195 | buf[i] = 0; 196 | return; 197 | } else if (buf[i] == 0) { 198 | return; 199 | } 200 | } 201 | 202 | buf[buf_size - 1] = 0; 203 | } 204 | 205 | int handle_client(int client) { 206 | int received; 207 | char buf[BUF_SIZE]; 208 | bool phase=false; 209 | 210 | while (1) { 211 | if ((received = recv(client, &buf, BUF_SIZE - 1, 0)) <= 0) { 212 | perror("recv"); 213 | break; 214 | } 215 | 216 | strip_buffer(buf, BUF_SIZE); 217 | if (!strcmp(buf, "phase")) { 218 | phase = true; 219 | send_str_packet(client, "K"); 220 | printf("client %d phase mode\n", client); 221 | } else if (!strcmp(buf, "servo")) { 222 | phase = false; 223 | send_str_packet(client, "K"); 224 | printf("client %d servo mode\n", client); 225 | } else if (!strcmp(buf, "types")) { 226 | send_types(client, phase); 227 | } else if (!strcmp(buf, "data")) { 228 | send_data(client, phase); 229 | } else if (!strcmp(buf, "all")) { 230 | if (send_types(client, phase)) { 231 | send_data(client, phase); 232 | } 233 | } 234 | 235 | buf[0] = 0; 236 | } 237 | 238 | printf("client %d closed\n", client); 239 | return 0; 240 | 241 | } 242 | 243 | /// Handler for the child processes 244 | void sigchld_handler(int s) 245 | { 246 | while(waitpid(-1, NULL, WNOHANG) > 0); 247 | } 248 | 249 | /// Get IPv4/IPv6 address info 250 | void *get_in_addr(struct sockaddr *sa) 251 | { 252 | if (sa->sa_family == AF_INET) { 253 | // IPv4 254 | return &(((struct sockaddr_in*)sa)->sin_addr); 255 | } else { 256 | // IPv6 257 | return &(((struct sockaddr_in6*)sa)->sin6_addr); 258 | } 259 | } 260 | 261 | // Main server loop, listens on port 262 | int server_loop(const char *port) { 263 | int sockfd, new_fd; // listen on sock_fd, new connection on new_fd 264 | struct addrinfo hints, *servinfo, *p; 265 | struct sockaddr_storage their_addr; // connector's address information 266 | socklen_t sin_size; 267 | struct sigaction sa; 268 | int yes=1; 269 | char s[INET6_ADDRSTRLEN]; 270 | int rv; 271 | 272 | // Initialize the Power PMAC gplib library 273 | InitLibrary(); 274 | 275 | memset(&hints, 0, sizeof hints); 276 | hints.ai_family = AF_UNSPEC; 277 | hints.ai_socktype = SOCK_STREAM; 278 | hints.ai_flags = AI_PASSIVE; 279 | 280 | if ((rv = getaddrinfo(NULL, port, &hints, &servinfo)) != 0) { 281 | fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv)); 282 | return 1; 283 | } 284 | 285 | // Bind to the first result that works 286 | for(p = servinfo; p != NULL; p = p->ai_next) { 287 | if ((sockfd = socket(p->ai_family, p->ai_socktype, 288 | p->ai_protocol)) == -1) { 289 | perror("server: socket"); 290 | continue; 291 | } 292 | 293 | if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes, 294 | sizeof(int)) == -1) { 295 | perror("setsockopt"); 296 | exit(1); 297 | } 298 | 299 | if (bind(sockfd, p->ai_addr, p->ai_addrlen) == -1) { 300 | close(sockfd); 301 | perror("server: bind"); 302 | continue; 303 | } 304 | 305 | break; 306 | } 307 | 308 | if (p == NULL) { 309 | fprintf(stderr, "server: failed to bind\n"); 310 | return 2; 311 | } 312 | 313 | freeaddrinfo(servinfo); 314 | 315 | if (listen(sockfd, BACKLOG) == -1) { 316 | perror("listen"); 317 | exit(1); 318 | } 319 | 320 | // reap all dead processes -- set their handler to this function 321 | sa.sa_handler = sigchld_handler; 322 | sigemptyset(&sa.sa_mask); 323 | sa.sa_flags = SA_RESTART; 324 | if (sigaction(SIGCHLD, &sa, NULL) == -1) { 325 | perror("sigaction"); 326 | exit(1); 327 | } 328 | 329 | printf("server: listening on port %s\n", port); 330 | 331 | while(1) { // main accept() loop 332 | sin_size = sizeof their_addr; 333 | new_fd = accept(sockfd, (struct sockaddr *)&their_addr, &sin_size); 334 | if (new_fd == -1) { 335 | perror("accept"); 336 | continue; 337 | } 338 | 339 | inet_ntop(their_addr.ss_family, 340 | get_in_addr((struct sockaddr *)&their_addr), 341 | s, sizeof s); 342 | printf("server: got connection from %s\n", s); 343 | 344 | if (fork() == 0) { 345 | close(sockfd); // child doesn't need the listener 346 | handle_client(new_fd); 347 | close(new_fd); 348 | exit(0); 349 | } 350 | close(new_fd); 351 | } 352 | 353 | // Close the Power PMAC gplib library 354 | CloseLibrary(); 355 | return 0; 356 | } 357 | 358 | int main(int argc, char *argv[]) 359 | { 360 | if (argc == 2) { 361 | int port = atoi(argv[1]); 362 | if (port > 0 && port < 65536) { 363 | return server_loop(argv[1]); 364 | } else { 365 | printf("Invalid port. Use %s [port_number]\n", argv[0]); 366 | } 367 | } else { 368 | return server_loop(DEFAULT_PORT); 369 | } 370 | 371 | } 372 | 373 | 374 | /* 375 | // Some old gather tests 376 | // 377 | // Previously used in send_types: 378 | // The size of each gather element in bytes 379 | unsigned int gather_type_sz[] = { 380 | 4, // sizeof(uint32), 381 | 4, // sizeof(int32), 382 | 4, // sizeof(uint24), // <-- TODO not sure if stored as 3 or 4 bytes 383 | 4, // sizeof(int24), // <-- TODO not sure if stored as 3 or 4 bytes 384 | sizeof(float), 385 | sizeof(double), 386 | 4, //sizeof(ubits), // <-- TODO this may be incorrect 387 | 4 //sizeof(sbits) // <-- TODO this may be incorrect 388 | }; 389 | 390 | int j; 391 | unsigned short type; 392 | for (j = 0; j < items; j++) { 393 | type = types[j]; 394 | if (type < N_GATHER_TYPES) { 395 | line_length += gather_type_sz[type]; 396 | } else { 397 | line_length += 4; 398 | } 399 | } 400 | 401 | 402 | // Miscellaneous 403 | unsigned int samples; 404 | unsigned int i; 405 | unsigned short type; 406 | GATHER *gather; 407 | char *p_buffer; 408 | unsigned int time; 409 | gather = &pshm->Gather; 410 | 411 | samples = gather->Index; 412 | 413 | printf("Index: %d\n", gather->Index); 414 | printf("Samples: %d (max=%d)\n", gather->Samples, gather->MaxLines); 415 | printf("Period: %d\n", gather->Period); 416 | printf("Bytes per line: %d\n", gather->LineLength); // <-- this is wrong, returns 4 417 | // note: turns out it's not bytes per line, but 32-bit words per line (so multiply by 4) 418 | printf("Items: %d\n", gather->Items); 419 | 420 | unsigned int bit_start, bit_count; 421 | for (i = 0; i <= gather->Items; i++) { 422 | type = gather->Type[i]; 423 | printf("Type %d: %d ", i, type); 424 | if (type < N_GATHER_TYPES) { 425 | printf("(%s)", gather_type_str[type]); 426 | } else { 427 | bit_start = (type & start_mask) >> 11; 428 | bit_count = (type & bit_count_mask); 429 | bit_count = 32 - (bit_count >> 6); 430 | printf("(bit start %d count %d)", bit_start, bit_count); 431 | } 432 | printf("\n"); 433 | } 434 | 435 | p_buffer = (char *)(gather->Buffer); 436 | 437 | int size, int_temp; 438 | unsigned int uint_temp; 439 | float flt_temp; 440 | double dbl_temp; 441 | int j; 442 | for (i = 0; i < 100; i++) { 443 | for (j = 0; j < gather->Items; j++) { 444 | type = gather->Type[j]; 445 | if (type < N_GATHER_TYPES) { 446 | size = gather_type_sz[type]; 447 | } else { 448 | size = 4; 449 | } 450 | 451 | switch (type) { 452 | case enum_uint32gat: 453 | memcpy(&uint_temp, (unsigned char*)p_buffer, size); 454 | printf("uint:%u", uint_temp); 455 | break; 456 | 457 | case enum_int32gat: 458 | memcpy(&int_temp, (unsigned char*)p_buffer, size); 459 | printf("int:%d", int_temp); 460 | break; 461 | 462 | case enum_uint24gat: 463 | case enum_int24gat: 464 | int_temp = 0; 465 | memcpy(&int_temp, (unsigned char*)p_buffer, size); 466 | printf("(int24:%d)", int_temp); 467 | // TODO sign extend 468 | break; 469 | 470 | case enum_floatgat: 471 | memcpy(&flt_temp, (unsigned char*)p_buffer, size); 472 | printf("float:%f", flt_temp); 473 | break; 474 | 475 | case enum_doublegat: 476 | memcpy(&dbl_temp, (unsigned char*)p_buffer, size); 477 | printf("double:%f", dbl_temp); 478 | break; 479 | 480 | case enum_ubitsgat: 481 | case enum_sbitsgat: 482 | memcpy(&uint_temp, (unsigned char*)p_buffer, sizeof(unsigned int)); 483 | printf("?:%u", uint_temp); 484 | break; 485 | 486 | default: 487 | bit_start = (type & start_mask) >> 11; 488 | bit_count = (type & bit_count_mask); 489 | bit_count = 32 - (bit_count >> 6); 490 | 491 | memcpy(&uint_temp, (unsigned char*)p_buffer, sizeof(unsigned int)); 492 | printf("(%u bits of):%u", bit_count, uint_temp); 493 | uint_temp = (uint_temp >> bit_start); 494 | uint_temp &= ((1 << bit_count) - 1); 495 | printf("=%u", uint_temp); 496 | break; 497 | } 498 | 499 | p_buffer += size; 500 | 501 | printf("\t"); 502 | } 503 | printf("\n"); 504 | } 505 | */ 506 | 507 | -------------------------------------------------------------------------------- /misc/building_kernel_modules.txt: -------------------------------------------------------------------------------- 1 | Debian multiarch for 64-bit machines: 2 | sudo apt-get install multiarch-support 3 | sudo apt-get --add-architecture i386 4 | sudo apt-get update 5 | sudo apt-get install ia32-libs 6 | 7 | Install ELDK: 8 | download ppc-2008-04-01_amcc.iso 9 | mount -o loop *.iso install_cd 10 | 11 | cd install_cd 12 | ./install -d ~/ppmac/eldk ppc_4xxFP 13 | 14 | Make eldk environment: 15 | 16 | $ source ~/ppmac/eldk/env.sh 17 | $ cat ~/ppmac/eldk/env.sh 18 | export ARCH=powerpc 19 | export CROSS_COMPILE=ppc_4xxFP- 20 | export DEPMOD=~/ppmac/eldk/usr/bin/depmod.pl 21 | export PATH=~/ppmac/eldk/usr/bin:~/ppmac/eldk/bin:/usr/lib/lightdm/lightdm:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin 22 | 23 | Copy libppmac, rtpmac from Power PMAC itself (**NOT** the IDE distribution!) 24 | scp -r deltatau@ppmac_ip:/opt/ppmac/libppmac $(PPMAC_ROOT)/src/dtlibs 25 | scp -r deltatau@ppmac_ip:/opt/ppmac/rtpmac $(PPMAC_ROOT)/src/dtlibs 26 | 27 | Copied windows ELDK version of the kernel (with whatever patches were applied, 28 | no need to rebuild it), prepared it with xenomai to fix the soft links: 29 | 30 | Used 7-zip to compress the 31 | C:\suite_install_path\powerpc-460-linux-gnu\opt\eldk-4.2\debian_rootfs\usr\src\linux-2.6.30.3-xeno-2.5.6 32 | directory to linux-2.6.30.3-xeno-2.5.6.7z 33 | 34 | 7zr x linux-2.6.30.3-xeno-2.5.6.7z 35 | ln -s linux-2.6.30.3-xeno-2.5.6 linux 36 | cd linux 37 | 38 | # remove the windows softlinks (.lnk files) 39 | find . |grep \.lnk$ | xargs rm 40 | cd .. 41 | tar xfvj xenomai-2.5.6.tar.bz2 42 | cd xenomai-* 43 | 44 | # create the proper linux softlinks 45 | sh scripts/prepare-kernel.sh --linux=../linux --arch=powerpc 46 | 47 | # symlinks should be created then: 48 | find ../linux/ -type l 49 | 50 | cd ../linux 51 | source ~/ppmac/eldk/env.sh 52 | 53 | # just rebuild the scripts directory for the necessary utilities 54 | make scripts 55 | 56 | kernel module builds with this makefile, assuming ppmac stuff is in ~/ppmac: 57 | 58 | ARCH=powerpc 59 | PPMAC_ROOT=$(HOME)/ppmac 60 | PATH=$(PPMAC_ROOT)/eldk/usr/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin 61 | CROSS_COMPILE=ppc_4xxFP- 62 | KDIR=$(PPMAC_ROOT)/src/linux 63 | KSRC=$(PPMAC_ROOT)/src/linux 64 | CC=$(CROSS_COMPILE)gcc 65 | AS=$(CROSS_COMPILE)as 66 | STRIP=$(CROSS_COMPILE)strip 67 | INCLUDE=$(PPMAC_ROOT)/eldk/ppc_4xxFP-/usr/include 68 | RTPMACINCLUDEDIR=$(PPMAC_ROOT)/src/dtlibs/rtpmac 69 | LIBPPMACINCLUDEDIR=$(PPMAC_ROOT)/src/dtlibs/libppmac 70 | export ARCH 71 | export CROSS_COMPILE 72 | 73 | OBJS := ${patsubst %, %.o, $(MODULES)} 74 | CLEANMOD := ${patsubst %, .%*, $(MODULES)} 75 | PWD := $(shell if [ "$$PWD" != "" ]; then echo $$PWD; else pwd; fi) 76 | 77 | 78 | obj-m += usralgo.o 79 | usralgo-objs := usralgomain.o usrcode.o 80 | 81 | EXTRA_CFLAGS := -O2 -DCONFIG_460EX -D_GNU_SOURCE -D_REENTRANT -D__XENO__ -mhard-float -I$(RTPMACINCLUDEDIR) -I$(LIBPPMACINCLUDEDIR) -I$(KSRC)/include/xenomai -I$(KSRC)/include -I$(KSRC)/include/xenomai/posix -I$(INCLUDE) $(ADD_CFLAGS) 82 | KBUILD_EXTRA_SYMBOLS := $(LIBPPMACINCLUDEDIR)/Module.symvers 83 | 84 | %.o: %.S 85 | $(CC) -D__KERNEL__ -x c -E $< -o $*.i 86 | $(AS) -mbooke -o $@ $*.i 87 | 88 | all:: 89 | $(MAKE) -C $(KSRC) SUBDIRS=$(PWD) modules 90 | 91 | modules: 92 | @echo "$(CFLAGS)" 93 | 94 | clean:: 95 | $(RM) *.o .*.o.d .*.o.cmd *.ko 96 | $(RM) -R .tmp* 97 | $(RM) .runinfo 98 | rm -rf .*.cmd *.o *.ko *.mod.c *.i *.so Module.symvers modules.order 99 | 100 | 101 | -------------------------------------------------------------------------------- /misc/dac_read/Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | g++ -c dac_read.c 3 | g++ -c dac_read_util.c 4 | g++ -o dac_read dac_read.o dac_read_util.o 5 | 6 | clean: 7 | rm -rf *.o dac_read 8 | -------------------------------------------------------------------------------- /misc/dac_read/dac_read.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "dac_read.h" 5 | 6 | // vi: ts=2 sw=2 7 | 8 | inline 9 | int is_little_endian() 10 | { 11 | int i=1; 12 | char c = *((char*)&i); 13 | return (c == 1); 14 | } 15 | 16 | bool read_dac_file(const char *fn, DACData *df) { 17 | FILE *fp = fopen(fn, "rb"); 18 | if (!fp) { 19 | perror("unable to open file"); 20 | return false; 21 | } 22 | 23 | if (!df) { 24 | perror("DACData not specified"); 25 | return false; 26 | } 27 | 28 | if (df->table) { 29 | free(df->table); 30 | df->table = NULL; 31 | } 32 | 33 | unsigned int magic; 34 | if (fread(&magic, sizeof(unsigned int), 1, fp) <= 0) { 35 | perror("unable to read file"); 36 | goto fail; 37 | } 38 | 39 | if (ntohl(magic) != DACDATA_MAGIC) { 40 | perror("file type not recognized"); 41 | goto fail; 42 | } 43 | 44 | if (fread(&df->table_size, sizeof(unsigned int), 1, fp) <= 0) { 45 | perror("unable to read file"); 46 | goto fail; 47 | } 48 | 49 | if (fread(&df->scale_factor, sizeof(unsigned int), 1, fp) <= 0) { 50 | perror("unable to read file"); 51 | goto fail; 52 | } 53 | 54 | df->scale_factor = ntohl(df->scale_factor); 55 | df->table_size = ntohl(df->table_size); 56 | 57 | printf("Scale factor: %d\n", df->scale_factor); 58 | printf("Array size: %d\n", df->table_size); 59 | if (df->table_size == 0) { 60 | perror("empty dac table"); 61 | goto fail; 62 | } 63 | 64 | df->table = (int *)malloc(sizeof(int) * df->table_size); 65 | if (!df->table) { 66 | perror("failed to allocate dac table memory"); 67 | goto fail; 68 | } 69 | 70 | if (fread(df->table, sizeof(int), df->table_size, fp) != df->table_size) { 71 | perror("unable to read table"); 72 | goto fail; 73 | } 74 | 75 | if (is_little_endian()) { 76 | unsigned int i; 77 | for (i=0; i < df->table_size; i++) { 78 | df->table[i] = ntohl(df->table[i]); 79 | } 80 | } 81 | 82 | fclose(fp); 83 | return true; 84 | 85 | fail: 86 | fclose(fp); 87 | if (df->table) { 88 | free(df->table); 89 | df->table = NULL; 90 | } 91 | return false; 92 | } 93 | 94 | 95 | -------------------------------------------------------------------------------- /misc/dac_read/dac_read.h: -------------------------------------------------------------------------------- 1 | #ifndef _H_DAC_READ 2 | #define _H_DAC_READ 3 | 4 | // vi: ts=2 sw=2 5 | #define DACDATA_MAGIC 0x494e54 // .INT 6 | 7 | struct DACData { 8 | unsigned int table_size; 9 | unsigned int scale_factor; 10 | int *table; 11 | }; 12 | 13 | bool read_dac_file(const char *fn, DACData *df); 14 | 15 | #endif 16 | -------------------------------------------------------------------------------- /misc/dac_read/dac_read_util.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "dac_read.h" 4 | 5 | int main(int argc, char *argv[]) { 6 | if (argc != 2) { 7 | printf("Usage: %s filename.dac\n", argv[0]); 8 | return 1; 9 | } 10 | 11 | DACData df; 12 | df.table = NULL; 13 | 14 | if (read_dac_file(argv[1], &df)) { 15 | printf("Table size: %d\n", df.table_size); 16 | printf("Scale factor: %d\n", df.scale_factor); 17 | for (int i=0; i < 10; i++) { 18 | if (i < df.table_size) { 19 | printf("%d\t%d\n", i, df.table[i]); 20 | } 21 | } 22 | 23 | free(df.table); 24 | df.table = NULL; 25 | return 0; 26 | } else { 27 | return 1; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /misc/pos: -------------------------------------------------------------------------------- 1 | python position_gui.py 3 5 -f %.4f -r 0.05 -t & 2 | PID1=$! 3 | 4 | python position_gui.py 11 12 -f %.4f -r 0.05 -t & 5 | PID2=$! 6 | 7 | trap ctrl_c INT 8 | 9 | function ctrl_c() { 10 | kill $PID1 11 | kill $PID2 12 | } 13 | 14 | wait 15 | 16 | -------------------------------------------------------------------------------- /misc/position_gui.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | :mod:`position_gui` -- PyQt4 Power PMAC motor position monitor 4 | ============================================================== 5 | 6 | .. module:: position_gui 7 | :synopsis: Display motor positions in a simple PyQt4 GUI 8 | .. moduleauthor:: Ken Lauer 9 | """ 10 | 11 | from __future__ import print_function 12 | import os 13 | import sys 14 | import time 15 | import argparse 16 | import copy 17 | 18 | from PyQt4 import (QtGui, QtCore) 19 | from PyQt4.QtCore import Qt 20 | 21 | MODULE_PATH = os.path.dirname(os.path.abspath(__file__)) 22 | sys.path.insert(0, os.path.join(MODULE_PATH, '..')) 23 | from ppmac import pp_comm 24 | 25 | PPMAC_HOST = os.environ.get('PPMAC_HOST', '10.3.2.115') 26 | PPMAC_PORT = int(os.environ.get('PPMAC_PORT', '22')) 27 | PPMAC_USER = os.environ.get('PPMAC_USER', 'root') 28 | PPMAC_PASS = os.environ.get('PPMAC_PASS', 'deltatau') 29 | 30 | 31 | class PositionMonitor(QtGui.QFrame): 32 | def __init__(self, comm, motors=[1, 2, 3], rate=0.1, 33 | scale=1.0, format_='%g', 34 | parent=None, on_top=False): 35 | QtGui.QFrame.__init__(self, parent) 36 | 37 | if on_top: 38 | self.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint) 39 | 40 | self.comm = comm 41 | self.motors = motors 42 | self.update_rate = rate * 1000. 43 | self.scale = scale 44 | self.format_ = format_ 45 | self.widgets = widgets = [] 46 | 47 | layout = QtGui.QFormLayout() 48 | for motor in motors: 49 | widgets.append(QtGui.QLabel('0.0')) 50 | label = widgets[-1] 51 | label.setAlignment(Qt.AlignRight) 52 | layout.addRow(str(motor), label) 53 | 54 | self.setLayout(layout) 55 | 56 | QtCore.QTimer.singleShot(0, self.update) 57 | 58 | def reconnect(self): 59 | print('Reconnecting...') 60 | try: 61 | comm = copy.copy(self.comm) 62 | except: 63 | return 64 | else: 65 | self.comm = comm 66 | 67 | @property 68 | def gpascii(self): 69 | if self.comm: 70 | return self.comm.gpascii 71 | else: 72 | return None 73 | 74 | def update(self): 75 | t0 = time.time() 76 | 77 | motors = self.motors 78 | 79 | gpascii = self.gpascii 80 | if gpascii is None: 81 | self.reconnect() 82 | gpascii = self.gpascii 83 | if gpascii is None: 84 | return 85 | 86 | try: 87 | act_pos = [gpascii.get_variable('Motor[%d].ActPos' % i, type_=float) 88 | for i in motors] 89 | home_pos = [gpascii.get_variable('Motor[%d].HomePos' % i, type_=float) 90 | for i in motors] 91 | except pp_comm.TimeoutError: 92 | self.reconnect() 93 | QtCore.QTimer.singleShot(5000.0, self.update) 94 | return 95 | 96 | rel_pos = [self.scale * (act - home) 97 | for act, home in zip(act_pos, home_pos)] 98 | 99 | for i, pos in enumerate(rel_pos): 100 | self.widgets[i].setText(self.format_ % pos) 101 | 102 | self.act_pos = act_pos 103 | self.home_pos = home_pos 104 | self.rel_pos = rel_pos 105 | 106 | elapsed = (time.time() - t0) * 1000.0 107 | next_update = max(self.update_rate - elapsed, 0) 108 | QtCore.QTimer.singleShot(next_update, self.update) 109 | 110 | 111 | def main(host=PPMAC_HOST, port=PPMAC_PORT, 112 | user=PPMAC_USER, password=PPMAC_PASS, 113 | **kwargs): 114 | global gui 115 | 116 | app = QtGui.QApplication(sys.argv) 117 | 118 | print('Connecting to host %s:%d' % (host, port)) 119 | print('User %s password %s' % (user, password)) 120 | print('Motors: %s' % motors) 121 | #print('Scale: %s Format: %s' % (scale, format_)) 122 | try: 123 | comm = pp_comm.PPComm(host=host, port=port, user=user, password=password) 124 | except Exception as ex: 125 | print('Failed to connect (%s) %s' % (ex.__class__.__name__, ex)) 126 | return 127 | 128 | app.quitOnLastWindowClosed = True 129 | QtGui.QApplication.instance = app 130 | 131 | monitor = PositionMonitor(comm, **kwargs) 132 | monitor.show() 133 | try: 134 | sys.exit(app.exec_()) 135 | except Exception as ex: 136 | print('ERROR: Failed with exception', ex) 137 | raise 138 | 139 | if __name__ == '__main__': 140 | parser = argparse.ArgumentParser(description='Motor position display') 141 | parser.add_argument('first', type=int, default=1, 142 | help='First motor to display') 143 | parser.add_argument('last', type=int, default=10, 144 | help='Last motor to display') 145 | parser.add_argument('-r', '--rate', type=float, default=0.1, 146 | help='Update rate (0.1sec)') 147 | parser.add_argument('-i', '--host', type=str, default=PPMAC_HOST, 148 | help='Power PMAC host IP (environment variable PPMAC_HOST)') 149 | parser.add_argument('-o', '--port', type=int, default=PPMAC_PORT, 150 | help='Power PMAC SSH port (environment variable PPMAC_PORT)') 151 | parser.add_argument('-u', '--user', type=str, default=PPMAC_USER, 152 | help='Username (root) (environment variable PPMAC_USER)') 153 | parser.add_argument('-p', '--password', type=str, default=PPMAC_PASS, 154 | help='Password (deltatau) (environment variable PPMAC_PASS)') 155 | parser.add_argument('-s', '--scale', type=float, default=1.0, 156 | help='Scale factor for the encoder positions') 157 | parser.add_argument('-f', '--format', type=str, default='%.3f', 158 | help='String format for the encoder positions') 159 | parser.add_argument('-t', '--on-top', action='store_true', 160 | help='String format for the encoder positions') 161 | 162 | args = parser.parse_args() 163 | if args is not None: 164 | motors = range(args.first, args.last + 1) 165 | main(host=args.host, port=args.port, 166 | user=args.user, password=args.password, 167 | motors=motors, rate=args.rate, 168 | scale=args.scale, format_=args.format, 169 | on_top=args.on_top) 170 | -------------------------------------------------------------------------------- /misc/tp2pp.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | import re 3 | 4 | ON_BOARD_IO = 'On_Board_IO' 5 | ON_BOARD_SERVO_ICS = 'On_Board_Servo_ICs' 6 | EXPANSION_SERVO_ICS = 'Expansion_Servo_ICs' 7 | MACRO_ICS = 'MACRO_ICs' 8 | EXPANSION_IO = 'Expansion_IO' 9 | SHARED_MEMORY = 'Shared_memory' 10 | UNRECOGNIZED = 'Unrecognized' 11 | 12 | card_types = [ 13 | 'On_Board_IO', 14 | 'On_Board_Servo_ICs', 15 | 'Expansion_Servo_ICs', 16 | 'MACRO_ICs', 17 | 'Expansion_IO', 18 | 'Shared_memory', 19 | 20 | 'Unrecognized', 21 | ] 22 | 23 | def trim(s): 24 | if ',' in s: 25 | s = s.split(',')[0] 26 | return re.sub('[$XxYy:,]', '', s) 27 | 28 | def conv_on_board_io(addr): 29 | return sum([0x000000, 30 | (addr & 0x300) * 0x1000, 31 | (addr & 0x7CF8) * 0x8, 32 | (addr & 0x7) * 4 33 | ]) 34 | 35 | def conv_on_board_servo_ics(addr): 36 | return sum([0x400000, 37 | (addr & 0x100) * 0x1000, 38 | (addr & 0x7CF8) * 0x8, 39 | (addr & 0x7) * 4, 40 | ]) 41 | 42 | def conv_expansion_servo_ics(addr): 43 | return sum([0x600000, 44 | (addr & 0x100) * 0x1000, 45 | (addr & 0x7CF8) * 0x8, 46 | (addr & 0x7) * 4 47 | ]) 48 | 49 | def conv_macro_ics(addr): 50 | return sum([0x800000, 51 | 0, 52 | (addr & 0x7BF8) * 0x8, 53 | (addr & 0x7) * 4 54 | ]) 55 | 56 | def conv_expansion_io(addr): 57 | return sum([0xA00000, 58 | (addr & 0x300) * 0x1000, 59 | (addr & 0x70F8) * 0x8, 60 | (addr & 0x7) * 4 61 | ]) 62 | 63 | def conv_shared_memory(addr): 64 | return sum([0xE00000, 65 | (addr & 0x10000) * 0x10, 66 | (addr & 0x3FF8) * 0x8, 67 | (addr & 0x7) * 4 68 | ]) 69 | 70 | def tp2pp(tp_addr): 71 | """ 72 | Input: tp_addr -- turbo PMAC address 73 | Output: (pp_addr, chip_select_info) 74 | """ 75 | card_type = card_types[-1] # unrecognized 76 | 77 | address_info = [ 78 | ('DPRCS_', 0x60000, SHARED_MEMORY), 79 | ('CS00_', 0x78800, ON_BOARD_IO), 80 | ('CS02_', 0x78900, ON_BOARD_IO), 81 | ('CS04_', 0x78A00, ON_BOARD_IO), 82 | ('CS06_', 0x78B00, ON_BOARD_IO), 83 | ('CS0_', 0x78000, ON_BOARD_SERVO_ICS), 84 | ('CS1_', 0x78100, ON_BOARD_SERVO_ICS), 85 | ('CS2_', 0x78200, EXPANSION_SERVO_ICS), 86 | ('CS3_', 0x78300, EXPANSION_SERVO_ICS), 87 | ('CS4_', 0x78400, MACRO_ICS), 88 | ('CS10_', 0x78C00, EXPANSION_IO), 89 | ('CS12_', 0x78D00, EXPANSION_IO), 90 | ('CS14_', 0x78E00, EXPANSION_IO), 91 | ('CS16_', 0x78F00, EXPANSION_IO), 92 | ] 93 | 94 | addr_offset = 0 95 | addr_width = 0 96 | if ',' in tp_addr: 97 | tp_info = tp_addr.split(',') 98 | addr_offset = int(tp_info[1]) 99 | if len(tp_info) > 2: 100 | addr_width = int(tp_info[2]) 101 | else: 102 | addr_width = 1 103 | 104 | if addr_offset > 23 or addr_width > 23: 105 | addr_offset = 0 106 | addr_width = 24 107 | 108 | addr = int(trim(tp_addr), 16) 109 | pp_addr = None 110 | 111 | for chip_select, mask, desc in address_info: 112 | if (addr & mask) == mask: 113 | conv_func = globals()['conv_%s' % desc.lower()] 114 | pp_addr = conv_func(addr) 115 | cs_info = '%s CS:%s' % (desc, chip_select) 116 | 117 | if pp_addr is None: 118 | raise ValueError('Unknown address: %x' % addr) 119 | 120 | if 'x' in tp_addr.lower(): 121 | pp_addr = pp_addr + 0x20 122 | 123 | if addr_offset > 0 or addr_width > 0: 124 | return ('$%x.%d.%d' % (pp_addr, addr_offset + 8, addr_width), cs_info) 125 | else: 126 | return ('$%x' % (pp_addr), cs_info) 127 | 128 | def examples(): 129 | examples = ['78C00', 130 | '$78C00', 131 | 'Y:$78C00,7', 132 | 'Y:$78C00,0,8', 133 | 'X:$78C00,0,8', 134 | 'Y:$79D81,0,16', 135 | 'Y:$79D82,0,16', 136 | 'Y:$79D83,0,16', 137 | 'Y:$79D84,0,16', 138 | 'Y:$79D85,0,16', 139 | 'Y:$79D86,0,16', 140 | 'Y:$79D87,0,16', 141 | 'Y:$79D88,0,16', 142 | 'Y:$79D89,0,16', 143 | 'Y:$79D8A,0,16', 144 | 'Y:$79D8B,0,16', 145 | 'Y:$79D8C,0,16', 146 | 'Y:$79D8D,0,16', 147 | 'Y:$79D8E,0,16', 148 | 'Y:$79D8F,0,16', 149 | 'Y:$79D90,0,16', 150 | 'Y:$79D91,0,16', 151 | 'Y:$79D92,0,16', 152 | 'Y:$79D93,0,16', 153 | 'Y:$79D94,0,16', 154 | 'Y:$79D95,0,16', 155 | 'Y:$79D96,0,16', 156 | 'Y:$79D97,0,16', 157 | 'Y:$79D98,0,16', 158 | 'Y:$79D99,0,16', 159 | 'Y:$79D9A,0,16', 160 | 'Y:$79D9B,0,16', 161 | 'Y:$79D9C,0,16', 162 | 'Y:$79D9D,0,16', 163 | 'Y:$79D9E,0,16', 164 | 'Y:$79D9F,0,16', 165 | 'Y:$79DA0,0,16', 166 | 'Y:$79DA1,0,16', 167 | 'Y:$79DA2,0,16', 168 | 'Y:$79DA3,0,16', 169 | 'Y:$79DA4,0,16', 170 | 'Y:$79DA5,0,16', 171 | 'Y:$79DA6,0,16', 172 | 'Y:$79DA7,0,16', 173 | 'Y:$79DA8,0,16', 174 | 'Y:$79DA9,0,16', 175 | 'Y:$79DAA,0,16', 176 | 'Y:$79DAB,0,16', 177 | 'Y:$79DAC,0,16', 178 | 'Y:$79DAD,0,16', 179 | 'Y:$79DAE,0,16', 180 | 'Y:$79DAF,0,16', 181 | 'Y:$79DB0,0,16', 182 | 'Y:$79DB1,0,16', 183 | 'Y:$79DB2,0,16', 184 | 'Y:$79DB3,0,16', 185 | 'Y:$79DB4,0,16', 186 | 'Y:$79DB5,0,16', 187 | 'Y:$79DB6,0,16', 188 | 'Y:$79DB7,0,16', 189 | 'Y:$79DB8,0,16', 190 | 'Y:$79DB9,0,16', 191 | 'Y:$79DBA,0,16', 192 | 'Y:$79DBB,0,16', 193 | 'Y:$79DBC,0,16', 194 | 'Y:$79DBD,0,16', 195 | 'Y:$79DBE,0,16', 196 | 'Y:$79DBF,0,16', 197 | 'Y:$79DC0,0,16', 198 | 'Y:$79DC1,0,16', 199 | 'Y:$79DC2,0,16', 200 | 'Y:$79DC3,0,16', 201 | 'Y:$79DC4,0,16', 202 | 'Y:$79DC5,0,16', 203 | 'Y:$79DC6,0,16', 204 | 'Y:$79DC7,0,16', 205 | 'Y:$79DC8,0,16', 206 | 'Y:$79DC9,0,16', 207 | 'Y:$79DCA,0,16', 208 | 'Y:$79DCB,0,16', 209 | 'Y:$79DCC,0,16', 210 | 'Y:$79DCD,0,16', 211 | 'Y:$79DCE,0,16', 212 | 'Y:$79DCF,0,16', 213 | 'Y:$79DD0,0,16', 214 | 'Y:$79DD1,0,16', 215 | 'Y:$79DD2,0,16', 216 | 'Y:$79DD5,0,16,S', 217 | 'Y:$79DD6,0,16,S', 218 | 'X:$79DD7,0,16,S', 219 | 'Y:$79DD8,0,16,S', 220 | 'Y:$79DD9,0,16,S', 221 | 'Y:$79DDA,0,16,S', 222 | 'Y:$79DDB,0,16,S', 223 | 'Y:$79DDC,0,16,S', 224 | 'Y:$79DDD,0,16,S', 225 | 'Y:$79DDE,0,16,S', 226 | 'Y:$79DDF,0,16,S', 227 | 'Y:$79DE0,0,16,S', 228 | 'Y:$79DE1,0,16,S', 229 | 'Y:$79DE2,0,16,S', 230 | 'Y:$79DE3,0,16,S', 231 | 'Y:$79DE4,0,16,S', 232 | 'X:$79218,11', 233 | 'X:$79218,8', 234 | 'X:$79218,14', 235 | 'X:$79218,16', 236 | 'X:$79218,17', 237 | 'X:$79218,18', 238 | 'X:$79218,15', 239 | 'X:$79218,20', 240 | 'X:$79218,21', 241 | 'X:$79218,22', 242 | 'X:$79218,23', 243 | 'X:$79218,20,4' 244 | ] 245 | 246 | for example in examples: 247 | if example.strip(): 248 | print('Turbo PMAC: %s Power PMAC: %s' % (example, tp2pp(example))) 249 | 250 | if __name__ == '__main__': 251 | import sys 252 | if len(sys.argv) == 1: 253 | print('Usage: %s (turbo pmac address 1) [address 2 [address 3] ...]' % sys.argv[0]) 254 | sys.exit(1) 255 | 256 | for tpmac in sys.argv[1:]: 257 | print('%s\t%s' % (tpmac, '\t'.join(tp2pp(tpmac)))) 258 | -------------------------------------------------------------------------------- /misc/utility_test/Makefile: -------------------------------------------------------------------------------- 1 | # All source (.c) files 2 | SRCS = test.c 3 | 4 | # Cross compiler toolchain 5 | ARCH=powerpc 6 | CC=gcc 7 | 8 | INCLUDE := -I$(PWD) -I/opt/ppmac/libppmac -I/opt/ppmac/rtpmac 9 | 10 | CFLAGS := -O0 -g3 -Wall -fmessage-length=0 -mhard-float -funsigned-char -D_REENTRANT -D__XENO__ 11 | 12 | LDFLAGS := -L/opt/ppmac/libppmac -L/usr/local/xenomai/lib 13 | 14 | LIBS := -lrt -lpthread -lpthread_rt -ldl -lppmac 15 | 16 | WRAP := -Wl,-rpath,/opt/ppmac/rtppmac \ 17 | -Wl,-rpath,/opt/ppmac/libppmac \ 18 | -Wl,-rpath,/usr/local/xenomai/lib \ 19 | -Wl,--wrap,shm_open \ 20 | -Wl,--wrap,pthread_create \ 21 | -Wl,--wrap,pthread_setschedparam \ 22 | -Wl,--wrap,pthread_getschedparam \ 23 | -Wl,--wrap,pthread_yield \ 24 | -Wl,--wrap,sched_yield \ 25 | -Wl,--wrap,pthread_kill \ 26 | -Wl,--wrap,sem_init \ 27 | -Wl,--wrap,sem_destroy \ 28 | -Wl,--wrap,sem_post \ 29 | -Wl,--wrap,sem_timedwait \ 30 | -Wl,--wrap,sem_wait \ 31 | -Wl,--wrap,sem_trywait \ 32 | -Wl,--wrap,sem_getvalue \ 33 | -Wl,--wrap,sem_open \ 34 | -Wl,--wrap,sem_close \ 35 | -Wl,--wrap,sem_unlink \ 36 | -Wl,--wrap,clock_getres \ 37 | -Wl,--wrap,clock_gettime \ 38 | -Wl,--wrap,clock_settime \ 39 | -Wl,--wrap,clock_nanosleep \ 40 | -Wl,--wrap,nanosleep \ 41 | -Wl,--wrap,pthread_mutexattr_init \ 42 | -Wl,--wrap,pthread_mutexattr_destroy \ 43 | -Wl,--wrap,pthread_mutexattr_gettype \ 44 | -Wl,--wrap,pthread_mutexattr_settype \ 45 | -Wl,--wrap,pthread_mutexattr_getprotocol \ 46 | -Wl,--wrap,pthread_mutexattr_setprotocol \ 47 | -Wl,--wrap,pthread_mutexattr_getpshared \ 48 | -Wl,--wrap,pthread_mutexattr_setpshared \ 49 | -Wl,--wrap,pthread_mutex_init \ 50 | -Wl,--wrap,pthread_mutex_destroy \ 51 | -Wl,--wrap,pthread_mutex_lock \ 52 | -Wl,--wrap,pthread_mutex_trylock \ 53 | -Wl,--wrap,pthread_mutex_timedlock\ 54 | -Wl,--wrap,pthread_mutex_unlock \ 55 | -Wl,--wrap,pthread_condattr_init \ 56 | -Wl,--wrap,pthread_condattr_destroy \ 57 | -Wl,--wrap,pthread_condattr_getclock \ 58 | -Wl,--wrap,pthread_condattr_setclock \ 59 | -Wl,--wrap,pthread_condattr_getpshared \ 60 | -Wl,--wrap,pthread_condattr_setpshared \ 61 | -Wl,--wrap,pthread_cond_init \ 62 | -Wl,--wrap,pthread_cond_destroy \ 63 | -Wl,--wrap,pthread_cond_wait \ 64 | -Wl,--wrap,pthread_cond_timedwait \ 65 | -Wl,--wrap,pthread_cond_signal \ 66 | -Wl,--wrap,pthread_cond_broadcast \ 67 | -Wl,--wrap,mq_open \ 68 | -Wl,--wrap,mq_close \ 69 | -Wl,--wrap,mq_unlink \ 70 | -Wl,--wrap,mq_getattr \ 71 | -Wl,--wrap,mq_setattr \ 72 | -Wl,--wrap,mq_send \ 73 | -Wl,--wrap,mq_timedsend \ 74 | -Wl,--wrap,mq_receive \ 75 | -Wl,--wrap,mq_timedreceive \ 76 | -Wl,--wrap,mq_notify \ 77 | -Wl,--wrap,open \ 78 | -Wl,--wrap,socket \ 79 | -Wl,--wrap,close \ 80 | -Wl,--wrap,ioctl \ 81 | -Wl,--wrap,read \ 82 | -Wl,--wrap,write \ 83 | -Wl,--wrap,recvmsg \ 84 | -Wl,--wrap,sendmsg \ 85 | -Wl,--wrap,recvfrom \ 86 | -Wl,--wrap,sendto \ 87 | -Wl,--wrap,recv \ 88 | -Wl,--wrap,send \ 89 | -Wl,--wrap,getsockopt \ 90 | -Wl,--wrap,setsockopt \ 91 | -Wl,--wrap,bind \ 92 | -Wl,--wrap,connect \ 93 | -Wl,--wrap,listen \ 94 | -Wl,--wrap,accept \ 95 | -Wl,--wrap,getsockname \ 96 | -Wl,--wrap,getpeername \ 97 | -Wl,--wrap,shutdown \ 98 | -Wl,--wrap,timer_create \ 99 | -Wl,--wrap,timer_delete \ 100 | -Wl,--wrap,timer_settime \ 101 | -Wl,--wrap,timer_getoverrun \ 102 | -Wl,--wrap,timer_gettime \ 103 | -Wl,--wrap,ftruncate \ 104 | -Wl,--wrap,ftruncate64 \ 105 | -Wl,--wrap,close \ 106 | -Wl,--wrap,shm_open \ 107 | -Wl,--wrap,shm_unlink \ 108 | -Wl,--wrap,mmap \ 109 | -Wl,--wrap,mmap64 \ 110 | -Wl,--wrap,munmap \ 111 | -Wl,--wrap,select 112 | 113 | OBJS = $(SRCS:.c=.o) 114 | PROG = $(SRCS:.c=).out 115 | 116 | all: $(PROG) 117 | 118 | $(PROG): $(OBJS) 119 | @echo "Linking object files with output." 120 | $(CC) -o $(PROG) $(OBJS) $(LDFLAGS) $(LIBS) $(WRAP) 121 | @echo "Linking complete." 122 | @echo "Cleaning up build directory." 123 | @rm *.o 124 | 125 | $(OBJS): $(SRCS) 126 | @echo "Starting compilation." 127 | $(CC) $(CFLAGS) $(INCLUDE) -c $< 128 | @echo "Compilation complete." 129 | 130 | clean:: 131 | @$(RM) *.out *.o -------------------------------------------------------------------------------- /misc/utility_test/test.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int 5 | main( int argc, char *argv[] ) 6 | { 7 | int err = InitLibrary(); 8 | if(err != 0) abort(); 9 | struct SHM *pshm = GetSharedMemPtr(); 10 | pshm->P[0]++; 11 | printf("P0 = %e\n", pshm->P[0]); 12 | 13 | CloseLibrary(); 14 | return 0; 15 | } 16 | -------------------------------------------------------------------------------- /ppmac/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | :mod:`ppmac` -- Power PMAC Python Library 4 | ========================================= 5 | 6 | .. module:: ppmac 7 | :synopsis: Miscellaneous tools for using the Power PMAC with Python 8 | .. moduleauthor:: Ken Lauer 9 | """ 10 | -------------------------------------------------------------------------------- /ppmac/clock.py: -------------------------------------------------------------------------------- 1 | """ 2 | :mod:`ppmac.clock` -- Phase/servo clock tools 3 | ============================================= 4 | 5 | .. module:: ppmac.clock 6 | :synopsis: Set the phase/servo clocks for all devices. 7 | .. moduleauthor:: Ken Lauer 8 | 9 | """ 10 | 11 | from __future__ import print_function 12 | from .hardware import enumerate_hardware 13 | from .hardware import GateIO 14 | from . import const 15 | from . import util 16 | 17 | 18 | def get_clock_master(devices): 19 | """ 20 | Returns the device in control of the clocks 21 | 22 | Returns: (phase clock master, servo clock master) 23 | """ 24 | phase_master = None 25 | servo_master = None 26 | 27 | for device in devices: 28 | if isinstance(device, GateIO): 29 | continue 30 | 31 | if device.phase_master: 32 | phase_master = device 33 | 34 | if device.servo_master: 35 | servo_master = device 36 | 37 | return phase_master, servo_master 38 | 39 | 40 | def valid_servo_frequencies(phase_freq): 41 | return [float(phase_freq) / i 42 | for i in range(1, const.MAX_SERVO_DIVIDER + 1)] 43 | 44 | 45 | def valid_pwm_frequencies(phase_freq): 46 | return [float(phase_freq) / (2. * i) 47 | for i in range(1, const.MAX_PWM_DIVIDER + 1)] 48 | 49 | 50 | def get_global_phase_script(devices, phase_freq, servo_divider, 51 | phase_divider=0, pwm_freq_mult=0, 52 | phase_mult=0, 53 | time_base=100): 54 | """ 55 | Get a script that would setup the clock for all devices and 56 | channels. 57 | 58 | devices: a list of devices, from `hardware.enumerate_hardware` 59 | phase_freq: desired phase frequency 60 | servo_divider: servo divider, servo_freq = phase_freq / (servo_divider + 1) 61 | phase_divider: phase divider for non-phase masters: 62 | phase_freq = main_phase_freq / (servo_divider + 1) 63 | """ 64 | 65 | assert(servo_divider in range(0, const.MAX_SERVO_DIVIDER)) 66 | 67 | phase_master, servo_master = get_clock_master(devices) 68 | 69 | if phase_master is None or servo_master is None: 70 | raise RuntimeError('Phase/servo master not found') 71 | 72 | devices = list(devices) 73 | 74 | # Move the phase master and servo master to the end of the update list 75 | devices.remove(phase_master) 76 | devices.append(phase_master) 77 | 78 | if servo_master is not phase_master: 79 | devices.remove(servo_master) 80 | devices.append(servo_master) 81 | 82 | script_lines = [] 83 | for device in devices: 84 | s = device.get_clock_settings(phase_freq, phase_divider, servo_divider, 85 | pwm_freq_mult=pwm_freq_mult, 86 | phase_clock_mult=phase_mult) 87 | 88 | if s is not None: 89 | for var, value in s: 90 | script_lines.append('%s=%s' % (var, value)) 91 | 92 | script_lines.append('') 93 | 94 | servo_period_ms = 1000.0 * (servo_divider + 1) / phase_freq 95 | phase_over_servo_pd = 1. / (servo_divider + 1) 96 | script_lines.append('Sys.ServoPeriod=%g' % servo_period_ms) 97 | script_lines.append('Sys.PhaseOverServoPeriod=%g' % phase_over_servo_pd) 98 | 99 | if time_base is not None: 100 | # Set the time base for all coordinate systems 101 | script_lines.append('&*%{0}'.format(time_base)) 102 | 103 | return script_lines 104 | 105 | 106 | def set_global_phase(devices, phase_freq, servo_divider, verbose=True, 107 | dry_run=False, **kwargs): 108 | """ 109 | Set the phase clock settings 110 | 111 | See `get_global_phase_script` for parameter information 112 | """ 113 | 114 | script = get_global_phase_script(devices, phase_freq, servo_divider, 115 | **kwargs) 116 | 117 | gpascii = devices[0].gpascii 118 | with util.WpKeySave(gpascii, verbose=True): 119 | for line in script: 120 | if not line: 121 | continue 122 | 123 | if verbose: 124 | if '=' in line: 125 | var, value = line.split('=') 126 | print('Setting %s=%s (current value=%s)' % 127 | (var, value, gpascii.get_variable(var))) 128 | else: 129 | print('Sending %s' % line) 130 | 131 | if not dry_run: 132 | try: 133 | gpascii.send_line(line, sync=True) 134 | except Exception as ex: 135 | print('* Failed: %s' % ex) 136 | 137 | 138 | def test(): 139 | from .pp_comm import PPComm 140 | 141 | comm = PPComm() 142 | gpascii = comm.gpascii 143 | 144 | devices = list(enumerate_hardware(comm.gpascii)) 145 | 146 | for device in devices: 147 | if hasattr(device, 'phase_frequency'): 148 | print(device, 'phase frequency', device.phase_frequency, 149 | device.servo_clock_div) 150 | 151 | for i, chan in device.channels.items(): 152 | print('\t', chan, 'pwm freq', chan.pwm_frequency) 153 | 154 | phase_master, servo_master = get_clock_master(devices) 155 | print('master is', phase_master, servo_master) 156 | print('current servo period is', gpascii.get_variable('Sys.ServoPeriod')) 157 | print('current phase over servo period is', 158 | gpascii.get_variable('Sys.PhaseOverServoPeriod')) 159 | 160 | if 1: 161 | set_global_phase(devices, 10000, 1) 162 | else: 163 | set_global_phase(devices, phase_master.phase_frequency, 7) 164 | 165 | 166 | if __name__ == '__main__': 167 | test() 168 | -------------------------------------------------------------------------------- /ppmac/completer.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | :mod:`ppmac.completer` -- Ppmac Completer 4 | ========================================= 5 | 6 | .. module:: ppmac.completer 7 | :synopsis: Allows for Python introspection into Power PMAC variables. 8 | .. moduleauthor:: Ken Lauer 9 | """ 10 | # this whole module should be redone if I ever get a chance... 11 | 12 | from __future__ import print_function 13 | import os 14 | import re 15 | import sys 16 | import subprocess 17 | 18 | import sqlite3 as sqlite 19 | 20 | MODULE_PATH = os.path.dirname(os.path.abspath(__file__)) 21 | 22 | 23 | def get_index(name): 24 | m = re.search('\[(\d+)\]', name) 25 | if m: 26 | return int(m.groups()[0]) 27 | return None 28 | 29 | 30 | def remove_indices_and_brackets(name): 31 | return re.sub('(\[\d+\]?)', '', name) 32 | 33 | 34 | def remove_indices(name): 35 | return re.sub('(\[\d+\])', '[]', name) 36 | 37 | 38 | def fix_name(name): 39 | return name.replace('[]', '') 40 | 41 | 42 | def check_alias(c, name): 43 | c.execute('select Alias from software_tbl0 where Command=? collate nocase', (name, )) 44 | try: 45 | row = c.fetchone() 46 | alias = row['Alias'] 47 | if alias is not None: 48 | return alias 49 | except: 50 | pass 51 | 52 | return name 53 | 54 | 55 | class PPCompleterNode(object): 56 | def __init__(self, conn, parent, row, index=None, gpascii=None): 57 | self.gpascii = gpascii 58 | self.conn = conn 59 | self.row = row 60 | self.index = index 61 | 62 | self._name = row['Command'] 63 | self.parent = parent 64 | self._cache = {} 65 | 66 | c = conn.cursor() 67 | 68 | top = self.full_name.split('.')[0] 69 | top = check_alias(c, top) 70 | if not self.parent: 71 | cid = top 72 | else: 73 | cid = self._name 74 | 75 | cid = remove_indices(cid) 76 | c.execute('select * from software_tbl1 where CommandID=?', (cid, )) 77 | self.info = dict((fix_name(item['Command']), item) for item in c.fetchall()) 78 | 79 | #print('gatechan', top, 'cid', self._db_name) 80 | c.execute('select * from software_tbl2 where GateChan=? and CommandID=?', (top, self._db_name)) 81 | gate_items = dict((fix_name(item['Command']), item) for item in c.fetchall()) 82 | self.info.update(gate_items) 83 | 84 | if not gate_items: 85 | c.execute('select * from software_tbl2 where CommandID=?', (cid, )) 86 | table2_items = dict((fix_name(item['Command']), item) for item in c.fetchall()) 87 | self.info.update(table2_items) 88 | 89 | self._lower_case = dict((name.lower(), name) for name in self.info.keys()) 90 | self._set_docstring() 91 | 92 | def search(self, text, search_row=True, case_insensitive=True): 93 | """ 94 | Search keys and optionally all rows for `text` 95 | Returns dictionary of {key: rows} 96 | """ 97 | ret = {} 98 | if case_insensitive: 99 | text = text.lower() 100 | 101 | for key, info in self.info.items(): 102 | match = False 103 | if text in key: 104 | match = True 105 | elif case_insensitive and text in key.lower(): 106 | match = True 107 | elif search_row: 108 | s = str(info) 109 | if case_insensitive: 110 | s = s.lower() 111 | match = (text in s) 112 | 113 | if match: 114 | ret[key] = self.info[key] 115 | 116 | return ret 117 | 118 | def _set_docstring(self): 119 | info_keys = ['Comments', 'AddedComments', 'TypeInfo', 120 | 'RangeInfo', 'Units', 'DefaultInfo', 121 | 'UserLevel', 'Category'] 122 | 123 | doc = [] 124 | for key in info_keys: 125 | if key in self.row: 126 | value = self.row[key] 127 | if value is None: 128 | continue 129 | 130 | if key == 'AddedComments': 131 | key = 'Comments' 132 | 133 | doc.append((key, value)) 134 | 135 | def fix_desc(s): 136 | return str(s).replace('NULL', 'None') 137 | 138 | doc = ['%s: %s' % (name, fix_desc(desc)) for name, desc in doc] 139 | self.__doc__ = '\n'.join(doc) 140 | 141 | def __dir__(self): 142 | return self.info.keys() 143 | 144 | def _get_node(self, row): 145 | full_name = row['Command'] 146 | try: 147 | return self._cache[full_name] 148 | except KeyError: 149 | if full_name.endswith('[]'): 150 | node = PPCompleterList(self.conn, self.full_name, row, 151 | gpascii=self.gpascii) 152 | else: 153 | node = PPCompleterNode(self.conn, self.full_name, row, 154 | gpascii=self.gpascii) 155 | 156 | self._cache[full_name] = node 157 | return node 158 | 159 | def __getattr__(self, key): 160 | try: 161 | key = key.lower() 162 | if key in self._lower_case: 163 | key = self._lower_case[key] 164 | 165 | return self._get_node(self.info[key]) 166 | except KeyError: 167 | raise AttributeError('%s.%s' % (str(self), key)) 168 | 169 | @property 170 | def name(self): 171 | if self.index is not None: 172 | return '%s[%d]' % (self._name[:-2], self.index) 173 | else: 174 | return self._name 175 | 176 | @property 177 | def full_name(self): 178 | if self.parent: 179 | return '%s.%s' % (self.parent, self.name) 180 | else: 181 | return self.name 182 | 183 | @property 184 | def address(self): 185 | return '%s.a' % (self.full_name, ) 186 | 187 | @property 188 | def _db_name(self): 189 | return remove_indices(self._name) 190 | 191 | @property 192 | def _db_full_name(self): 193 | return remove_indices(self.full_name) 194 | 195 | def __str__(self): 196 | return self.full_name 197 | 198 | @property 199 | def value(self): 200 | if self.gpascii is not None: 201 | return self.gpascii.get_variable(self.full_name) 202 | else: 203 | return None 204 | 205 | __repr__ = __str__ 206 | 207 | 208 | class PPCompleterList(object): 209 | def __init__(self, conn, parent, row, gpascii=None): 210 | self.gpascii = gpascii 211 | self.conn = conn 212 | self.row = row 213 | self.name = row['Command'] 214 | self.parent = parent 215 | self.item0 = PPCompleterNode(self.conn, self.parent, self.row, index=0, 216 | gpascii=gpascii) 217 | self.items = {0: self.item0} 218 | 219 | @property 220 | def full_name(self): 221 | if self.parent: 222 | return '%s.%s' % (self.parent, self.name) 223 | else: 224 | return self.name 225 | 226 | def __getitem__(self, idx): 227 | try: 228 | return self.items[idx] 229 | except KeyError: 230 | node = PPCompleterNode(self.conn, self.parent, self.row, index=idx, 231 | gpascii=self.gpascii) 232 | self.items[idx] = node 233 | return node 234 | 235 | def search(self, *args, **kwargs): 236 | return self.item0.search(*args, **kwargs) 237 | 238 | def __getattr__(self, key): 239 | if hasattr(self.item0, key): 240 | return getattr(self.item0, key) 241 | raise AttributeError(key) 242 | 243 | def __dir__(self): 244 | return dir(self.item0) 245 | 246 | def __str__(self): 247 | return self.full_name 248 | 249 | __repr__ = __str__ 250 | 251 | 252 | class PPCompleter(object): 253 | def __init__(self, conn, gpascii=None): 254 | self.conn = conn 255 | self.gpascii = gpascii 256 | 257 | tbl0 = conn.cursor() 258 | tbl0.execute('select * from software_tbl0') 259 | rows = tbl0.fetchall() 260 | self.top_level = dict((fix_name(item['Command']), item) for item in rows) 261 | self._lower_case = dict((name.lower(), name) for name in self.top_level.keys()) 262 | self._cache = {} 263 | 264 | def __dir__(self): 265 | return self.top_level.keys() 266 | 267 | def _get_node(self, row): 268 | full_name = row['Command'] 269 | try: 270 | return self._cache[full_name] 271 | except KeyError: 272 | if full_name.endswith('[]'): 273 | node = PPCompleterList(self.conn, '', row, gpascii=self.gpascii) 274 | else: 275 | node = PPCompleterNode(self.conn, '', row, gpascii=self.gpascii) 276 | 277 | self._cache[full_name] = node 278 | return node 279 | 280 | def __getattr__(self, name): 281 | name = name.lower() 282 | if name in self._lower_case: 283 | name = self._lower_case[name] 284 | 285 | return self._get_node(self.top_level[name]) 286 | 287 | raise AttributeError(name) 288 | 289 | def check(self, addr): 290 | """ 291 | Check a PPMAC variable and fix its case, if necessary 292 | Returns the variable with proper case or raises AttributeError 293 | on failure. 294 | """ 295 | #print('-- check', addr) 296 | addr = addr.split('.') 297 | obj = self 298 | for i, entry in enumerate(addr): 299 | index = get_index(entry) 300 | 301 | entry = remove_indices(entry) 302 | if entry.endswith('[]'): 303 | entry = entry[:-2] 304 | 305 | try: 306 | obj = getattr(obj, entry) 307 | except AttributeError as ex: 308 | if i > 0: 309 | raise AttributeError('%s does not exist in %s' % 310 | (entry, '.'.join(addr[:i])) 311 | ) 312 | else: 313 | raise AttributeError('%s does not exist' % (entry)) 314 | 315 | if index is not None and not isinstance(obj, PPCompleterList): 316 | raise AttributeError('%s is not a list' % (entry)) 317 | elif index is None and isinstance(obj, PPCompleterList): 318 | raise AttributeError('%s is a list' % (entry)) 319 | 320 | if index is not None: 321 | obj = obj[index] 322 | 323 | name = obj.name 324 | addr[i] = name 325 | 326 | return obj 327 | 328 | 329 | def dict_factory(cursor, row): 330 | d = {} 331 | for idx, col in enumerate(cursor.description): 332 | d[col[0]] = row[idx] 333 | return d 334 | 335 | 336 | def start_completer_from_db(dbfile=':memory:', gpascii=None): 337 | conn = sqlite.connect(dbfile) 338 | conn.row_factory = dict_factory 339 | return PPCompleter(conn, gpascii=gpascii) 340 | 341 | 342 | def start_completer_from_sql_script(script, db_file, gpascii=None): 343 | conn = sqlite.connect(db_file) 344 | conn.row_factory = dict_factory 345 | 346 | c = conn.cursor() 347 | c.executescript(script) 348 | conn.commit() 349 | return PPCompleter(conn, gpascii=gpascii) 350 | 351 | 352 | def start_completer_from_sql_file(sql_file='ppmac.sql', db_file=':memory:'): 353 | with open(sql_file, 'rt') as f: 354 | sql = f.read() 355 | return start_completer_from_sql_script(sql, db_file) 356 | 357 | 358 | def start_completer_from_mysql(mysql_host, ppmac_ip, mysql_user='root', 359 | script='mysql2sqlite.sh', db_file=':memory:', 360 | gpascii=None): 361 | """ 362 | database is 'ppmac' + ip address with dots as underscores: 363 | so 10.0.0.98 -> ppmac10_0_0_98 364 | 365 | on windows machine: 366 | mysql -u root 367 | GRANT ALL PRIVILEGES ON ppmac10_0_0_98.* TO 'root'@'%'; 368 | edit C:\Program Files\MySQL\MySQL Server 5.0 369 | from: 370 | host = localhost 371 | to: 372 | host = 10.0.0.6 <-- windows machine ip accessible by this script 373 | 374 | net stop mysql 375 | net start mysql 376 | 377 | then finally: 378 | 379 | sh ./mysql2sqlite.sh -h 10.0.0.6 -u root ppmac10_0_0_98 > temp.sql 380 | iconv -f latin1 -t utf-8 temp.sql > ppmac.sql 381 | """ 382 | dbname = 'ppmac%s' % (ppmac_ip.replace('.', '_')) 383 | 384 | script = os.path.join(MODULE_PATH, script) 385 | cmd = 'sh %(script)s -h %(mysql_host)s -u %(mysql_user)s %(dbname)s | iconv -f latin1 -t utf-8' 386 | cmd = cmd % locals() 387 | 388 | print('Executing', cmd) 389 | try: 390 | sqlite_sql = subprocess.check_output(cmd, shell=True) 391 | except subprocess.CalledProcessError as ex: 392 | print('Failed (ret=%d): %s' % (ex.returncode, ex.output)) 393 | return None 394 | 395 | return start_completer_from_sql_script(sqlite_sql, db_file, gpascii=gpascii) 396 | 397 | 398 | def main(ppmac_ip='10.0.0.98', windows_ip='10.0.0.6'): 399 | db_file = os.path.join(MODULE_PATH, 'ppmac.db') 400 | c = None 401 | if os.path.exists(db_file): 402 | try: 403 | c = start_completer_from_db(db_file) 404 | except Exception as ex: 405 | print('Unable to load current db file: %s (%s) %s' % 406 | (db_file, ex.__class__.__name__, ex)) 407 | 408 | if c is None: 409 | if os.path.exists(db_file): 410 | os.unlink(db_file) 411 | c = start_completer_from_mysql(windows_ip, ppmac_ip, db_file=db_file, 412 | gpascii=None) 413 | 414 | if c is None: 415 | sys.exit(1) 416 | 417 | #print(dir(c)) 418 | print(c.Sys) 419 | print(c.Gate3) 420 | print(c.Gate3[0]) 421 | print(c.Gate3[0].Chan[0]) 422 | print(c.Gate3[0].Chan[0].ABC) 423 | print(c.Acc24E3[0]) 424 | print(c.Acc24E3[0].Chan[0]) 425 | print() 426 | print('docstring:') 427 | print(c.Gate3[0].Chan[0].ABC.__doc__) 428 | print('check', c.check('Gate3[0].Chan[0].ABC')) 429 | print('check', c.check('Sys')) 430 | print('check', c.check('Gate3[0]')) 431 | print('check', c.check('acc24e3[0].chan[0]')) 432 | print(c.Motor[3]) 433 | print('check', c.check('motor[3].pos')) 434 | 435 | try: 436 | print(c.check('Acc24E3[0].Chan[0].blah')) 437 | except AttributeError as ex: 438 | print('ok -', ex) 439 | else: 440 | print('fail') 441 | 442 | try: 443 | print(c.check('Acc24E3.Chan.ABC')) 444 | except AttributeError as ex: 445 | print('ok -', ex) 446 | else: 447 | print('fail') 448 | 449 | try: 450 | print(c.check('Acc24E3[0].Chan[0].ABC[0]')) 451 | except AttributeError as ex: 452 | print('ok -', ex) 453 | else: 454 | print('fail') 455 | 456 | c0 = c.acc24e3[0].chan 457 | for key, value in c0.search('4095').items(): 458 | print(key, value) 459 | 460 | c0 = c.acc24e3[0].chan[0] 461 | for key, value in c0.search('4095').items(): 462 | print(key, value.values()) 463 | 464 | print(dir(c.acc24e2s[4].chan[0])) 465 | try: 466 | c.acc24e2s[4].chan[0].pfmwidth 467 | except AttributeError as ex: 468 | print('ok -', ex) 469 | else: 470 | print('fail') 471 | 472 | print(c.check('acc24e2s[4].chan[0]')) 473 | 474 | 475 | if __name__ == '__main__': 476 | main() 477 | -------------------------------------------------------------------------------- /ppmac/config.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | import os 3 | import logging 4 | 5 | 6 | logger = logging.getLogger(__name__) 7 | 8 | 9 | hostname = os.environ.get('PPMAC_HOST', '10.3.2.115') 10 | port = int(os.environ.get('PPMAC_PORT', '22')) 11 | username = os.environ.get('PPMAC_USER', 'root') 12 | password = os.environ.get('PPMAC_PASS', 'deltatau') 13 | 14 | fast_gather_port = int(os.environ.get('PPMAC_GATHER_PORT', '2332')) 15 | 16 | logger.debug('Power PMAC default host: %s:%d', hostname, port) 17 | logger.debug('Power PMAC default login: %s/%s', username, password) 18 | logger.debug('Power PMAC default fast gather port: %d', fast_gather_port) 19 | -------------------------------------------------------------------------------- /ppmac/const.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | :mod:`ppmac.const` -- Ppmac-related constants 4 | ======================================== 5 | 6 | .. module:: ppmac.const 7 | .. moduleauthor:: Ken Lauer 8 | """ 9 | 10 | coord_errors = { 11 | 1 : ('SyncBufferError', 'Error in synchronous variable assignment buffer'), 12 | 2 : ('BufferError Error', 'in motion program buffer'), 13 | 3 : ('CCMoveTypeError', 'Illegal move mode or command while cutter compensation active (rapid, pvt, spline, lin-to-pvt, new normal, pmatch, pclr, pset, pload)'), 14 | 4 : ('LinToPvtError', 'Error in automatic linear-to-PVT-mode conversion'), 15 | 5 : ('CCLeadOutMoveError', 'Illegal cutter compensation lead-out move (circle mode or length less than cutter radius)'), 16 | 6 : ('CCLeadInMoveError', 'Illegal cutter compensation lead-in move (circle mode or length less than cutter radius)'), 17 | 7 : ('CCBufSizeError', 'Insufficient size for cutter compensation move buffer (not enough to find next in-plane move)'), 18 | 8 : ('PvtError', 'Illegally specified PVT-mode move'), 19 | 9 : ('CCFeedRateError', 'Moves not specified by feedrate for cutter comp'), 20 | 10 : ('CCDirChangeError', 'Compensated move in opposite direction from programmed move; indicates interference condition'), 21 | 11 : ('CCNoSolutionError', 'No solution could be found for compensated move'), 22 | 12 : ('CC3NdotTError', '3D cutter compensation vector calculation error ("NdotT" value less than 1st entry in tool-tip table)'), 23 | 13 : ('CCDistanceError', 'Could not resolve overcuts by removing moves'), 24 | 14 : ('CCNoIntersectError', 'Could not find intersection of compensated paths'), 25 | 15 : ('CCNoMovesError', 'No compensated moves between lead-in and lead-out moves'), 26 | 16 : ('RunTimeError', 'Insufficient move calculation time'), 27 | 17 : ('InPosTimeOutError', 'Exceeded specified time limit for in-position'), 28 | 18 : ('LHNumMotorsError', 'Mismatch between # of motors used and # in lookahead buffer'), 29 | 32 : ('SoftLimitError', 'Stopped from exceeding software position limit'), 30 | 64 : ('RadiusErrorbit_0', 'Radius error in X/Y/Z-space circle move'), 31 | 128 : ('RadiusErrorbit_1', 'Radius error in XX/YY/ZZ-space circle move'), 32 | 33 | # 19-31 Reserved for future use 34 | # 33-63 Reserved for future use 35 | # 65-127 Reserved for future use 36 | } 37 | 38 | 39 | motor_status = ['ServoCtrl', 40 | 'TriggerMove', 41 | 'HomeInProgress', 42 | 'MinusLimit', 43 | 'PlusLimit', 44 | 'FeWarn', 45 | 'FeFatal', 46 | 'LimitStop', 47 | 'AmpFault', 48 | 'SoftMinusLimit', 49 | 'SoftPlusLimit', 50 | 'I2tFault', 51 | 'TriggerNotFound', 52 | 'AmpWarn', 53 | 'HomeComplete', 54 | 55 | 'DesVelZero', 56 | 'ClosedLoop', 57 | 'AmpEna', 58 | 'InPos', 59 | 'BlockRequest', 60 | 'PhaseFound', 61 | 'TriggerSpeedSel', 62 | 'GantryHomed', 63 | 'SpindleMotor', 64 | 'Csolve', 65 | 'SoftLimit', 66 | 'DacLimit', 67 | 'BlDir', 68 | 'EncLoss', 69 | ] 70 | 71 | motor_normal = {'ServoCtrl': 1, 72 | 'TriggerMove': 0, 73 | 'HomeInProgress': 0, 74 | 'MinusLimit': 0, 75 | 'PlusLimit': 0, 76 | 'FeWarn': 0, 77 | 'FeFatal': 0, 78 | 'LimitStop': 0, 79 | 'AmpFault': 0, 80 | 'SoftMinusLimit': 0, 81 | 'SoftPlusLimit': 0, 82 | 'I2tFault': 0, 83 | 'TriggerNotFound': 0, 84 | 'AmpWarn': 0, 85 | 'HomeComplete': 1, 86 | 87 | 'DesVelZero': 1, 88 | 'ClosedLoop': 0, 89 | 'AmpEna': 0, 90 | 'InPos': 1, 91 | 'BlockRequest': 0, 92 | 'PhaseFound': 0, 93 | 'TriggerSpeedSel': 0, 94 | 'GantryHomed': 0, 95 | 'SpindleMotor': 0, 96 | 'Csolve': 1, 97 | 'SoftLimit': 0, 98 | 'DacLimit': 0, 99 | 'EncLoss': 0, 100 | } 101 | 102 | coord_status = ['TriggerMove', 103 | 'HomeInProgress', 104 | 'MinusLimit', 105 | 'PlusLimit', 106 | 'FeWarn', 107 | 'FeFatal', 108 | 'LimitStop', 109 | 'AmpFault', 110 | 'SoftMinusLimit', 111 | 'SoftPlusLimit', 112 | 'I2tFault', 113 | 'TriggerNotFound', 114 | 'AmpWarn', 115 | 'EncLoss', 116 | 'TimerEnabled', 117 | 'HomeComplete', 118 | 'DesVelZero', 119 | 'ClosedLoop', 120 | 'AmpEna', 121 | 'InPos', 122 | 'BlockRequest', 123 | 'TimersEnabled', 124 | 'ErrorStatus', 125 | 'Csolve', 126 | 'LinToPvtBuf', 127 | 'FeedHold', 128 | 'BlockActive', 129 | 'ContMotion', 130 | 'CCMode', 131 | 'MoveMode', 132 | 'SegMove', 133 | 'SegMoveAccel', 134 | 'SegMoveDecel', 135 | 'SegEnabled', 136 | 'SegStopReq', 137 | 'LookAheadWrap', 138 | 'LookAheadLookBack', 139 | 'LookAheadDir', 140 | 'LookAheadStop', 141 | 'LookAheadChange', 142 | 'LookAheadReCalc', 143 | 'LookAheadFlush', 144 | 'LookAheadActive', 145 | 'CCAddedArc', 146 | 'CCOffReq', 147 | 'CCMoveType', 148 | 'CC3Active', 149 | 'SharpCornerStop', 150 | 'AddedDwellDis', 151 | 'ProgRunning', 152 | 'ProgActive', 153 | 'ProgProceeding', 154 | ] 155 | 156 | 157 | coord_normal = {'TriggerMove': 0, 158 | 'HomeInProgress': 0, 159 | 'MinusLimit': 0, 160 | 'PlusLimit': 0, 161 | 'FeWarn': 0, 162 | 'FeFatal': 0, 163 | 'LimitStop': 0, 164 | 'AmpFault': 0, 165 | 'SoftMinusLimit': 0, 166 | 'SoftPlusLimit': 0, 167 | 'I2tFault': 0, 168 | 'TriggerNotFound': 0, 169 | 'AmpWarn': 0, 170 | 'EncLoss': 0, 171 | 'TimerEnabled': 0, 172 | 'HomeComplete': 1, 173 | 'DesVelZero': 1, 174 | 'ClosedLoop': 1, 175 | 'AmpEna': 1, 176 | 'InPos': 1, 177 | 'BlockRequest': 1, 178 | 'TimersEnabled': 1, 179 | 'ErrorStatus': 0, 180 | 'Csolve': 1, 181 | 'LinToPvtBuf': 0, 182 | 'FeedHold': 0, 183 | 'BlockActive': 0, 184 | 'ContMotion': 0, 185 | 'CCMode': 0, 186 | 'MoveMode': 0, 187 | 'SegMove': 0, 188 | 'SegMoveAccel': 0, 189 | 'SegMoveDecel': 0, 190 | 'SegEnabled': 0, 191 | 'SegStopReq': 0, 192 | 'LookAheadWrap': 0, 193 | 'LookAheadLookBack': 0, 194 | 'LookAheadDir': 0, 195 | 'LookAheadStop': 0, 196 | 'LookAheadChange': 0, 197 | 'LookAheadReCalc': 0, 198 | 'LookAheadFlush': 0, 199 | 'LookAheadActive': 0, 200 | 'CCAddedArc': 0, 201 | 'CCOffReq': 0, 202 | 'CCMoveType': 0, 203 | 'CC3Active': 0, 204 | 'SharpCornerStop': 0, 205 | 'AddedDwellDis': 1, 206 | 'ProgRunning': 0, 207 | 'ProgActive': 0, 208 | 'ProgProceeding': 0, 209 | } 210 | 211 | 212 | # Part numbers to string identifiers, compiled from the documentation 213 | parts = { 214 | # Gate 3 215 | 604002: 'ACC24E3', 216 | 604017: 'ACC5E3', 217 | 604027: 'ACC59E3', 218 | 604030: 'PowerBrick', 219 | 220 | # Gate 2 221 | 603437: 'ACC5E', 222 | 223 | # Gate 1 224 | 603441: 'ACC24E2S', 225 | 226 | # Gate IO 227 | 603404: 'ACC28E', 228 | 603603: 'ACC11C', 229 | 603474: 'ACC14E', 230 | 603575: 'ACC65E', 231 | 603576: 'ACC66E', 232 | 603577: 'ACC67E', 233 | 603595: 'ACC68E', 234 | } 235 | 236 | 237 | part_types = {0: 'Servo', 238 | 1: 'MACRO', 239 | 2: 'Analog I/O', 240 | 3: 'Digital I/O', 241 | } 242 | 243 | PWM_FREQ_HZ = 117964800.0 244 | PWM_FREQ_KHZ = PWM_FREQ_HZ / 1000.0 245 | MAX_SERVO_DIVIDER = 16 246 | MAX_PWM_DIVIDER = 8 247 | -------------------------------------------------------------------------------- /ppmac/fast_gather.py: -------------------------------------------------------------------------------- 1 | """ 2 | :mod:`ppmac.fast_gather` -- fast_gather client 3 | ========================================== 4 | 5 | .. module:: fast_gather 6 | :synopsis: GatherClient connects to a TCP server running on the Power PMAC 7 | called fast_gather. It requests the type information and the raw gather 8 | data from the server. Conversion to native Python types is then done. 9 | 10 | Note that prior information about the gather addresses may be required 11 | to understand the gathered data. This is because the addresses listed in 12 | Gather.Addr[] or Gather.PhaseAddr[] are numeric and not descriptive. 13 | 14 | Overall, this is significantly faster than working with the Power PMAC 15 | gather program which dumps out tab-delimited strings to a file. 16 | .. moduleauthor:: K Lauer 17 | 18 | """ 19 | 20 | from __future__ import print_function 21 | import socket 22 | import struct 23 | import time 24 | import numpy as np 25 | 26 | from . import config 27 | from .gather_types import GATHER_TYPES 28 | 29 | 30 | class TCPSocket(object): 31 | def __init__(self, sock=None, host_port=None): 32 | if sock is None: 33 | self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 34 | else: 35 | self.sock = sock 36 | 37 | if host_port is not None: 38 | self.connect(host_port) 39 | 40 | def __del__(self): 41 | if self.sock is not None: 42 | try: 43 | self.sock.close() 44 | except Exception: 45 | pass 46 | 47 | self.sock = None 48 | 49 | def send(self, packet): 50 | """ 51 | Send the full packet, ensuring delivery 52 | """ 53 | total = 0 54 | while total < len(packet): 55 | sent = self.sock.send(packet[total:]) 56 | if sent == 0: 57 | raise RuntimeError("Connection lost") 58 | 59 | total += sent 60 | 61 | def recv_fixed(self, expected): 62 | """ 63 | Receive a fixed-length packet, of length `expected` 64 | """ 65 | packet = [] 66 | received = 0 67 | while received < expected: 68 | chunk = self.sock.recv(expected - len(packet)) 69 | if len(chunk) == 0: 70 | raise RuntimeError("Connection lost") 71 | 72 | received += len(chunk) 73 | packet.append(chunk) 74 | 75 | return b''.join(packet) 76 | 77 | def __getattr__(self, s): 78 | # can't subclass socket.socket, so here's the next best thing 79 | return getattr(self.sock, s) 80 | 81 | 82 | class GatherError(Exception): 83 | pass 84 | 85 | 86 | class GatherClient(TCPSocket): 87 | """ 88 | Power PMAC fast_gather client 89 | """ 90 | START_MASK = 0xF800 91 | BIT_MASK = 0x07FF 92 | 93 | def _recv_packet(self, expected_code): 94 | """ 95 | Receive a packet, with an expected code 96 | 97 | For example, the data code 'D': 98 | (packet length, uint32) D (packet) 99 | 100 | Raises RuntimeError upon receiving an unexpected code or disconnection 101 | Raises GatherError upon receiving an error code from the server 102 | """ 103 | packet_len = self.recv(4) 104 | 105 | packet_len, = struct.unpack('>I', packet_len) 106 | packet = self.recv_fixed(packet_len) 107 | code, packet = packet[:1], packet[1:] 108 | 109 | if code == b'E': 110 | error_code, = struct.unpack('>I', packet[:4]) 111 | raise GatherError('Error %d' % error_code) 112 | 113 | elif expected_code == code: 114 | return memoryview(packet) 115 | 116 | else: 117 | raise RuntimeError('Unexpected code %s (expected %s)' % (code, expected_code)) 118 | 119 | def query_types(self): 120 | """ 121 | Get the integral types of the gathered data, one for each address 122 | """ 123 | self.send(b'types\n') 124 | buf = self._recv_packet(b'T') 125 | n_items, = struct.unpack('B', buf[:1]) 126 | types = struct.unpack('>' + 'H' * n_items, buf[1:]) 127 | 128 | assert(n_items == len(types)) 129 | return types 130 | 131 | def query_raw_data(self): 132 | """ 133 | Query the server for all the raw data 134 | 135 | Returns: sample count (lines), and raw data 136 | """ 137 | self.send(b'data\n') 138 | buf = self._recv_packet(b'D') 139 | 140 | samples, = struct.unpack('>I', buf[:4]) 141 | return samples, buf[4:] 142 | 143 | def set_phase_mode(self): 144 | """ 145 | Instruct the server to return gathered phase data 146 | """ 147 | self.send(b'phase\n') 148 | self._recv_packet(b'K') 149 | 150 | def set_servo_mode(self): 151 | """ 152 | Instruct the server to return gathered servo data 153 | """ 154 | self.send(b'servo\n') 155 | self._recv_packet(b'K') 156 | 157 | def query_types_and_raw_data(self): 158 | """ 159 | Query both types and raw data 160 | 161 | Note: due to Nagle's algorithm where small packets are buffered before sending, 162 | requesting the type information separately from the raw data can be significantly 163 | slower. This method is more efficient, requesting both at the same time. 164 | """ 165 | self.send(b'all\n') 166 | type_buf = self._recv_packet(b'T') 167 | n_items, = struct.unpack('B', type_buf[:1]) 168 | types = struct.unpack('>' + 'H' * n_items, type_buf[1:]) 169 | 170 | if n_items == 0: 171 | return types, 0, [] 172 | else: 173 | assert(n_items == len(types)) 174 | data_buf = self._recv_packet(b'D') 175 | samples, = struct.unpack('>I', data_buf[:4]) 176 | return types, samples, data_buf[4:] 177 | 178 | def _get_type(self, type_): 179 | """ 180 | Return type information for a numeric Gather type 181 | 182 | Returns: (length in bytes, 183 | struct.unpack conversion character, 184 | post-processing conversion function) 185 | """ 186 | if type_ in GATHER_TYPES: 187 | return GATHER_TYPES[type_] 188 | 189 | def make_conv_bits(start, count): 190 | mask = ((1 << count) - 1) 191 | 192 | def wrapped(values): 193 | return [((value >> start) & mask) 194 | for value in values] 195 | 196 | wrapped.__name__ = 'conv_bits_%d_to_%d' % (start, start + count) 197 | return wrapped 198 | 199 | # Undocumented types -- a certain number of bits and such 200 | # see gather_serve.c or: 201 | # http://forums.deltatau.com/archive/index.php?thread-933.html 202 | start = (type_ & self.START_MASK) >> 11 203 | count = (type_ & self.BIT_MASK) 204 | count = 32 - (count >> 6) 205 | 206 | ret = (4, 'I', make_conv_bits(start, count)) 207 | GATHER_TYPES[type_] = ret 208 | return ret 209 | 210 | def _parse_raw_data(self, types, raw_data): 211 | """ 212 | Combines type information and raw data into a 1D array of processed 213 | data 214 | 215 | Returns: (processed but flat data, 216 | number of addresses, 217 | number of samples/lines) 218 | """ 219 | n_items = len(types) 220 | 221 | types = [self._get_type(type_) for type_ in types] 222 | 223 | line_size = sum(size for (size, format_, conv) in types) 224 | line_count = int(len(raw_data) / line_size) 225 | 226 | data_format = ''.join(format_ for (size, format_, conv) in types) 227 | struct_ = struct.Struct('>' + data_format * line_count) 228 | 229 | if not isinstance(raw_data, memoryview): 230 | raw_data = memoryview(raw_data) 231 | 232 | data = struct_.unpack(raw_data[:line_size * line_count]) 233 | 234 | ret_data = [] 235 | for i, (size, format_, conv) in enumerate(types): 236 | col = data[i::n_items] 237 | if conv is not None: 238 | ret_data.append(conv(col)) 239 | else: 240 | ret_data.append(col) 241 | 242 | # import pdb; pdb.set_trace() 243 | return ret_data, n_items, line_count 244 | 245 | def _query_all(self): 246 | """ 247 | Queries the server for type and raw data, and does a bit of processing 248 | 249 | Returns: (processed but flat data, 250 | number of addresses, 251 | number of samples/lines) 252 | """ 253 | types, samples, raw_data = self.query_types_and_raw_data() 254 | 255 | if samples == 0: 256 | return [], len(types), 0 257 | 258 | return self._parse_raw_data(types, raw_data) 259 | 260 | def get_columns(self, as_numpy=False): 261 | """ 262 | Query the server for all gather data, and pack it into columns 263 | 264 | Returns: [[addr0[0], addr0[1], ...], 265 | [addr1[0], addr1[1], ...], 266 | ...] 267 | 268 | """ 269 | data, n_items, samples = self._query_all() 270 | 271 | if as_numpy: 272 | return np.asarray(data).reshape(n_items, samples) 273 | else: 274 | return data 275 | 276 | def get_rows(self, as_numpy=False): 277 | """ 278 | Query the server for all gather data, and pack it into rows 279 | 280 | Returns: [[addr0[0], addr1[0], ...], 281 | [addr0[1], addr1[1], ...], 282 | ...] 283 | """ 284 | if as_numpy: 285 | return self.get_rows(as_numpy=as_numpy).T 286 | else: 287 | data, n_items, samples = self._query_all() 288 | 289 | return list(zip(*data)) 290 | 291 | 292 | def test(host=config.hostname, port=config.fast_gather_port): 293 | port = int(port) 294 | 295 | s = GatherClient() 296 | s.connect((host, port)) 297 | 298 | t0 = time.time() 299 | s.set_servo_mode() 300 | # s.set_phase_mode() 301 | cols = s.get_columns(as_numpy=False) 302 | t1 = time.time() - t0 303 | 304 | print('gather elapsed %.2fms' % (t1 * 1000)) 305 | 306 | if cols: 307 | import numpy as np 308 | import matplotlib.pyplot as plt 309 | x_axis = np.array(cols[0]) 310 | x_axis -= x_axis[0] 311 | 312 | for i, col in enumerate(cols[1:]): 313 | col = np.array(col) 314 | col -= np.min(col) 315 | plt.plot(x_axis, col, label='Addr %d' % (i + 1)) 316 | plt.legend(loc='best') 317 | plt.show() 318 | 319 | 320 | if __name__ == '__main__': 321 | import sys 322 | # Simple test usage: gather_client.py [ip] [port] 323 | # Assumes gathered data exists, with addr[0] being time (or 324 | # the shared x-axis at least), and addr[1:] being some other data 325 | if len(sys.argv) > 1: 326 | test(*sys.argv[1:]) 327 | else: 328 | test() 329 | -------------------------------------------------------------------------------- /ppmac/gather.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | :mod:`ppmac.gather` -- Ppmac Gather Utilities 4 | ============================================= 5 | 6 | .. module:: ppmac.gather 7 | :synopsis: Power PMAC gather utility functions 8 | .. moduleauthor:: Ken Lauer 9 | """ 10 | 11 | from __future__ import print_function 12 | import os 13 | import time 14 | import re 15 | import sys 16 | import ast 17 | import struct 18 | import functools 19 | import logging 20 | 21 | import matplotlib.pyplot as plt 22 | import numpy as np 23 | 24 | from . import pp_comm 25 | from .pp_comm import vlog 26 | from .util import InsList 27 | 28 | 29 | logger = logging.getLogger(__name__) 30 | 31 | max_samples = 0x7FFFFFFF 32 | gather_config_file = '/var/ftp/gather/GatherSetting.txt' 33 | gather_output_file = '/var/ftp/gather/GatherFile.txt' 34 | 35 | 36 | def get_sample_count(servo_period, gather_period, duration): 37 | """ 38 | For a specified servo and gather period, return the number 39 | of samples required to get a specific duration. 40 | """ 41 | return int(duration / (servo_period * gather_period)) 42 | 43 | 44 | def get_duration(servo_period, gather_period, samples): 45 | """ 46 | For a specified servo and gather period, return the duration 47 | in seconds of how long the gather would run 48 | """ 49 | return int(samples) * (servo_period * gather_period) 50 | 51 | 52 | def get_settings(servo_period, addresses=[], gather_period=1, duration=2.0, 53 | samples=None): 54 | 55 | if samples is not None: 56 | duration = get_duration(servo_period, gather_period, samples) 57 | else: 58 | samples = get_sample_count(servo_period, gather_period, duration) 59 | 60 | yield 'gather.enable=0' 61 | for i, addr in enumerate(addresses): 62 | yield 'gather.addr[%d]=%s' % (i, addr) 63 | 64 | yield 'gather.items=%d' % len(addresses) 65 | yield 'gather.Period=%d' % gather_period 66 | yield 'gather.enable=1' 67 | yield 'gather.enable=0' 68 | yield 'gather.MaxSamples=%d' % samples 69 | 70 | 71 | def read_settings_file(comm, fn=None): 72 | def get_index(name): 73 | m = re.search('\[(\d+)\]', name) 74 | if m: 75 | return int(m.groups()[0]) 76 | return None 77 | 78 | def remove_indices_and_brackets(name): 79 | return re.sub('(\[\d+\]?)', '', name) 80 | 81 | if fn is None: 82 | fn = gather_config_file 83 | 84 | lines = comm.read_file(fn) 85 | settings = {} 86 | for line in lines: 87 | line = line.strip() 88 | lower_line = line.lower() 89 | if lower_line.startswith('gather') and '=' in lower_line: 90 | var, _, value = line.partition('=') 91 | var = var.lower() 92 | if '[' in var: 93 | base = remove_indices_and_brackets(var) 94 | index = get_index(var) 95 | if index is None: 96 | settings[var] = value 97 | else: 98 | if base not in settings: 99 | settings[base] = {} 100 | settings[base][index] = value 101 | else: 102 | settings[var] = value 103 | 104 | if 'gather.addr' in settings: 105 | addr_dict = settings['gather.addr'] 106 | # addresses comes in as a dictionary of {index: value} 107 | max_addr = max(addr_dict.keys()) 108 | addr_list = InsList(['']) * (max_addr + 1) 109 | for index, value in addr_dict.items(): 110 | addr_list[index] = value 111 | 112 | settings['gather.addr'] = addr_list 113 | 114 | return settings 115 | 116 | 117 | def parse_gather(addresses, lines, delim=' '): 118 | def fix_line(line): 119 | try: 120 | return [ast.literal_eval(num) for num in line] 121 | except Exception as ex: 122 | raise RuntimeError('Unable to parse gather results (%s): %s [%s]' % 123 | (ex.__class__.__name__, ex, line)) 124 | 125 | count = len(addresses) 126 | data = [fix_line(line.split(delim)) 127 | for line in lines 128 | if line.count(delim) == (count - 1)] 129 | 130 | if len(data) == 0 and len(lines) > 2: 131 | raise RuntimeError('Gather results inconsistent with settings file' 132 | '(wrong file or addresses incorrect?)') 133 | 134 | return data 135 | 136 | 137 | def setup_gather(gpascii, addresses, duration=0.1, period=1, 138 | output_file=gather_output_file): 139 | comm = gpascii._comm 140 | 141 | servo_period = gpascii.servo_period 142 | 143 | total_samples = get_sample_count(servo_period, period, duration) 144 | 145 | settings = get_settings(servo_period, addresses, duration=duration, 146 | gather_period=period) 147 | 148 | settings = '\n'.join(settings) 149 | 150 | comm.write_file(gather_config_file, settings) 151 | 152 | logger.debug('Wrote configuration to: %s', gather_config_file) 153 | 154 | comm.gpascii_file(gather_config_file) 155 | 156 | max_lines = gpascii.get_variable('gather.maxlines', type_=int) 157 | if max_lines < total_samples: 158 | total_samples = max_lines 159 | duration = get_duration(servo_period, period, total_samples) 160 | gpascii.set_variable('gather.maxsamples', total_samples) 161 | 162 | logger.warning('* Warning: Buffer not large enough.') 163 | logger.warning(' Maximum count with the current addresses: %d', 164 | max_lines) 165 | logger.warning(' New duration is: %.2f s', duration) 166 | 167 | return total_samples 168 | 169 | 170 | def gather(gpascii, addresses, duration=0.1, period=1, 171 | output_file=gather_output_file, verbose=True, f=sys.stdout): 172 | 173 | total_samples = setup_gather(gpascii, addresses, duration=duration, 174 | period=period, output_file=output_file) 175 | 176 | gpascii.set_variable('gather.enable', 2) 177 | samples = 0 178 | 179 | logger.info('Waiting for %d samples', total_samples) 180 | try: 181 | while samples < total_samples: 182 | samples = gpascii.get_variable('gather.samples', type_=int) 183 | if total_samples != 0 and verbose: 184 | percent = 100. * (float(samples) / total_samples) 185 | print('%-6d/%-6d (%.2f%%)' % (samples, total_samples, 186 | percent), 187 | end='\r', file=f) 188 | f.flush() 189 | time.sleep(0.1) 190 | except KeyboardInterrupt: 191 | pass 192 | finally: 193 | print(file=f) 194 | 195 | gpascii.set_variable('gather.enable', 0) 196 | 197 | comm = gpascii._comm 198 | return get_gather_results(comm, addresses, output_file) 199 | 200 | 201 | def get_columns(all_columns, data, *to_get): 202 | if data is None or len(data) == 0: 203 | return [np.zeros(1) for col in to_get] 204 | 205 | if isinstance(data, list): 206 | data = np.array(data) 207 | 208 | all_columns = InsList(all_columns) 209 | indices = [get_addr_index(all_columns, col) 210 | for col in to_get] 211 | return [data[:, idx] for idx in indices] 212 | 213 | 214 | INTERP_MAGIC = (ord('I') << 16) + (ord('N') << 8) + ord('T') 215 | 216 | 217 | def save_interp(fn, addresses, data, col, 218 | point_time=1000, format_='I'): 219 | """ 220 | Save gather data to a simple binary file, interpolated over 221 | a regularly spaced interval (defined by point_time usec) 222 | 223 | Saves big endian, 32-bit unsigned integers (by default) 224 | """ 225 | x, y = get_columns(addresses, data, 226 | 'sys.servocount.a', col) 227 | 228 | point_time = int(point_time) 229 | start_t = x[0] 230 | end_t = x[-1] 231 | step_t = 1e-6 * point_time 232 | new_x = np.arange(start_t, end_t, step_t) 233 | 234 | y = np.interp(new_x, x, y) 235 | 236 | # Store as big endian 237 | format_ = '>%s' % format_ 238 | 239 | with open(fn, 'wb') as f: 240 | f.write(struct.pack(format_, INTERP_MAGIC)) 241 | f.write(struct.pack(format_, len(y))) 242 | f.write(struct.pack(format_, point_time)) 243 | y.astype(format_).tofile(f) 244 | 245 | 246 | def load_interp(fn, format_='I'): 247 | """ 248 | Load gather data from an interpolated binary file (see save_interp) 249 | """ 250 | 251 | header_st = struct.Struct('>III') 252 | with open(fn, 'rb') as f: 253 | header = f.read(header_st.size) 254 | magic, points, point_time = header_st.unpack(header) 255 | 256 | if magic != INTERP_MAGIC: 257 | raise RuntimeError('Invalid file (magic=%x should be=%x)' % 258 | (magic, INTERP_MAGIC)) 259 | 260 | raw_data = f.read() 261 | 262 | # Stored as big endian 263 | format_ = '>%d%s' % (points, format_) 264 | 265 | data = struct.unpack(format_, raw_data) 266 | 267 | point_time = 1.e-6 * point_time 268 | t_end = point_time * points 269 | t = np.arange(0, t_end, point_time) 270 | 271 | return t, np.array(data) 272 | 273 | 274 | def get_addr_index(addresses, addr): 275 | try: 276 | return int(addr) 277 | except: 278 | addr_a = '%s.a' % addr 279 | if addr_a in addresses: 280 | return addresses.index(addr_a) 281 | else: 282 | return addresses.index(addr) 283 | 284 | 285 | def _check_times(gpascii, addresses, rows): 286 | if rows is None or len(rows) == 0: 287 | return rows 288 | 289 | if 'Sys.ServoCount.a' in addresses: 290 | idx = get_addr_index(addresses, 'Sys.ServoCount.a') 291 | servo_period = gpascii.servo_period 292 | 293 | times = [row[idx] for row in rows] 294 | if isinstance(rows[0], tuple): 295 | # TODO fast gather returns list of tuples 296 | rows = [list(row) for row in rows] 297 | 298 | gather_period = gpascii.get_variable('gather.period', type_=int) 299 | if 0 in times: 300 | # This happens when the gather buffer rolls over, iirc 301 | logger.warning('Gather data issue, trimming data...') 302 | last_time = times.index(0) 303 | 304 | times = np.arange(0, len(rows) * gather_period, gather_period) 305 | 306 | rows = rows[:last_time] 307 | 308 | for row, t0 in zip(rows, times): 309 | row[idx] = t0 * servo_period 310 | 311 | return rows 312 | 313 | 314 | def get_gather_results(comm, addresses, output_file=gather_output_file): 315 | if comm.fast_gather is not None: 316 | # Use the 'fast gather' server 317 | client = comm.fast_gather 318 | rows = client.get_rows() 319 | else: 320 | # Use the Delta Tau-supplied 'gather' program 321 | 322 | # -u is for upload 323 | comm.shell_command('gather "%s" -u' % (output_file, )) 324 | 325 | lines = [line.strip() for line in comm.read_file(output_file)] 326 | rows = parse_gather(addresses, lines) 327 | 328 | return _check_times(comm.gpascii, addresses, rows) 329 | 330 | 331 | def gather_data_to_file(fn, addr, data, delim='\t'): 332 | with open(fn, 'wt') as f: 333 | print(delim.join(addr), file=f) 334 | for line in data: 335 | line = ['%s' % s for s in line] 336 | print(delim.join(line), file=f) 337 | 338 | 339 | def gather_data_from_file(fn, delim='\t'): 340 | with open(fn, 'rt') as f: 341 | addresses = f.readline() 342 | addresses = addresses.strip().split(delim) 343 | 344 | lines = [line.strip() for line in f.readlines()] 345 | 346 | return addresses, parse_gather(addresses, lines, delim=delim) 347 | 348 | 349 | def plot(addr, data): 350 | x_idx = get_addr_index(addr, 'Sys.ServoCount.a') 351 | 352 | data = np.array(data) 353 | x_axis = data[:, x_idx] - data[0, x_idx] 354 | for i in range(len(addr)): 355 | if i == x_idx: 356 | pass 357 | else: 358 | plt.figure(i) 359 | plt.plot(x_axis, data[:, i], label=addr[i]) 360 | plt.legend() 361 | 362 | logger.debug('Plotting') 363 | plt.show() 364 | 365 | 366 | def gather_and_plot(gpascii, addr, duration=0.2, period=1): 367 | servo_period = gpascii.servo_period 368 | logger.debug('Servo period is %g (%g KHz)', servo_period, 369 | 1.0 / (servo_period * 1000)) 370 | 371 | data = gather(gpascii, addr, duration=duration, period=period) 372 | gather_data_to_file('test.txt', addr, data) 373 | plot(addr, data) 374 | 375 | 376 | def other_trajectory(move_type, motor, distance, velocity=1, accel=1, dwell=0, 377 | reps=1, one_direction=False, kill=True): 378 | """ 379 | root@10.0.0.98:/opt/ppmac/tune# ./othertrajectory 380 | You need 9 Arguments for this function 381 | Move type (1:Ramp ; 2: Trapezoidal 3:S-Curve Velocity 382 | Motor Number 383 | Move Distance(cts) 384 | Velocity cts/ms 385 | SAcceleration time (cts/ms^2) 386 | Dwell after move time (ms) 387 | Number of repetitions 388 | Move direction flag (0:move in both direction 1: move in only one direction) in 389 | Kill flag (0 or 1) 390 | Please try again. 391 | """ 392 | logger.info('other trajectory: %s %s', motor, move_type) 393 | assert(move_type in (OT_RAMP, OT_TRAPEZOID, OT_S_CURVE)) 394 | velocity = abs(velocity) 395 | 396 | args = ['%(move_type)d', 397 | '%(motor)d', 398 | '%(distance)f', 399 | '%(velocity)f', 400 | '%(accel)f', 401 | '%(dwell)d', 402 | '%(reps)d', 403 | '%(one_direction)d', 404 | '%(kill)d', 405 | ] 406 | 407 | args = ' '.join([arg % locals() for arg in args]) 408 | return '%s %s' % (tune_paths['othertrajectory'], args) 409 | 410 | 411 | def plot_tune_results(columns, data, 412 | keys=['Sys.ServoCount.a', 413 | 'Desired', 'Actual', 414 | 'Velocity']): 415 | 416 | data = np.array(data) 417 | idx = [columns.index(key) for key in keys] 418 | x_axis, desired, actual, velocity = [data[:, i] for i in idx] 419 | 420 | fig, ax1 = plt.subplots() 421 | ax1.plot(x_axis, desired, color='black', label='Desired') 422 | ax1.plot(x_axis, actual, color='b', label='Actual') 423 | ax1.set_xlabel('Time (s)') 424 | ax1.set_ylabel('Position (motor units)') 425 | for tl in ax1.get_yticklabels(): 426 | tl.set_color('b') 427 | 428 | error = desired - actual 429 | ax2 = ax1.twinx() 430 | ax2.plot(x_axis, error, color='r', alpha=0.4, label='Following error') 431 | ax2.set_ylabel('Error (motor units)') 432 | for tl in ax2.get_yticklabels(): 433 | tl.set_color('r') 434 | 435 | plt.xlim(min(x_axis), max(x_axis)) 436 | plt.show() 437 | 438 | 439 | def run_tune_program(comm, cmd, result_path='/var/ftp/gather/othertrajectory_gather.txt', 440 | timeout=50): 441 | logger.info('Running tune: %s', cmd) 442 | for line, m in comm.shell_output(cmd, timeout=timeout, 443 | wait_match='^(.*)\s+finished Successfully!$'): 444 | if m is not None: 445 | logger.info('Finished: %s', m.groups()[0]) 446 | break 447 | else: 448 | logger.debug(line) 449 | 450 | columns = ['Sys.ServoCount.a', 451 | 'Desired', 452 | 'Actual', 453 | 'Velocity'] 454 | 455 | data = get_gather_results(comm, columns, result_path) 456 | plot_tune_results(columns, data) 457 | 458 | BIN_PATH = '/opt/ppmac' 459 | TUNE_PATH = os.path.join(BIN_PATH, 'tune') 460 | TUNE_TOOLS = ('analyzerautotunemove', 'autotunecalc', 461 | 'autotunemove', 'chirpmove', 462 | 'currentautotunecalc', 'currentstep', 463 | 'filtercalculation', 'openloopchirp', 464 | 'openloopsine', 'openlooptestmove', 465 | 'othertrajectory', 'parabolicmove', 466 | 'randommove', 'sinesweep', 467 | 'sinusoidal', 'stepmove', 'usertrajectory') 468 | tune_paths = dict((tool, os.path.join(TUNE_PATH, tool)) 469 | for tool in TUNE_TOOLS) 470 | OT_RAMP = 1 471 | OT_TRAPEZOID = 2 472 | OT_S_CURVE = 3 473 | 474 | 475 | def _other_traj(move_type): 476 | @functools.wraps(other_trajectory) 477 | def wrapped(*args, **kwargs): 478 | return other_trajectory(move_type, *args, **kwargs) 479 | return wrapped 480 | 481 | ramp = _other_traj(OT_RAMP) 482 | trapezoid = _other_traj(OT_TRAPEZOID) 483 | s_curve = _other_traj(OT_S_CURVE) 484 | 485 | 486 | def geterrors_motor(motor, time_=0.3, abort_cmd='', m_mask=0x7ac, c_mask=0x7ac, 487 | r_mask=0x1e, g_mask=0xffffffff): 488 | exe = '/opt/ppmac/geterrors/geterrors' 489 | args = ('-t %(time_).1f -#%(motor)d -m0x%(m_mask)x -c0x%(c_mask)x ' 490 | '-r0x%(r_mask)x -g0x%(g_mask)x' % locals()) 491 | if abort_cmd: 492 | args += ' -S"%(abort_cmd)s"' 493 | 494 | logger.info('%s %s', exe, args) 495 | 496 | 497 | def run_and_gather(gpascii, script_text, prog=999, coord_sys=0, 498 | gather_vars=[], period=1, samples=max_samples, 499 | cancel_callback=None, check_active=False, 500 | verbose=True): 501 | """ 502 | Run a motion program and read back the gathered data 503 | """ 504 | 505 | if 'gather.enable' not in script_text.lower(): 506 | script_text = '\n'.join(['gather.enable=2', 507 | script_text, 508 | 'gather.enable=0' 509 | ]) 510 | 511 | comm = gpascii._comm 512 | gpascii.set_variable('gather.enable', '0') 513 | 514 | gather_vars = InsList(gather_vars) 515 | 516 | if 'sys.servocount.a' not in gather_vars: 517 | gather_vars.insert(0, 'Sys.ServoCount.a') 518 | 519 | settings = get_settings(gpascii.servo_period, gather_vars, 520 | gather_period=period, 521 | samples=samples) 522 | 523 | settings = '\n'.join(settings) 524 | 525 | comm.write_file(gather_config_file, settings) 526 | 527 | logger.info('Wrote configuration to %s', gather_config_file) 528 | 529 | comm.gpascii_file(gather_config_file, verbose=verbose) 530 | 531 | for line in script_text.split('\n'): 532 | gpascii.send_line(line.lstrip()) 533 | 534 | gpascii.program(coord_sys, prog, start=True) 535 | 536 | if check_active: 537 | active_var = 'Coord[%d].ProgActive' % coord_sys 538 | else: 539 | active_var = 'gather.enable' 540 | 541 | def get_status(): 542 | return gpascii.get_variable(active_var, type_=int) 543 | 544 | try: 545 | # time.sleep(1.0 + abs((iterations * distance) / velocity)) 546 | vlog(verbose, "Waiting...") 547 | while get_status() == 0: 548 | time.sleep(0.1) 549 | 550 | while get_status() != 0: 551 | samples = gpascii.get_variable('gather.samples', type_=int) 552 | vlog(verbose, "Working... got %6d data points" % samples, end='\r') 553 | time.sleep(0.1) 554 | 555 | vlog(verbose, 'Done') 556 | 557 | except KeyboardInterrupt as ex: 558 | vlog(verbose, 'Cancelled - stopping program') 559 | gpascii.program(coord_sys, prog, stop=True) 560 | if cancel_callback is not None: 561 | cancel_callback(ex) 562 | 563 | try: 564 | for line in gpascii.read_timeout(timeout=0.1): 565 | if 'error' in line: 566 | if verbose: 567 | print(line) 568 | logger.error(line) 569 | except pp_comm.TimeoutError: 570 | pass 571 | 572 | data = get_gather_results(comm, gather_vars, gather_output_file) 573 | return gather_vars, data 574 | 575 | 576 | def check_servocapt_rollover(scapt, rollover=1e6): 577 | ret = np.zeros(len(scapt), dtype=float) 578 | last_raw = scapt[0] 579 | offset = 0 580 | for i, raw_s in enumerate(scapt): 581 | if abs(raw_s - last_raw) > rollover: 582 | offset += last_raw - raw_s 583 | 584 | ret[i] = offset + raw_s 585 | 586 | last_raw = raw_s 587 | 588 | return ret 589 | 590 | 591 | def main(): 592 | logging.basicConfig() 593 | logger.setLevel(logging.DEBUG) 594 | 595 | addr = ['Sys.ServoCount.a', 596 | 'Motor[3].Pos.a', 597 | # 'Motor[4].Pos.a', 598 | # 'Motor[5].Pos.a', 599 | ] 600 | duration = 10.0 601 | period = 1 602 | 603 | from .pp_comm import PPComm 604 | 605 | comm = PPComm() 606 | gpascii = comm.gpascii_channel() 607 | gpascii.servo_period 608 | 609 | if 0: 610 | ramp_cmd = ramp(3, distance=0.01, velocity=0.02) 611 | run_tune_program(comm, ramp_cmd) 612 | else: 613 | gather_and_plot(comm.gpascii, addr, duration=duration, period=period) 614 | 615 | 616 | if __name__ == '__main__': 617 | main() 618 | -------------------------------------------------------------------------------- /ppmac/gather_types.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | import struct 3 | import six 4 | 5 | 6 | # TODO: uint24/int24 are untested -- assuming they are still stored in 4 bytes 7 | # The following could potentially be greatly simplified if that can be 8 | # verified, especially if no sign-extension is required... 9 | # TODO: ubits/sbits -- not even sure what they are (unsigned/signed bits?) or 10 | # their size 11 | 12 | if six.PY2: 13 | def _extend_int24(b): 14 | sign_bit = ord(b[1]) & 0x80 15 | if sign_bit: 16 | sign_extension = '\xFF' 17 | else: 18 | sign_extension = '\x00' 19 | 20 | return ''.join((sign_extension, b[1], b[2], b[3])) 21 | 22 | def _extend_uint24(b): 23 | return ''.join(('\x00', b[1], b[2], b[3])) 24 | 25 | else: 26 | def _extend_int24(b): 27 | sign_bit = b[1] & 0x80 28 | if sign_bit: 29 | sign_extension = 0xFF 30 | else: 31 | sign_extension = 0x00 32 | 33 | return bytes([sign_extension, b[1], b[2], b[3]]) 34 | 35 | def _extend_uint24(b): 36 | return bytes([0x00, b[1], b[2], b[3]]) 37 | 38 | 39 | def conv_int24(values): 40 | '''sign extend big-endian 3-byte integers and unpack them''' 41 | return struct.unpack('>%di' % len(values), 42 | b''.join(_extend_int24(b) for b in values)) 43 | 44 | 45 | def conv_uint24(values): 46 | '''unpack big-endian 3-byte unsigned integers''' 47 | return struct.unpack('>%di' % len(values), 48 | b''.join(_extend_uint24(b) for b in values)) 49 | 50 | 51 | UINT32, INT32, UINT24, INT24, FLOAT, DOUBLE, UBITS, SBITS = range(8) 52 | 53 | GATHER_TYPES = { 54 | # type index : (size, format char, conversion function) 55 | UINT32: (4, 'I', None), 56 | INT32: (4, 'i', None), 57 | UINT24: (4, 'I', None), 58 | INT24: (4, '4B', conv_int24), 59 | FLOAT: (4, 'f', None), 60 | DOUBLE: (8, 'd', None), 61 | UBITS: (4, 'I', None), 62 | SBITS: (4, 'I', None), 63 | } 64 | -------------------------------------------------------------------------------- /ppmac/hardware.py: -------------------------------------------------------------------------------- 1 | """ 2 | :mod:`ppmac.hardware` -- Hardware enumeration 3 | ============================================= 4 | 5 | .. module:: ppmac.hardware 6 | :synopsis: Get a hierarchical representation of the installed cards in the 7 | Power PMAC. Note that not all devices are represented here, 8 | but only those that are listed in the incomplete Power PMAC 9 | documentation. Additionally, most device classes are just 10 | placeholders. 11 | .. moduleauthor:: Ken Lauer 12 | 13 | """ 14 | from __future__ import print_function 15 | from . import const 16 | 17 | VALID_GATES = (1, 2, 3, 'IO') 18 | 19 | 20 | def var_prop(read_var, write_var=None, 21 | cached=False, fail_value=None, 22 | set_args={}, **get_args): 23 | """ 24 | Create a variable property that reads `read_var` from 25 | gpascii on getattr, and sets `write_var` on setattr. 26 | 27 | If `cached` is set, the value is only read once and 28 | cached in the dictionary `_cache` on the object. Setting 29 | the value triggers a cache update. 30 | """ 31 | if write_var is None: 32 | write_var = read_var 33 | 34 | def check_cache(self): 35 | if not hasattr(self, '_cache'): 36 | self._cache = {} 37 | 38 | return self._cache.get(read_var, None) 39 | 40 | def fget(self): 41 | if cached: 42 | value = check_cache(self) 43 | if value is not None: 44 | return value 45 | 46 | try: 47 | value = self.get_variable(read_var, **get_args) 48 | except: 49 | if fail_value is not None: 50 | value = fail_value 51 | else: 52 | raise 53 | 54 | if cached: 55 | self._cache[read_var] = value 56 | 57 | return value 58 | 59 | def fset(self, value): 60 | readback = self.set_variable(write_var, value, **set_args) 61 | if cached: 62 | check_cache(self) 63 | self._cache[read_var] = readback 64 | 65 | doc = 'Variable property %s/%s' % (read_var, write_var) 66 | return property(fget, fset, doc=doc) 67 | 68 | 69 | class ChannelBase(object): 70 | """ 71 | Base class for channels 72 | """ 73 | 74 | def __init__(self, gate, index): 75 | self._gate = gate 76 | self._base = '%s.Chan[%d]' % (gate._base, index) 77 | 78 | @property 79 | def gpascii(self): 80 | return self._gate.gpascii 81 | 82 | def get_variable_name(self, name): 83 | """ 84 | Get the fully qualified variable name of a short variable name 85 | e.g., get_variable_name('PfmFormat') => 'Gate3[0].Chan[1].PfmFormat' 86 | """ 87 | return '%s.%s' % (self._base, name) 88 | 89 | def get_variable(self, name, **kwargs): 90 | """ 91 | Get the value of a variable from gpascii 92 | """ 93 | return self.gpascii.get_variable(self.get_variable_name(name), 94 | **kwargs) 95 | 96 | def set_variable(self, name, value, **kwargs): 97 | """ 98 | Set the value of a variable with gpascii 99 | """ 100 | return self.gpascii.set_variable(self.get_variable_name(name), value, 101 | **kwargs) 102 | 103 | def __repr__(self): 104 | return '%s(base="%s")' % (self.__class__.__name__, self._base) 105 | 106 | 107 | class GateBase(object): 108 | """ 109 | Base class for any Gate device (i.e., card) 110 | """ 111 | N_CHANNELS = 0 112 | channel_class = ChannelBase 113 | 114 | def __init__(self, gpascii, index): 115 | self.gpascii = gpascii 116 | self._index = index 117 | self._base = self.BASE % index 118 | self.types = [const.part_types.get(i, '%d' % i) 119 | for i in _bit_indices(self._type)] 120 | self.channels = {} 121 | 122 | if self.N_CHANNELS > 0: 123 | for channel in range(self.N_CHANNELS): 124 | self._get_channel(channel) 125 | 126 | num = var_prop('PartNum', type_=int, cached=True) 127 | rev = var_prop('PartRev', type_=int, cached=True, fail_value=-1) 128 | _type = var_prop('PartType', type_=int, cached=True, fail_value=0) 129 | 130 | phase_clock_div = var_prop('PhaseClockDiv', type_=int) 131 | servo_clock_div = var_prop('ServoClockDiv', type_=int) 132 | phase_servo_dir = var_prop('PhaseServoDir', type_=int) 133 | 134 | def _get_channel(self, index): 135 | try: 136 | channel = self.channels[index] 137 | except: 138 | channel = self.channels[index] = self.channel_class(self, index) 139 | 140 | return channel 141 | 142 | def get_variable_name(self, name): 143 | """ 144 | Get the fully qualified variable name of a short variable name 145 | e.g., get_variable_name('PartNum') => 'Gate3[0].PartNum' 146 | """ 147 | return '%s.%s' % (self._base, name) 148 | 149 | def get_variable(self, name, **kwargs): 150 | """ 151 | Get the value of a variable from gpascii 152 | """ 153 | return self.gpascii.get_variable(self.get_variable_name(name), 154 | **kwargs) 155 | 156 | def set_variable(self, name, value, **kwargs): 157 | """ 158 | Set the value of a variable with gpascii 159 | """ 160 | return self.gpascii.set_variable(self.get_variable_name(name), value, 161 | **kwargs) 162 | 163 | def __repr__(self): 164 | return ('{0}(index={1._index:d}, num={1.num:d}, rev={1.rev:d}, ' 165 | 'types={1.types})'.format(self.__class__.__name__, self)) 166 | 167 | @property 168 | def phase_master(self): 169 | """ 170 | The source of the phase clock for the entire system 171 | 172 | Quoting the help file: 173 | In any Power PMAC system, there must be one and only one source of 174 | servo and phase clock signals for the system - either one of the 175 | Servo ICs or MACRO ICs, or a source external to the system. 176 | """ 177 | return (self.phase_servo_dir & 1) == 1 178 | 179 | @property 180 | def servo_master(self): 181 | """ 182 | The source of the servo clock for the entire system 183 | """ 184 | return (self.phase_servo_dir & 2) == 2 185 | 186 | def get_clock_settings(self, phase_freq, phase_clock_div, servo_clock_div, 187 | **kwargs): 188 | pass 189 | 190 | def _update_clock(self, phase_freq, phase_clock_div, servo_clock_div, 191 | **kwargs): 192 | pass 193 | 194 | 195 | class Gate12Base(GateBase): 196 | """ 197 | Base for either Gate 1s or Gate 2s 198 | """ 199 | pwm_period = var_prop('PwmPeriod', type_=int) 200 | 201 | @property 202 | def phase_frequency(self): 203 | return self.max_phase_frequency / (self.phase_clock_div + 1.0) 204 | 205 | @property 206 | def pwm_frequency(self): 207 | return const.PWM_FREQ_HZ / (self.pwm_period * 4.0 + 6.0) 208 | 209 | @property 210 | def max_phase_frequency(self): 211 | return 2.0 * self.pwm_frequency 212 | 213 | def _get_pwm_period(self, phase_freq, phase_clock_div): 214 | return int(const.PWM_FREQ_HZ / (2 * (phase_clock_div + 1) * 215 | phase_freq)) - 1 216 | 217 | def get_clock_settings(self, phase_freq, phase_clock_div, servo_clock_div, 218 | **kwargs): 219 | pwm_period = self._get_pwm_period(phase_freq, phase_clock_div) 220 | 221 | return [(self.get_variable_name('PwmPeriod'), pwm_period), 222 | (self.get_variable_name('PhaseClockDiv'), phase_clock_div), 223 | (self.get_variable_name('ServoClockDiv'), servo_clock_div), 224 | ] 225 | 226 | def _update_clock(self, phase_freq, phase_clock_div, servo_clock_div, 227 | **kwargs): 228 | self.pwm_period = self._get_pwm_period(phase_freq, phase_clock_div) 229 | self.phase_clock_div = phase_clock_div 230 | self.servo_clock_div = servo_clock_div 231 | 232 | 233 | class Gate1(Gate12Base): 234 | BASE = 'Gate1[%d]' 235 | 236 | 237 | class Gate2(Gate12Base): 238 | BASE = 'Gate1[%d]' 239 | 240 | 241 | class GateIO(GateBase): 242 | BASE = 'GateIO[%d]' 243 | phase_servo_dir = 0 244 | 245 | 246 | class Gate3Channel(ChannelBase): 247 | """ 248 | """ 249 | 250 | def __init__(self, gate, index): 251 | ChannelBase.__init__(self, gate, index) 252 | 253 | pwm_freq_mult = var_prop('PwmFreqMult', type_=int) 254 | pwm_dead_time = var_prop('PwmDeadTime', type_=int) 255 | 256 | @property 257 | def pwm_frequency(self): 258 | return (1.0 + self.pwm_freq_mult) * self._gate.phase_frequency / 2.0 259 | 260 | 261 | class Gate3(GateBase): 262 | N_OPT = 8 263 | BASE = 'Gate3[%d]' 264 | channel_class = Gate3Channel 265 | 266 | phase_frequency = var_prop('PhaseFreq', type_=float) 267 | phase_clock_mult = var_prop('PhaseClockMult', type_=int) 268 | 269 | def __init__(self, gpascii, index): 270 | GateBase.__init__(self, gpascii, index) 271 | self.options = [gpascii.get_variable('%s.PartOpt%d' % (self._base, n), 272 | type_=int) 273 | for n in range(self.N_OPT)] 274 | 275 | @property 276 | def opt_base_board(self): 277 | """ 278 | Base board option 279 | """ 280 | return self.options[0] 281 | 282 | @property 283 | def opt_feedback(self): 284 | """ 285 | Feedback interface option for channels: 286 | (0 and 1, 2 and 3) 287 | """ 288 | return (self.options[1], self.options[5]) 289 | 290 | @property 291 | def opt_output(self): 292 | """ 293 | Output interface option for channels: 294 | (0 and 1, 2 and 3) 295 | """ 296 | return (self.options[2], self.options[6]) 297 | 298 | @property 299 | def opt_core(self): 300 | """ 301 | Core circuitry for second set of two channels (if separate) 302 | """ 303 | return self.options[4] 304 | 305 | def __repr__(self): 306 | return ('{0}(index={1._index:d}, num={1.num:d}, rev={1.rev:d}, ' 307 | 'types={1.types})'.format(self.__class__.__name__, self)) 308 | 309 | def get_clock_settings(self, phase_freq, phase_clock_div=0, 310 | servo_clock_div=0, pwm_freq_mult=None, 311 | phase_clock_mult=0, 312 | **kwargs): 313 | ret = [] 314 | 315 | # Phase clock divider is ignored if this is the phase clock master 316 | if self.phase_master: 317 | phase_clock_div = 0 318 | phase_clock_mult = 0 319 | 320 | ret.append((self.get_variable_name('PhaseFreq'), phase_freq)) 321 | ret.append((self.get_variable_name('PhaseClockDiv'), phase_clock_div)) 322 | ret.append((self.get_variable_name('PhaseClockMult'), phase_clock_mult)) 323 | ret.append((self.get_variable_name('ServoClockDiv'), servo_clock_div)) 324 | 325 | if pwm_freq_mult is not None: 326 | for i, chan in self.channels.items(): 327 | ret.append((chan.get_variable_name('PwmFreqMult'), 328 | pwm_freq_mult)) 329 | 330 | return ret 331 | 332 | def _update_clock(self, phase_freq, phase_clock_div=0, servo_clock_div=0, 333 | pwm_freq_mult=None, **kwargs): 334 | for line in self.get_clock_settings(phase_freq, 335 | phase_clock_div=phase_clock_div, 336 | servo_clock_div=servo_clock_div, 337 | pwm_freq_mult=None, **kwargs): 338 | self.gpascii.send_line(line) 339 | 340 | 341 | class ACC24E3(Gate3): 342 | N_CHANNELS = 4 343 | 344 | 345 | class ACC5E3(Gate3): 346 | pass 347 | 348 | 349 | class ACC59E3(Gate3): 350 | pass 351 | 352 | 353 | class PowerBrick(Gate3): 354 | pass 355 | 356 | 357 | class ACC5E(Gate2): 358 | pass 359 | 360 | 361 | class ACC24E2S(Gate1): 362 | pass 363 | 364 | 365 | class ACC28E(GateIO): 366 | pass 367 | 368 | 369 | class ACC11C(GateIO): 370 | pass 371 | 372 | 373 | class ACC14E(GateIO): 374 | pass 375 | 376 | 377 | class ACC65E(GateIO): 378 | pass 379 | 380 | 381 | class ACC66E(GateIO): 382 | pass 383 | 384 | 385 | class ACC67E(GateIO): 386 | pass 387 | 388 | 389 | class ACC68E(GateIO): 390 | pass 391 | 392 | 393 | def _bit_indices(value): 394 | i = 0 395 | while value > 0: 396 | if (value & 1) == 1: 397 | yield i 398 | 399 | value >>= 1 400 | i += 1 401 | 402 | 403 | def get_autodetect_indices(gpascii, gate=3): 404 | """ 405 | Yields all of the gate indices for a specified type of gate 406 | """ 407 | assert(gate in VALID_GATES) 408 | 409 | if gate == 'IO': 410 | det_var = 'Sys.CardIOAutoDetect' 411 | else: 412 | det_var = 'Sys.Gate%dAutoDetect' % gate 413 | 414 | detected = gpascii.get_variable(det_var, type_=int) 415 | for index in _bit_indices(detected): 416 | yield index 417 | 418 | 419 | def get_addr_error_indices(gpascii, gate=3): 420 | """ 421 | Yields the gate indices of a specific type that may have incorrectly 422 | configured addresses 423 | """ 424 | assert(gate in VALID_GATES) 425 | 426 | if gate == 'IO': 427 | pass 428 | else: 429 | det_var = 'Sys.Gate%dAddrErrDetect' % gate 430 | 431 | detected = gpascii.get_variable(det_var, type_=int) 432 | for index in _bit_indices(detected): 433 | yield index 434 | 435 | 436 | def _get_gates(gpascii, gate_ver, default_class): 437 | for index in get_autodetect_indices(gpascii, gate_ver): 438 | base = 'Gate%s[%d]' % (gate_ver, index) 439 | part_num = gpascii.get_variable('%s.PartNum' % base, type_=int) 440 | if part_num == 0: 441 | continue 442 | 443 | part_str = const.parts.get(part_num, '') 444 | if part_str in globals(): 445 | class_ = globals()[part_str] 446 | else: 447 | class_ = default_class 448 | 449 | yield class_(gpascii, index) 450 | 451 | 452 | def enumerate_hardware(gpascii): 453 | """ 454 | Returns a list of Gate* instances for all detected hardware 455 | """ 456 | 457 | gates = [(1, Gate1), (2, Gate2), (3, Gate3), ('IO', GateIO)] 458 | return [inst 459 | for gate_ver, default_class in gates 460 | for inst in _get_gates(gpascii, gate_ver, default_class) 461 | ] 462 | 463 | 464 | def enumerate_address_errors(gpascii): 465 | """ 466 | Returns indices of all hardware with address errors detected 467 | 468 | In the format dict(1=[index0, index1], 2=[index2], 3=[]), 469 | where the keys represent Gate1, Gate2, and Gate3. 470 | """ 471 | ret = {} 472 | for gate in VALID_GATES: 473 | ret[gate] = [] 474 | for index in get_addr_error_indices(gpascii, 3): 475 | ret[gate].append(index) 476 | 477 | return ret 478 | 479 | 480 | def test(): 481 | from .pp_comm import PPComm 482 | comm = PPComm() 483 | for device in enumerate_hardware(comm.gpascii): 484 | if device.phase_master or device.servo_master: 485 | print('%s <-- Master (phase: %d servo: %d' 486 | ')' % (device, device.phase_master, device.servo_master)) 487 | else: 488 | print(device) 489 | for i, chan in device.channels.items(): 490 | print('\tChannel %d: %s' % (i, chan)) 491 | 492 | errors = enumerate_address_errors(comm.gpascii) 493 | print('Address errors: %s' % errors) 494 | 495 | 496 | if __name__ == '__main__': 497 | test() 498 | -------------------------------------------------------------------------------- /ppmac/pp_comm.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | :mod:`ppmac.pp_comm` -- Ppmac Communication 4 | =========================================== 5 | 6 | .. module:: ppmac.pp_comm 7 | :synopsis: Power PMAC communication through SSH by way of Paramiko. 8 | Simplifies working with gpascii (the Power PMAC interpreter) 9 | remotely. 10 | Additionally does simple file operations through SFTP. 11 | .. moduleauthor:: Ken Lauer 12 | """ 13 | 14 | from __future__ import print_function 15 | import re 16 | import sys 17 | import time 18 | import logging 19 | import threading 20 | import six 21 | 22 | import paramiko 23 | 24 | from . import const 25 | from . import config 26 | 27 | 28 | logger = logging.getLogger(__name__) 29 | 30 | 31 | try: 32 | from . import fast_gather as fast_gather_mod 33 | except ImportError as ex: 34 | fast_gather_mod = None 35 | logger.warning('Unable to load the fast gather module', exc_info=ex) 36 | 37 | 38 | class PPCommError(Exception): 39 | pass 40 | 41 | 42 | class PPCommChannelClosed(PPCommError): 43 | pass 44 | 45 | 46 | class TimeoutError(PPCommError): 47 | pass 48 | 49 | 50 | class GPError(PPCommError): 51 | pass 52 | 53 | 54 | class ScriptFailed(GPError): 55 | pass 56 | 57 | 58 | class ScriptCancelled(ScriptFailed): 59 | pass 60 | 61 | 62 | PPMAC_MESSAGES = [re.compile('.*\/\/ \*\*\* exit'), 63 | re.compile('^UnlinkGatherThread:.*'), 64 | re.compile('^\/\/ \*\*\* EOF'), 65 | ] 66 | 67 | 68 | def vlog(verbose, *args, **kwargs): 69 | '''Verbose logging 70 | 71 | Print output to kwarg `file` if `verbose` is set 72 | 73 | Outputs to the module logger at the debug level in all cases 74 | ''' 75 | if verbose: 76 | print(*args, **kwargs) 77 | 78 | kwargs.pop('file', '') 79 | logger.debug(*args, **kwargs) 80 | 81 | 82 | def _wait_for(generator, wait_pattern, 83 | verbose=False, remove_matching=[], 84 | remove_ppmac_messages=True, rstrip=True): 85 | """ 86 | Wait, up until `timeout` seconds, for wait_pattern 87 | 88 | Removes `remove_matching` regular expressions from the 89 | output. 90 | """ 91 | 92 | if remove_ppmac_messages: 93 | remove_matching = list(remove_matching) + PPMAC_MESSAGES 94 | 95 | wait_re = re.compile(wait_pattern) 96 | 97 | for line in generator: 98 | if rstrip: 99 | line = line.rstrip() 100 | 101 | vlog(verbose, line) 102 | 103 | if line == wait_pattern: 104 | yield line, [] 105 | break 106 | 107 | m = wait_re.match(line) 108 | if m is not None: 109 | yield line, m.groups() 110 | 111 | skip = False 112 | for regex in remove_matching: 113 | m = regex.match(line) 114 | if m is not None: 115 | skip = True 116 | break 117 | 118 | if not skip: 119 | yield line, None 120 | 121 | 122 | class ShellChannel(object): 123 | """ 124 | An interactive SSH shell channel 125 | """ 126 | 127 | def __init__(self, comm, command=None, single=False, 128 | disable_readline=False, verbose=False): 129 | self.lock = threading.RLock() 130 | self._comm = comm 131 | self._client = comm._client 132 | self._channel = comm._client.invoke_shell() 133 | self._verbose = verbose 134 | 135 | if disable_readline: 136 | self.send_line('/bin/bash --noediting') 137 | 138 | self.send_line('stty -echo') 139 | self.send_line(r'export PS1="\u@\h:\w\$ "') 140 | self.wait_for('%s@.*' % comm._user, verbose=verbose) 141 | 142 | if command is not None: 143 | self.send_line(command) 144 | 145 | def wait_for(self, wait_pattern, timeout=5.0, verbose=False, 146 | remove_matching=[], **kwargs): 147 | """ 148 | Wait, up until `timeout` seconds, for wait_pattern 149 | 150 | Removes `remove_matching` regular expressions from the 151 | output. 152 | """ 153 | 154 | with self.lock: 155 | gen = self.read_timeout(timeout, **kwargs) 156 | ret = [] 157 | for line, groups in _wait_for(gen, wait_pattern, verbose=verbose, 158 | remove_matching=remove_matching): 159 | ret.append(line) 160 | if groups is not None: 161 | return ret, groups 162 | 163 | return False 164 | 165 | def sync(self, verbose=False, timeout=0.01): 166 | """ 167 | Empty the incoming read buffer 168 | """ 169 | channel = self._channel 170 | if channel is None: 171 | raise PPCommChannelClosed() 172 | 173 | with self.lock: 174 | logger.debug('Sync') 175 | 176 | try: 177 | for line in self.read_timeout(timeout=timeout): 178 | if 'error' in line: 179 | raise GPError(line) 180 | 181 | vlog(verbose, line) 182 | except TimeoutError: 183 | pass 184 | 185 | vlog(verbose, '') 186 | 187 | def read_timeout(self, timeout=5.0, delim='\r\n', verbose=False): 188 | """ 189 | Generator which reads lines from the channel, optionally outputting the 190 | lines to stdout (if verbose=True) 191 | """ 192 | channel = self._channel 193 | if channel is None: 194 | raise PPCommChannelClosed() 195 | 196 | with self.lock: 197 | t0 = time.time() 198 | buf = '' 199 | 200 | def check_timeout(): 201 | if timeout is None: 202 | return True 203 | return ((time.time() - t0) <= timeout) 204 | 205 | while channel.recv_ready() or check_timeout(): 206 | if channel.recv_ready(): 207 | if six.PY3: 208 | buf += channel.recv(1024).decode('ascii') 209 | else: 210 | buf += channel.recv(1024) 211 | 212 | lines = buf.split(delim) 213 | if not buf.endswith(delim): 214 | buf = lines[-1] 215 | lines = lines[:-1] 216 | else: 217 | buf = '' 218 | 219 | for line in lines: 220 | vlog(verbose, '<- %s' % line) 221 | yield line.rstrip() 222 | 223 | else: 224 | time.sleep(0.01) 225 | 226 | if channel.recv_stderr_ready(): 227 | line = channel.recv_stderr(1024) 228 | vlog(verbose, ' %s' % line) 243 | channel.send('%s%s' % (line, delim)) 244 | 245 | if sync: 246 | self.sync() 247 | 248 | 249 | class GpasciiChannel(ShellChannel): 250 | """ 251 | An SSH channel which represents a connection to 252 | Gpascii, the Power PMAC command interpreter 253 | """ 254 | 255 | CMD_GPASCII = 'gpascii -2 2>&1' 256 | EOT = '\04' 257 | 258 | def __init__(self, comm, command=None, verbose=False): 259 | if command is None: 260 | command = self.CMD_GPASCII 261 | 262 | ShellChannel.__init__(self, comm, command=command, 263 | verbose=verbose) 264 | 265 | if not self.wait_for('.*(STDIN Open for ASCII Input)$'): 266 | raise ValueError('GPASCII startup string not found') 267 | 268 | def close(self): 269 | """ 270 | Close the gpascii connection 271 | """ 272 | channel = self._channel 273 | 274 | if channel is not None and not channel.closed: 275 | self.sync() 276 | channel.send(self.EOT) 277 | self._channel = None 278 | 279 | __del__ = close 280 | 281 | def set_variable(self, var, value, check=True): 282 | """ 283 | Set a Power PMAC variable to value 284 | """ 285 | var = var.lower() 286 | self.send_line('%s=%s' % (var, value)) 287 | if check: 288 | return self.get_variable(var) 289 | 290 | def get_variable(self, var, type_=str, timeout=2.0): 291 | """ 292 | Get a Power PMAC variable, and typecast it to type_ 293 | 294 | e.g., 295 | >> comm.get_variable('i100', type_=str) 296 | '0' 297 | >> comm.get_variable('i100', type_=int) 298 | 0 299 | """ 300 | var = var.lower() 301 | with self.lock: 302 | self.send_line(var) 303 | 304 | for line in self.read_timeout(timeout=timeout): 305 | if 'error' in line: 306 | raise GPError(line) 307 | 308 | if '=' in line: 309 | vname, value = line.split('=', 1) 310 | if var == vname.lower(): 311 | if value.startswith('$'): 312 | # check for a hex value 313 | value = int(value[1:], 16) 314 | 315 | return type_(value) 316 | 317 | def get_variables(self, variables, type_=str, timeout=0.2, 318 | cb=None, error_cb=None): 319 | """ 320 | Get Power PMAC variables, typecasting them to type_ 321 | 322 | Optionally calls a callback per variable to modify its value 323 | 324 | >> comm.get_variables(['i100', 'i200'], type_=int) 325 | [0, 1] 326 | >> comm.get_variables(['i100', 'i200'], type_=int, 327 | cb=lambda var, value: value + 1) 328 | [1, 2] 329 | """ 330 | ret = [] 331 | for var in variables: 332 | try: 333 | value = self.get_variable(var) 334 | except (GPError, TimeoutError) as ex: 335 | if error_cb is None: 336 | ret.append('Error: %s' % (ex, )) 337 | else: 338 | ret.append(error_cb(var, ex)) 339 | else: 340 | if cb is not None: 341 | try: 342 | value = cb(var, value) 343 | except: 344 | pass 345 | 346 | ret.append(value) 347 | 348 | return ret 349 | 350 | def kill_motor(self, motor): 351 | """ 352 | Kill a specific motor 353 | """ 354 | self.send_line('#%dk' % (motor, )) 355 | 356 | def kill_motors(self, motors): 357 | """ 358 | Kill a list of motors 359 | """ 360 | motor_list = list(set(motors)) 361 | motor_list.sort() 362 | motor_list = ','.join('%d' % motor for motor in motor_list) 363 | 364 | self.send_line('#%sk' % (motor_list, )) 365 | 366 | @property 367 | def servo_period(self): 368 | """ 369 | The servo period, in seconds 370 | """ 371 | period = self.get_variable('Sys.ServoPeriod', type_=float) 372 | return period * 1e-3 373 | 374 | @property 375 | def servo_frequency(self): 376 | """ 377 | The servo frequency, in Hz 378 | """ 379 | return 1.0 / self.servo_period 380 | 381 | def get_coord(self, motor): 382 | """ 383 | Query a motor to determine which coordinate system it's in 384 | """ 385 | with self.lock: 386 | self.send_line('&0#%d->' % motor) 387 | 388 | for line in self.read_timeout(): 389 | if 'error' in line: 390 | raise GPError(line) 391 | 392 | if '#' not in line: 393 | continue 394 | 395 | # <- &2#1->x 396 | # ('&2', '2', '1', 'x') 397 | # <- #3->0 398 | # (None, None, '3', '0') 399 | 400 | m = re.search('(&(\d+))?#(\d+)->([a-zA-Z0-9]+)', line) 401 | if not m: 402 | continue 403 | 404 | groups = m.groups() 405 | _, coord, mnum, assigned = groups 406 | if assigned == '0': 407 | assigned = None 408 | if int(mnum) == motor: 409 | if coord is None: 410 | coord = 0 411 | else: 412 | coord = int(coord) 413 | return coord, assigned 414 | 415 | return None, None 416 | 417 | def get_coords(self): 418 | """ 419 | Returns the coordinate system setup 420 | 421 | For example: 422 | {1: {11: 'x'}, 2: {1: 'x', 12: 'y'}} 423 | Sets: 424 | coordinate system 1, motor 11 is X 425 | coordinate system 2, motor 1 is X, motor 12 is Y 426 | """ 427 | num_motors = self.get_variable('sys.maxmotors', type_=int) 428 | coords = {} 429 | for motor in range(num_motors): 430 | coord, assigned = self.get_coord(motor) 431 | if assigned is not None: 432 | if coord not in coords: 433 | coords[coord] = {} 434 | coords[coord][motor] = assigned 435 | 436 | return coords 437 | 438 | def get_motor_coords(self): 439 | """ 440 | Get the coordinate systems motors are assigned to 441 | 442 | Returns a dictionary with key=motor, value=coordinate system 443 | """ 444 | coords = self.get_coords() 445 | ret = {} 446 | for coord, motors in coords.items(): 447 | for motor, axis in motors.items(): 448 | ret[motor] = coord 449 | 450 | return ret 451 | 452 | def set_coords(self, coords, verbose=False, undefine_coord=False, 453 | undefine_all=False, check=True): 454 | """ 455 | Clear and then set all of the coordinate systems 456 | as in `coords`. 457 | 458 | For example: 459 | {1: {11: 'x'}, 2: {1: 'x', 12: 'y'}} 460 | Sets: 461 | coordinate system 1, motor 11 is X 462 | coordinate system 2, motor 1 is X, motor 12 is Y 463 | """ 464 | with self.lock: 465 | if not coords: 466 | return 467 | 468 | max_coord = max(coords.keys()) 469 | if max_coord > self.get_variable('sys.maxcoords', type_=int): 470 | vlog(verbose, 'Increasing maxcoords to %d' % (max_coord + 1)) 471 | self.set_variable('sys.maxcoords', max_coord + 1) 472 | 473 | # Abort any running programs in coordinate systems 474 | for coord in coords.keys(): 475 | self.send_line('&%dabort' % (coord, )) 476 | 477 | if undefine_all: 478 | # Undefine all coordinate systems 479 | self.send_line('undefine all') 480 | elif undefine_coord: 481 | # Undefine only the coordinate systems being set here 482 | for coord in coords.keys(): 483 | self.send_line('&%dundefine' % (coord, )) 484 | 485 | # Ensure the motors aren't in coordinate systems already 486 | motor_to_coord = self.get_motor_coords() 487 | for coord, motors in coords.items(): 488 | for motor, assigned in motors.items(): 489 | try: 490 | coord = motor_to_coord[motor] 491 | except KeyError: 492 | # Not in coordinate system currently 493 | pass 494 | else: 495 | # Remove it from the coordinate sytem 496 | undef_line = '&%d#%d->0' % (coord, motor, ) 497 | self.send_line(undef_line, sync=True) 498 | 499 | # Then assign them to the new coordinate systems 500 | for coord, motors in coords.items(): 501 | for motor, assigned in motors.items(): 502 | assign_line = '&%d#%d->%s' % (coord, motor, assigned) 503 | vlog(verbose, 'Coordinate system %d: motor %d is %s' % 504 | (coord, motor, assigned)) 505 | 506 | try: 507 | self.send_line(assign_line, sync=True) 508 | except GPError as ex: 509 | raise GPError('Failed to set coord[%d] motor %d: %s' % 510 | (coord, motor, ex)) 511 | 512 | if check: 513 | current = self.get_coords() 514 | for coord, motors in coords.items(): 515 | motors = [(num, axis.lower()) for num, axis in 516 | motors.items()] 517 | motors_current = [(num, axis.lower()) for num, axis in 518 | current[coord].items()] 519 | if set(motors) != set(motors_current): 520 | vlog(verbose, motors, motors_current) 521 | raise ValueError('Motors in coord system %d differ' % 522 | (coord, )) 523 | 524 | vlog(verbose, 'Done') 525 | 526 | def program(self, coord_sys, program, 527 | stop=None, start=None, line_label=None): 528 | """ 529 | Start/stop a motion program in coordinate system(s) 530 | """ 531 | if isinstance(coord_sys, (list, tuple)): 532 | coord_sys = ','.join('%d' % c for c in coord_sys) 533 | else: 534 | coord_sys = '%d' % coord_sys 535 | 536 | command = ['&%(coord_sys)s', 'begin%(program)d'] 537 | 538 | if line_label is not None: 539 | command.append('.%(line_label)d') 540 | 541 | if start: 542 | command.append('r') 543 | elif stop: 544 | command.append('abort') 545 | 546 | command = ''.join(command) % locals() 547 | self.send_line(command, sync=True) 548 | 549 | def run_and_wait(self, coord_sys, program, variables=[], 550 | active_var=None, verbose=True, change_callback=None, 551 | read_timeout=5.0, cancel_signal=None, 552 | stop_on_cancel=True): 553 | """ 554 | Run a motion program in a coordinate system. 555 | 556 | Optionally monitor variables (at a low rate) during execution 557 | 558 | May raise GPError when running program if coordinate system/motors 559 | are not ready 560 | 561 | active_var: defaults to Coord[].ProgActive 562 | 563 | returns: coordinate system error status 564 | """ 565 | self.program(coord_sys, program, start=True) 566 | 567 | if active_var is None: 568 | active_var = 'Coord[%d].ProgActive' % program 569 | 570 | vlog(verbose, 'Coord %d Program %d' % (coord_sys, program)) 571 | 572 | active_var = 'Coord[%d].ProgActive' % coord_sys 573 | 574 | def get_active(): 575 | try: 576 | return self.get_variable(active_var, type_=int, 577 | timeout=read_timeout) 578 | except TimeoutError as ex: 579 | vlog(verbose, 'Read active timed out (%s: %s)' % 580 | (active_var, ex)) 581 | return True 582 | 583 | last_values = [self.get_variable(var, timeout=read_timeout) 584 | for var in variables] 585 | 586 | for var, value in zip(variables, last_values): 587 | vlog(verbose, '%s = %s' % (var, value)) 588 | 589 | try: 590 | active = [True, True, True] 591 | while any(active): 592 | active.pop(0) 593 | active.append(get_active()) 594 | 595 | if variables is None or not variables: 596 | time.sleep(0.1) 597 | else: 598 | try: 599 | values = [self.get_variable(var, timeout=read_timeout) 600 | for var in variables] 601 | except TimeoutError as ex: 602 | print('Get variables timed out (%s: %s)' % 603 | (variables, ex)) 604 | continue 605 | 606 | for var, old_value, new_value in zip(variables, 607 | last_values, values): 608 | if old_value != new_value: 609 | vlog(verbose, '%s = %s' % (var, new_value)) 610 | if change_callback is not None: 611 | try: 612 | change_callback(var, old_value, new_value) 613 | except Exception as ex: 614 | logger.error('Change callback failed', 615 | exc_info=ex) 616 | 617 | last_values = values 618 | 619 | if cancel_signal is not None and cancel_signal.is_set(): 620 | if stop_on_cancel: 621 | self.program(coord_sys, program, stop=True) 622 | raise ScriptCancelled('aborted') 623 | raise ScriptCancelled('continuing to run in background') 624 | except KeyboardInterrupt: 625 | if get_active(): 626 | vlog(verbose, "Aborting...") 627 | self.program(coord_sys, program, stop=True) 628 | 629 | raise 630 | 631 | vlog(verbose, 'Done (%s = %s)' % (active_var, get_active())) 632 | 633 | error_status = 'Coord[%d].ErrorStatus' % coord_sys 634 | errno = self.get_variable(error_status, type_=int) 635 | 636 | if errno in const.coord_errors: 637 | error_desc = '({}) {}'.format(errno, const.coord_errors[errno]) 638 | logger.error('Error %s', error_desc) 639 | raise ScriptFailed(error_desc) 640 | 641 | return errno 642 | 643 | def send_program(self, coord, prog_num, motors={}, 644 | macros={}, filename=None, script=None, run=False, 645 | verbose=False, 646 | **kwargs): 647 | """ 648 | Send a program and (optionally) run it in a coordinate system. 649 | """ 650 | self.send_line('&%dabort' % (coord, )) 651 | 652 | opening_lines = ['close all buffers', 653 | 'open prog %d' % prog_num] 654 | closing_lines = ['close'] 655 | 656 | if filename is not None: 657 | logger.debug('Sending script: %s' % filename) 658 | with open(filename, 'rt') as f: 659 | script = f.readlines() 660 | elif script is None: 661 | raise ValueError('Must specify script text or filename') 662 | 663 | if isinstance(script, (list, tuple)): 664 | script = list(script) 665 | else: 666 | script = [script] 667 | 668 | script = opening_lines + script + closing_lines 669 | 670 | if macros: 671 | script = '\n'.join(script) 672 | script = script.format(**macros) 673 | script = script.split('\n') 674 | 675 | for line in script: 676 | if line.rstrip(): 677 | logger.debug('Script line: %s', line.rstrip()) 678 | try: 679 | self.send_line(line.strip()) 680 | except GPError as ex: 681 | logger.error('Failed to send script: %s', ex) 682 | raise 683 | 684 | self.sync() 685 | 686 | if motors: 687 | self.set_coords({coord: motors}, 688 | verbose=verbose, 689 | undefine_coord=True) 690 | 691 | if run: 692 | try: 693 | self.run_and_wait(coord, prog_num, **kwargs) 694 | except GPError as ex: 695 | logger.error('Script run error: %s', ex) 696 | if 'READY TO RUN' in str(ex): 697 | logger.info('Are all motors in the coordinate system in closed' 698 | ' loop?') 699 | raise 700 | 701 | return script 702 | 703 | def run_simple_script(self, fn, macros=None): 704 | if macros is None: 705 | macros = {} 706 | 707 | with open(fn, 'rt') as f: 708 | for line in f.readlines(): 709 | line = line.strip().format(**macros) 710 | if line.startswith('//') or not line: 711 | continue 712 | 713 | self.send_line(line) 714 | 715 | self.sync() 716 | 717 | def monitor_variables(self, variables, f=sys.stdout, 718 | change_callback=None, show_change_set=False, 719 | show_initial=True): 720 | change_set = set() 721 | last_values = self.get_variables(variables, cb=change_callback) 722 | 723 | if show_initial: 724 | for var, value in zip(variables, last_values): 725 | if value is not None: 726 | print('%s = %s' % (var, value), file=f) 727 | 728 | try: 729 | while True: 730 | values = self.get_variables(variables, cb=change_callback) 731 | for var, old_value, new_value in zip(variables, 732 | last_values, values): 733 | if new_value is None: 734 | continue 735 | 736 | if old_value != new_value: 737 | print('%s = %s' % (var, new_value), file=f) 738 | change_set.add(var) 739 | 740 | last_values = values 741 | 742 | except KeyboardInterrupt: 743 | if show_change_set and change_set: 744 | print("Variables changed:", file=f) 745 | for var in sorted(change_set): 746 | print(var, file=f) 747 | 748 | def print_variables(self, variables, cb=None, f=sys.stdout): 749 | values = self.get_variables(variables, cb=cb) 750 | 751 | for var, value in zip(variables, values): 752 | if value is not None: 753 | print('%s = %s' % (var, value), file=f) 754 | 755 | return values 756 | 757 | def get_servo_control(self, motor): 758 | return (1 == self.get_variable('Motor[%d].ServoCtrl' % motor, 759 | type_=int)) 760 | 761 | def set_servo_control(self, motor, enabled): 762 | if enabled: 763 | enabled = 1 764 | else: 765 | enabled = 0 766 | 767 | self.set_variable('Motor[%d].ServoCtrl' % motor, 1) 768 | return self.get_servo_control(motor) 769 | 770 | def motor_hold_position(self, motor): 771 | with self.lock: 772 | self.send_line('#%djog/' % motor, sync=True) 773 | 774 | def jog(self, motor, position, relative=False, wait=True, timeout=2.0): 775 | if relative: 776 | cmd = '^' 777 | else: 778 | cmd = '=' 779 | 780 | with self.lock: 781 | self.send_line('#%djog%s%f' % (motor, cmd, position), sync=True) 782 | 783 | if wait: 784 | t0 = time.time() 785 | while self.get_variable('Motor[%d].InPos' % motor) == 0: 786 | time.sleep(0.1) 787 | 788 | if (time.time() - t0) > timeout: 789 | raise TimeoutError() 790 | 791 | 792 | class PPComm(object): 793 | """ 794 | Power PMAC Communication via ssh/sftp 795 | """ 796 | 797 | def __init__(self, host=config.hostname, port=config.port, 798 | user=config.username, password=config.password, 799 | fast_gather=False, fast_gather_port=config.fast_gather_port): 800 | self._host = host 801 | self._port = port 802 | self._user = user 803 | self._pass = password 804 | 805 | self._fast_gather = fast_gather and (fast_gather_mod is not None) 806 | self._fast_gather_port = fast_gather_port 807 | self._gather_client = None 808 | 809 | self._client = paramiko.SSHClient() 810 | self._client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) 811 | self._client.connect(self._host, self._port, 812 | username=self._user, password=self._pass) 813 | 814 | self.gpascii = self.gpascii_channel() 815 | self._sftp = None 816 | 817 | def __copy__(self): 818 | return PPComm(host=self._host, port=self._port, user=self._user, 819 | password=self._pass, fast_gather=self._fast_gather, 820 | fast_gather_port=self._fast_gather_port) 821 | 822 | def gpascii_channel(self, cmd=None, verbose=False): 823 | """ 824 | Create a new gpascii channel -- an independent 825 | gpascii process running on the remote machine 826 | """ 827 | return GpasciiChannel(self, command=cmd, verbose=verbose) 828 | 829 | def gpascii_file(self, filename, check_errors=True, **kwargs): 830 | """ 831 | Execute a gpascii script by remote filename 832 | """ 833 | ret = self.shell_command('gpascii -i"%s" 2>&1' % filename, **kwargs) 834 | if not check_errors: 835 | return ret 836 | 837 | for line in ret: 838 | if 'error' in line: 839 | raise GPError(line) 840 | 841 | return ret 842 | 843 | def shell_channel(self, cmd=None): 844 | """ 845 | Create a new SSH channel connected to a shell 846 | """ 847 | return ShellChannel(self, cmd) 848 | 849 | def shell_command(self, command, verbose=False, **kwargs): 850 | """ 851 | Execute a command in a remote shell 852 | """ 853 | stdin, stdout, stderr = self._client.exec_command(command, **kwargs) 854 | 855 | def output_lines(): 856 | for line in stdout.readlines(): 857 | yield line 858 | for line in stderr.readlines(): 859 | yield line 860 | 861 | if verbose: 862 | ret = [] 863 | remove_matching = PPMAC_MESSAGES 864 | for line in output_lines(): 865 | skip = False 866 | for regex in remove_matching: 867 | m = regex.match(line) 868 | if m is not None: 869 | skip = True 870 | break 871 | 872 | if not skip: 873 | vlog(verbose, line.rstrip()) 874 | ret.append(line) 875 | 876 | return ret 877 | 878 | else: 879 | return stdout.readlines() 880 | 881 | def shell_output(self, command, wait_match=None, timeout=None, **kwargs): 882 | """ 883 | Execute command, and wait up until timeout 884 | 885 | If wait_match is set to a regular expression, each line 886 | will be compared against it. 887 | """ 888 | stdin, stdout, stderr = self._client.exec_command(command, 889 | timeout=timeout) 890 | 891 | if wait_match is not None: 892 | for line, m in _wait_for(stdout.readlines(), wait_match, **kwargs): 893 | yield line, m 894 | else: 895 | for line in stdout.readlines(): 896 | yield line.rstrip('\n') 897 | 898 | @property 899 | def sftp(self): 900 | """ 901 | The SFTP instance associated with the SSH client 902 | """ 903 | if self._sftp is None: 904 | self._sftp = self._client.open_sftp() 905 | 906 | return self._sftp 907 | 908 | def read_file(self, filename, encoding='ascii'): 909 | """ 910 | Read a remote file, result is a list of lines 911 | """ 912 | with self.sftp.file(filename, 'rb') as f: 913 | if encoding is None: 914 | return f.readlines() 915 | else: 916 | return [line.decode(encoding) for line in f.readlines()] 917 | 918 | def file_exists(self, remote): 919 | """ 920 | Check to see if a remote file exists 921 | """ 922 | 923 | try: 924 | self.sftp.file(remote, 'rb') 925 | except: 926 | return False 927 | else: 928 | return True 929 | 930 | def send_file(self, local, remote): 931 | """ 932 | Send via sftp a local file to the remote machine 933 | """ 934 | self.sftp.put(local, remote) 935 | 936 | def make_directory(self, path): 937 | """ 938 | Create a remote directory 939 | """ 940 | self.sftp.mkdir(path) 941 | 942 | def write_file(self, filename, contents): 943 | """ 944 | Write a remote file with the given contents via sftp 945 | """ 946 | with self.sftp.file(filename, 'wb') as remote_f: 947 | remote_f.write(contents) 948 | 949 | def remove_file(self, filename): 950 | """ 951 | Remove a file on the remote machine 952 | """ 953 | self.sftp.unlink(filename) 954 | 955 | @property 956 | def fast_gather(self): 957 | if not self._fast_gather: 958 | return None 959 | 960 | if self._gather_client is None: 961 | client = self._gather_client = fast_gather_mod.GatherClient() 962 | try: 963 | client.connect((self._host, self._fast_gather_port)) 964 | except Exception as ex: 965 | logger.error('Fast gather client disabled', exc_info=ex) 966 | self._fast_gather = False 967 | self._gather_client = None 968 | else: 969 | client.set_servo_mode() 970 | 971 | return self._gather_client 972 | 973 | @property 974 | def fast_gather_port(self): 975 | return self._fast_gather_port 976 | 977 | 978 | class CoordinateSave(object): 979 | """ 980 | Context manager that saves/restores the current coordinate 981 | system setup 982 | """ 983 | def __init__(self, comm, verbose=True): 984 | self.channel = comm.gpascii 985 | self.verbose = verbose 986 | 987 | def __enter__(self): 988 | self.coords = self.channel.get_coords() 989 | 990 | def __exit__(self, type_, value, traceback): 991 | self.channel.set_coords(self.coords, verbose=self.verbose) 992 | 993 | 994 | def main(): 995 | comm = PPComm() 996 | chan = comm.gpascii_channel() 997 | print('[test] channel opened') 998 | coords = chan.get_coords() 999 | print('[test] coords are', coords) 1000 | # coords = {1: {11: 'x'}, 2: {12: 'x'}, 3: {1: 'x'}} 1001 | chan.set_coords(coords) 1002 | 1003 | # chan = comm.shell_channel() 1004 | passwd = comm.read_file('/etc/passwd', encoding='ascii') 1005 | 1006 | tmp_file = '/tmp/blah' 1007 | 1008 | comm.write_file(tmp_file, ''.join(passwd)) 1009 | 1010 | assert(comm.file_exists(tmp_file)) 1011 | 1012 | read_ = comm.read_file(tmp_file, encoding='ascii') 1013 | 1014 | assert(passwd == read_) 1015 | 1016 | assert(comm.file_exists('/etc/passwd')) 1017 | assert(not comm.file_exists('/asdlfkja')) 1018 | return comm 1019 | 1020 | if __name__ == '__main__': 1021 | logger.setLevel(logging.DEBUG) 1022 | 1023 | logging.basicConfig(format=('%(asctime)s %(name)-12s ' 1024 | '%(levelname)-8s %(message)s'), 1025 | datefmt='%m-%d %H:%M', 1026 | ) 1027 | comm = main() 1028 | -------------------------------------------------------------------------------- /ppmac/tune.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | :mod:`ppmac.tune` -- Ppmac Tuning 4 | ================================= 5 | 6 | .. module:: ppmac.tune 7 | :synopsis: Power PMAC tune utility functions 8 | .. moduleauthor:: Ken Lauer 9 | """ 10 | 11 | from __future__ import print_function 12 | import os 13 | import functools 14 | import logging 15 | 16 | import matplotlib.pyplot as plt 17 | import numpy as np 18 | from .gather import get_gather_results 19 | from . import gather as gather_mod 20 | from . import pp_comm 21 | 22 | 23 | MODULE_PATH = os.path.dirname(os.path.abspath(__file__)) 24 | 25 | OT_RAMP = 1 26 | OT_TRAPEZOID = 2 27 | OT_S_CURVE = 3 28 | 29 | logger = logging.getLogger('ppmac_tune') 30 | 31 | 32 | def custom_tune(gpascii, script_file, motor1=3, distance=0.01, velocity=0.01, 33 | dwell=0.0, accel=1.0, scurve=0.0, prog=999, coord_sys=0, 34 | gather=[], motor2=None, iterations=2, kill_after=False, 35 | **kwargs): 36 | """ 37 | Run a tuning script and return the gathered data 38 | 39 | Returns: gathered_variables, data 40 | """ 41 | 42 | if motor2 is None: 43 | motor2 = motor1 44 | 45 | motor_vars = ['Motor[%d].DesPos.a', 46 | 'Motor[%d].ActPos.a', 47 | 'Motor[%d].IqCmd.a', 48 | ] 49 | 50 | gather_vars = ['Sys.ServoCount.a'] 51 | 52 | gather_vars.extend([m % motor1 for m in motor_vars]) 53 | if motor2 != motor1: 54 | gather_vars.extend([m % motor2 for m in motor_vars]) 55 | 56 | if gather: 57 | gather_vars.extend(list(gather)) 58 | 59 | print('Script file is', script_file) 60 | script = open(script_file, 'rt').read() 61 | script = script % locals() 62 | # print(script) 63 | 64 | def killed(ex): 65 | pass 66 | 67 | comm = gpascii._comm 68 | 69 | with pp_comm.CoordinateSave(comm, verbose=False): 70 | gpascii.set_servo_control(motor1, True) 71 | gpascii.motor_hold_position(motor1) 72 | 73 | coords = {motor1: 'x'} 74 | if motor1 != motor2: 75 | coords = {motor2: 'y'} 76 | 77 | gpascii.set_servo_control(motor2, True) 78 | gpascii.motor_hold_position(motor2) 79 | 80 | gpascii.set_coords({coord_sys: coords}, 81 | undefine_coord=True) 82 | 83 | try: 84 | return gather_mod.run_and_gather(gpascii, script, prog=prog, 85 | coord_sys=coord_sys, 86 | gather_vars=gather_vars, 87 | cancel_callback=killed, 88 | **kwargs) 89 | finally: 90 | if kill_after: 91 | print('Killing motors') 92 | gpascii.kill_motors([motor1, motor2]) 93 | 94 | 95 | def other_trajectory(move_type, motor, distance, velocity=1, accel=1, dwell=0, reps=1, one_direction=False, kill=True): 96 | """ 97 | root@10.0.0.98:/opt/ppmac/tune# ./othertrajectory 98 | You need 9 Arguments for this function 99 | Move type (1:Ramp ; 2: Trapezoidal 3:S-Curve Velocity 100 | Motor Number 101 | Move Distance(cts) 102 | Velocity cts/ms 103 | SAcceleration time (cts/ms^2) 104 | Dwell after move time (ms) 105 | Number of repetitions 106 | Move direction flag (0:move in both direction 1: move in only one direction) in 107 | Kill flag (0 or 1) 108 | """ 109 | print('other trajectory', motor, move_type) 110 | assert(move_type in (OT_RAMP, OT_TRAPEZOID, OT_S_CURVE)) 111 | velocity = abs(velocity) 112 | 113 | args = ['%(move_type)d', 114 | '%(motor)d', 115 | '%(distance)f', 116 | '%(velocity)f', 117 | '%(accel)f', 118 | '%(dwell)d', 119 | '%(reps)d', 120 | '%(one_direction)d', 121 | '%(kill)d', 122 | ] 123 | 124 | args = ' '.join([arg % locals() for arg in args]) 125 | return '%s %s' % (tune_paths['othertrajectory'], args) 126 | 127 | 128 | def plot_tune_results(columns, data, 129 | keys=['Sys.ServoCount.a', 130 | 'Desired', 'Actual', 131 | 'Servo output']): 132 | 133 | data = np.array(data) 134 | idx = [columns.index(key) for key in keys] 135 | x_axis, desired, actual, servo = [data[:, i] for i in idx] 136 | 137 | fig, ax1 = plt.subplots() 138 | ax1.plot(x_axis, desired, color='black', label='Desired') 139 | ax1.plot(x_axis, actual, color='b', label='Actual') 140 | ax1.set_xlabel('Time (s)') 141 | ax1.set_ylabel('Position (motor units)') 142 | for tl in ax1.get_yticklabels(): 143 | tl.set_color('b') 144 | 145 | error = desired - actual 146 | ax2 = ax1.twinx() 147 | ax2.plot(x_axis, error, color='r', alpha=0.4, label='Following error') 148 | ax2.set_ylabel('Error (motor units)') 149 | for tl in ax2.get_yticklabels(): 150 | tl.set_color('r') 151 | 152 | plt.xlim(min(x_axis), max(x_axis)) 153 | plt.show() 154 | 155 | 156 | def run_tune_program(comm, cmd, result_path='/var/ftp/gather/othertrajectory_gather.txt', 157 | timeout=50): 158 | print('Running tune: %s' % cmd) 159 | for line, m in comm.shell_output(cmd, timeout=timeout, 160 | wait_match='^(.*)\s+finished Successfully!$'): 161 | if m is not None: 162 | print('Tune finished: %s' % m.groups()[0]) 163 | break 164 | else: 165 | print(line) 166 | 167 | columns = ['Sys.ServoCount.a', 168 | 'Desired', 169 | 'Actual', 170 | 'Servo output'] 171 | 172 | print('Plotting...') 173 | data = get_gather_results(comm, columns, result_path) 174 | return columns, data 175 | 176 | SERVO_SETTINGS = ['Ctrl', 177 | 'Servo.Kp', 178 | 'Servo.Ki', 179 | 'Servo.Kvfb', 180 | 'Servo.Kvff', 181 | 'Servo.Kviff', 182 | 183 | 'Servo.NominalGain', 184 | 185 | 'Servo.OutDbOn', 186 | 'Servo.OutDbOff', 187 | 'Servo.OutDbSeed', 188 | 'Servo.MaxPosErr', 189 | 'Servo.BreakPosErr', 190 | 'Servo.KBreak', 191 | 'Servo.SwZvInt', 192 | 'Servo.MaxInt', 193 | 194 | # Filter settings 195 | 'Servo.Kc1', 196 | 'Servo.Kd1', 197 | ] 198 | 199 | 200 | def get_settings_variables(completer, index=0): 201 | # Basic default settings 202 | settings = SERVO_SETTINGS 203 | 204 | # But if the completer is available, grab all servo settings through 205 | # introspection 206 | if completer is not None: 207 | try: 208 | motors = getattr(completer, 'Motor') 209 | servo = motors[index].Servo 210 | settings = ['Servo.%s' % setting for setting in dir(servo)] 211 | except Exception as ex: 212 | print('Servo settings from completer failed: (%s) %s' % 213 | (ex.__class__.__name__, ex)) 214 | else: 215 | settings.insert(0, 'Ctrl') 216 | 217 | return settings 218 | 219 | 220 | def get_settings(gpascii, motor, completer=None, settings=None): 221 | settings = get_settings_variables(completer) 222 | 223 | base = 'Motor[%d].' % motor 224 | for setting in sorted(settings): 225 | full_name = '%s%s' % (base, setting) 226 | value = gpascii.get_variable(full_name) 227 | if completer is not None: 228 | obj = completer.check(full_name) 229 | yield obj, value 230 | else: 231 | yield full_name, value 232 | 233 | 234 | def copy_settings(gpascii, motor_from, motor_to, settings=None, completer=None): 235 | if settings is None: 236 | settings = get_settings_variables(completer) 237 | 238 | for setting in settings: 239 | from_ = 'Motor[%d].%s' % (motor_from, setting) 240 | to_ = 'Motor[%d].%s' % (motor_to, setting) 241 | 242 | old_value = gpascii.get_variable(to_) 243 | new_value = gpascii.get_variable(from_) 244 | if old_value != new_value: 245 | gpascii.set_variable(to_, new_value) 246 | print('Set %s to %s (was: %s)' % (to_, new_value, old_value)) 247 | 248 | BIN_PATH = '/opt/ppmac' 249 | TUNE_PATH = os.path.join(BIN_PATH, 'tune') 250 | TUNE_TOOLS = ('analyzerautotunemove', 'autotunecalc', 251 | 'autotunemove', 'chirpmove', 252 | 'currentautotunecalc', 'currentstep', 253 | 'filtercalculation', 'openloopchirp', 254 | 'openloopsine', 'openlooptestmove', 255 | 'othertrajectory', 'parabolicmove', 256 | 'randommove', 'sinesweep', 257 | 'sinusoidal', 'stepmove', 'usertrajectory') 258 | tune_paths = dict((tool, os.path.join(TUNE_PATH, tool)) 259 | for tool in TUNE_TOOLS) 260 | 261 | 262 | def _other_traj(move_type): 263 | @functools.wraps(other_trajectory) 264 | def wrapped(*args, **kwargs): 265 | return other_trajectory(move_type, *args, **kwargs) 266 | return wrapped 267 | 268 | ramp = _other_traj(OT_RAMP) 269 | trapezoid = _other_traj(OT_TRAPEZOID) 270 | s_curve = _other_traj(OT_S_CURVE) 271 | 272 | 273 | def geterrors_motor(motor, time_=0.3, abort_cmd='', m_mask=0x7ac, c_mask=0x7ac, r_mask=0x1e, g_mask=0xffffffff): 274 | exe = '/opt/ppmac/geterrors/geterrors' 275 | args = '-t %(time_).1f -#%(motor)d -m0x%(m_mask)x -c0x%(c_mask)x -r0x%(r_mask)x -g0x%(g_mask)x' % locals() 276 | if abort_cmd: 277 | args += ' -S"%(abort_cmd)s"' 278 | 279 | print(exe, args) 280 | 281 | 282 | if not hasattr(np.fft, 'rfftfreq'): 283 | def rfftfreq(n, d=1.0): 284 | if not isinstance(n, int): 285 | raise ValueError("n should be an integer") 286 | val = 1.0 / (n * d) 287 | N = n // 2 + 1 288 | results = np.arange(0, N, dtype=int) 289 | return results * val 290 | 291 | np.fft.rfftfreq = rfftfreq 292 | 293 | 294 | def plot_custom(columns, data, left_indices=[], right_indices=[], 295 | xlabel='Time [s]', left_label='', 296 | right_label='', x_index=0, 297 | left_colors='bgc', right_colors='rmk', 298 | fft=False, fft_remove_dc=True): 299 | 300 | data = np.array(data) 301 | 302 | x_axis = data[:, x_index] 303 | 304 | if fft: 305 | all_indices = set(left_indices + right_indices) 306 | 307 | start_x = x_axis[0] 308 | end_x = x_axis[-1] 309 | step_x = x_axis[1] - x_axis[0] 310 | new_x = np.arange(start_x, end_x, step_x) 311 | 312 | freqs = np.fft.rfftfreq(len(x_axis), step_x) 313 | 314 | spectra = np.zeros((len(freqs), max(all_indices) + 1), dtype=float) 315 | for col in all_indices: 316 | interpolated = np.interp(new_x, x_axis, data[:, col]) 317 | fft = np.fft.rfft(interpolated) 318 | spectra[:len(fft), col] = np.abs(fft) / len(fft) 319 | 320 | # Remove DC component 321 | if fft_remove_dc: 322 | data = spectra[1:, :] 323 | x_axis = freqs[1:] 324 | else: 325 | data = spectra 326 | x_axis = freqs 327 | 328 | if xlabel.startswith('Time'): 329 | xlabel = 'Frequency [Hz]' 330 | 331 | fig, ax1 = plt.subplots() 332 | if left_indices: 333 | for idx, color in zip(left_indices, left_colors): 334 | ax1.plot(x_axis, data[:, idx], color, label=columns[idx], 335 | alpha=0.7) 336 | ax1.set_xlabel(xlabel) 337 | ax1.set_ylabel(left_label) 338 | for tl in ax1.get_yticklabels(): 339 | tl.set_color(left_colors[0]) 340 | 341 | ax2 = None 342 | if right_indices: 343 | ax2 = ax1.twinx() 344 | for idx, color in zip(right_indices, right_colors): 345 | ax2.plot(x_axis, data[:, idx], color, label=columns[idx], 346 | alpha=0.4) 347 | ax2.set_ylabel(right_label) 348 | for tr in ax2.get_yticklabels(): 349 | tr.set_color(right_colors[0]) 350 | 351 | plt.xlim(min(x_axis), max(x_axis)) 352 | return ax1, ax2 353 | 354 | 355 | def tune_range(gpascii, script_file, parameter, values, **kwargs): 356 | motor = kwargs['motor1'] 357 | if '.' not in parameter: 358 | parameter = 'Motor[%d].Servo.%s' % (int(motor), parameter) 359 | 360 | def calc_rms(addrs, data): 361 | desired_addr = 'motor[%d].despos.a' % motor 362 | actual_addr = 'motor[%d].actpos.a' % motor 363 | 364 | desired, actual = gather_mod.get_columns(addrs, data, 365 | desired_addr, actual_addr) 366 | 367 | err = desired - actual 368 | return np.sqrt(np.sum(err ** 2) / len(desired)) 369 | 370 | rms_results = [] 371 | try: 372 | start_value = gpascii.get_variable(parameter) 373 | for i, value in enumerate(values): 374 | print('%d) Setting %s=%s' % (i + 1, parameter, value)) 375 | gpascii.set_variable(parameter, value) 376 | print('%s = %s' % (parameter, gpascii.get_variable(parameter))) 377 | 378 | addrs, data = custom_tune(gpascii._comm, script_file, **kwargs) 379 | data = np.array(data) 380 | 381 | rms_ = calc_rms(addrs, data) 382 | print('\tDesired/actual position error (RMS): %g' % rms_) 383 | rms_results.append(rms_) 384 | except KeyboardInterrupt: 385 | pass 386 | finally: 387 | gpascii.set_variable(parameter, start_value) 388 | print('Resetting parameter %s = %s' % (parameter, gpascii.get_variable(parameter))) 389 | if rms_results: 390 | i = np.argmin(rms_results) 391 | print('Best %s = %s (error %s)' % (parameter, values[i], rms_results[i])) 392 | return values[i], rms_results 393 | else: 394 | return None, rms_results 395 | 396 | 397 | def main(): 398 | global servo_period 399 | 400 | comm = pp_comm.PPComm() 401 | servo_period = comm.gpascii.servo_period 402 | print('Servo period is', servo_period) 403 | 404 | if 1: 405 | labels, data = custom_tune(comm, 'tune/ramp.txt', 3, 0.01, 0.01, iterations=3, 406 | gather=['Acc24E3[1].Chan[0].ServoCapt.a']) 407 | 408 | data = np.array(data) 409 | data[:, 4] /= 4096 * 512 410 | # gather_mod.plot(gather_vars, data) 411 | ax1, ax2 = plot_custom(labels, data, left_indices=[1, 2], right_indices=[4], 412 | left_label='Position [um]', right_label='Raw encoder [um]') 413 | 414 | plt.title('10nm ramp move') 415 | plt.show() 416 | else: 417 | values = np.arange(20, 55, 0.1) 418 | best, rms = tune_range(comm.gpascii, 'tune/ramp.txt', 'Kp', values, 419 | motor1=3, distance=0.01, velocity=0.01, iterations=3) 420 | 421 | plt.plot(values[:len(rms)], rms) 422 | plt.xlabel('Kp') 423 | plt.ylabel('RMS error') 424 | plt.show() 425 | 426 | 427 | if __name__ == '__main__': 428 | main() 429 | -------------------------------------------------------------------------------- /ppmac/util.py: -------------------------------------------------------------------------------- 1 | # vi:sw=4 ts=4 2 | """ 3 | :mod:`ppmac.util` -- IPython plugin utilities 4 | ============================================= 5 | 6 | .. module:: ppmac.util 7 | .. moduleauthor:: Ken Lauer 8 | 9 | """ 10 | 11 | from __future__ import print_function 12 | import logging 13 | import functools 14 | import math 15 | import inspect 16 | 17 | 18 | class InsList(list): 19 | ''' 20 | Case insensitive list for Power PMAC descriptive addresses, 21 | variables, etc. 22 | 23 | Note that this is not an efficient implementation and should 24 | not be used for large lists. Additionally, it should only be 25 | used to store strings. 26 | ''' 27 | def _get_lower_case(self): 28 | for item in self: 29 | yield item.lower() 30 | 31 | def lower(self): 32 | return InsList(self._get_lower_case()) 33 | 34 | def __contains__(self, item): 35 | return (item.lower() in self._get_lower_case()) 36 | 37 | def index(self, find_item): 38 | find_item = find_item.lower() 39 | for i, item in enumerate(self._get_lower_case()): 40 | if find_item == item: 41 | return i 42 | 43 | raise IndexError(find_item) 44 | 45 | def __getslice__(self, *args): 46 | return InsList(list.__getslice__(self, *args)) 47 | 48 | def __add__(self, *args): 49 | return InsList(list.__add__(self, *args)) 50 | 51 | def __mul__(self, *args): 52 | return InsList(list.__mul__(self, *args)) 53 | 54 | def __copy__(self, *args): 55 | return InsList(self) 56 | 57 | 58 | class PpmacExported(object): 59 | """IPython Ppmac plugin exported function""" 60 | pass 61 | 62 | 63 | def PpmacExport(fcn): 64 | """ 65 | Simple decorator to indicate the function should be exported to the user 66 | namespace 67 | """ 68 | @functools.wraps(fcn) 69 | def wrapped(*args, **kwargs): 70 | return fcn(*args, **kwargs) 71 | 72 | wrapped.decorators = [PpmacExported()] 73 | return wrapped 74 | 75 | 76 | def export_magic_by_decorator(ipython, obj, 77 | magic_arguments=True, modify_name=None, 78 | strip_underscores=True, wrap_fcn=None): 79 | """ 80 | Functions that are decorated with specific decorators will be exported 81 | to the user in IPython. 82 | 83 | :param ipython: the IPython shell instance 84 | :param obj: the namespace (e.g., globals()) to check for functions, 85 | or alternatively, an instance of a class 86 | :param magic_arguments: export functions decorated with magic_arguments 87 | :type magic_arguments: bool 88 | :param strip_underscores: remove underscores from beginning of function 89 | name. This is useful if exported func() and 90 | magic %func both exist. 91 | :param modify_name: callback optionally allowing to change the exported 92 | name. new_name = modify_name(old_name, obj) 93 | strip_underscores is ignored if this is used. 94 | :param wrap_fcn: optionally wrap the function prior to exporting it 95 | """ 96 | all_decorators = set([PpmacExported]) 97 | if magic_arguments: 98 | from IPython.core.magic_arguments import argument as MagicArgument 99 | all_decorators.add(MagicArgument) 100 | 101 | is_instance = not isinstance(obj, dict) 102 | if is_instance: 103 | class_ = obj.__class__ 104 | ns = class_.__dict__ 105 | else: 106 | ns = obj 107 | 108 | for name, o in ns.iteritems(): 109 | if not hasattr(o, 'decorators') or not hasattr(o, '__call__'): 110 | continue 111 | 112 | try: 113 | decorators = set([dec.__class__ for dec in o.decorators]) 114 | except: 115 | continue 116 | 117 | matches = decorators.intersection(all_decorators) 118 | if matches: 119 | if is_instance: 120 | fcn = getattr(obj, name) 121 | else: 122 | fcn = o 123 | 124 | if wrap_fcn is not None: 125 | try: 126 | fcn = wrap_fcn(fcn) 127 | except Exception as ex: 128 | logging.debug('Unable to wrap: %s=%s: %s' % (name, fcn, ex)) 129 | continue 130 | 131 | if modify_name is not None: 132 | name = modify_name(name, fcn) 133 | elif strip_underscores: 134 | name = name.lstrip('_') 135 | 136 | if PpmacExported in matches: 137 | ipython.user_ns[name] = fcn 138 | logging.debug('Function exported: %s=%s' % (name, fcn)) 139 | else: 140 | ipython.define_magic(name, fcn) 141 | logging.debug('Magic defined: %s=%s' % (name, fcn)) 142 | 143 | 144 | def export_class_magic(ipython, instance): 145 | """ 146 | Functions of a class instance that are decorated with specific 147 | decorators will be exported to the user in IPython. 148 | 149 | :param ipython: the IPython shell instance 150 | :param instance: the class instance to check using introspection 151 | """ 152 | def wrap(fcn): 153 | @functools.wraps(fcn) 154 | def wrapped(*args): 155 | return fcn(*args) 156 | return wrapped 157 | 158 | return export_magic_by_decorator(ipython, instance, wrap_fcn=wrap) 159 | 160 | 161 | def tracking_filter(cutoff_freq, damping_ratio=0.7, servo_period=0.442673749446657994): 162 | ''' 163 | Calculate tracking filter according to power pmac manual 164 | ''' 165 | # Tf = 1 / (2. * pi * cutoff_freq) 166 | wn = 2 * math.pi * cutoff_freq 167 | Ts = servo_period 168 | Kp = index2 = 256. - 512. * damping_ratio * wn * Ts 169 | Ki = index1 = 256. * (wn ** 2) * (Ts ** 2) 170 | 171 | index1 = int(index1) 172 | index2 = int(index2) 173 | 174 | if index2 < 0: 175 | index2 = 0 176 | if index1 > 255: 177 | index1 = 255 178 | 179 | Tf = (256 / (256 - index2)) - 1 180 | print('Kp %g Ki %g' % (Kp, Ki)) 181 | print('Time constant: %d servo cycles' % Tf) 182 | return index1, index2 183 | 184 | 185 | class SaveVariable(object): 186 | """ 187 | Context manager which saves the current value of a variable, 188 | then restores it after the context exits. 189 | 190 | The value can be optionally set upon entering the context 191 | by specifying `new_value`. 192 | """ 193 | def __init__(self, gpascii, variable, new_value=None, verbose=False): 194 | self._gpascii = gpascii 195 | self._variable = variable 196 | self._verbose = verbose 197 | self._stored_value = None 198 | self._new_value = new_value 199 | 200 | @property 201 | def current_value(self): 202 | return self._gpascii.get_variable(self._variable) 203 | 204 | def set_value(self, value): 205 | self._gpascii.set_variable(self._variable, value) 206 | 207 | def __enter__(self): 208 | self._stored_value = self.current_value 209 | if self._verbose: 210 | print('Saving %s = %s' % (self._variable, self._stored_value)) 211 | 212 | if self._new_value is not None: 213 | if self._verbose: 214 | print('Setting %s = %s' % (self._variable, self._new_value)) 215 | 216 | self.set_value(self._new_value) 217 | 218 | def __exit__(self, type_, value, traceback): 219 | if self._verbose: 220 | print('Restoring %s = %s (was %s)' % 221 | (self._variable, self._stored_value, self.current_value)) 222 | 223 | self.set_value(self._stored_value) 224 | 225 | 226 | class WpKeySave(SaveVariable): 227 | """ 228 | Context manager which saves the current value of Sys.WpKey, 229 | then allows for system parameters to be modified in the 230 | context block (i.e., Sys.WpKey = $AAAAAAAA). The previous 231 | value is restored after the context exits. 232 | """ 233 | UNLOCK_VALUE = '$AAAAAAAA' 234 | 235 | def __init__(self, gpascii, **kwargs): 236 | SaveVariable.__init__(self, gpascii, 'Sys.WpKey', 237 | new_value=self.UNLOCK_VALUE, **kwargs) 238 | 239 | @property 240 | def current_value(self): 241 | return '$%X' % self._gpascii.get_variable(self._variable, type_=int) 242 | 243 | 244 | def get_caller_module(): 245 | curframe = inspect.currentframe() 246 | calframe = inspect.getouterframes(curframe, 2) 247 | caller_frame = calframe[2][0] 248 | return inspect.getmodule(caller_frame) 249 | 250 | 251 | def vlog(verbose, *args, **kwargs): 252 | '''Verbose logging 253 | 254 | Print output to kwarg `file` if `verbose` is set 255 | 256 | Gets the module's logger and outputs at the debug level in all cases 257 | ''' 258 | if verbose: 259 | print(*args, **kwargs) 260 | 261 | mlogger = logging.getLogger(get_caller_module().__name__) 262 | 263 | kwargs.pop('file', '') 264 | mlogger.debug(*args, **kwargs) 265 | -------------------------------------------------------------------------------- /project/Makefile: -------------------------------------------------------------------------------- 1 | export PPMAC_IP=10.0.0.98 2 | export PPMAC_FILES=motor1.cfg motor2.cfg base.cfg plcs.plc subprog1.pmc piezo_motion.pmc "global definitions.pmh" bgcplc01.c 3 | 4 | all: 5 | rm -rf proj_temp 6 | sh ./upload_config.sh 7 | -------------------------------------------------------------------------------- /project/base.cfg: -------------------------------------------------------------------------------- 1 | // Power PMAC base configuration 2 | // ** Should be loaded after all motor configuration files 3 | 4 | Sys.WpKey = $AAAAAAAA 5 | 6 | Sys.ServoPeriod=0.442673749446657994 7 | 8 | Sys.WpKey = 0 9 | 10 | M0,16384->* 11 | 12 | i100=0 13 | i200=0 14 | -------------------------------------------------------------------------------- /project/bgcplc_makefile: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------ 2 | # Copyright (C) Delta Tau Data Systems Inc., 2007 3 | # All rights reserved. 4 | # 5 | # Generic makefile for any c realtime C plc 0, user servo or user phase 6 | # For a new project change the following 7 | # 8 | # 1.) SRCS should be assigned the 'C' source code files that need to be compiled 9 | # 2.) issue the command 'make depend' the first time a project is created and 10 | # (every time an additional 'C' file is added to the project the command 11 | # 'make depend' must be issued) 12 | # 3.) issue the command make clean 13 | # 4.) issue the command make 14 | # 15 | # Notes 16 | # -------- 17 | # Change DTDEBUG above to -O2 for release w/ optimization 18 | # Change DTDEBUG above to -g3 for debug 19 | #------------------------------------------------------------------------------ 20 | CC = /usr/bin/gcc 21 | CPP = /usr/bin/g++ 22 | LD = /usr/bin/gcc 23 | 24 | CFLAGS = -mhard-float -funsigned-char -funroll-loops \ 25 | -I/opt/ppmac/rtpmac \ 26 | -I/opt/ppmac/libppmac \ 27 | -I/usr/local/xenomai/include \ 28 | -I/usr/local/xenomai/include/posix \ 29 | -D_GNU_SOURCE -D_REENTRANT -D__XENO__ 30 | 31 | DTDEBUG = %(dt_debug_flags)s 32 | 33 | LDFLAGS = -shared -lpthread_rt -lrt \ 34 | \ 35 | -L../../../bin/%(build_type)s/ \ 36 | -L/usr/local/xenomai/lib \ 37 | -lppmac -L/opt/ppmac/libppmac \ 38 | -Wl,-rpath,/var/ftp/usrflash/Project/C\ Language/Libraries \ 39 | 40 | RM = rm -f 41 | PROG = %(output_fn)s 42 | SRCS = %(source_files)s 43 | 44 | OBJS = $(SRCS:.c=.o) 45 | export CROSS_COMPILE=ppc_4xxFP- 46 | export ARCH=powerpc 47 | 48 | # now comes a meta-rule for compiling any C source file. 49 | $(PROG): $(OBJS) 50 | $(LD) -o $(PROG) $(OBJS) $(LDFLAGS) 51 | 52 | %%.o: %%.c 53 | #$(CPP) $(CFLAGS) $(DTDEBUG) -c $< 54 | $(CC) $(CFLAGS) $(DTDEBUG) -c $< 55 | 56 | 57 | clean: 58 | $(RM) $(PROG) $(OBJS) 59 | 60 | depend: 61 | $(RM) ../../bin/%(build_type)s/dependencyko.lst 62 | makedepend -f- -- $(CFLAGS) -- $(SRCS) > ../bin/%(build_type)s/dependencyko.lst 63 | 64 | #ifneq ($(MAKECMDGOALS),depend) 65 | #include ../bin/%(build_type)s/dependencyko.lst 66 | #endif 67 | 68 | 69 | -------------------------------------------------------------------------------- /project/global definitions.pmh: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/klauer/ppmac/d1cca48dd825c3b2bc9a6605ed0bf5890305c831/project/global definitions.pmh -------------------------------------------------------------------------------- /project/load_project.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # no ntpdate on the machine unfortunately 4 | DATE=$1 5 | if [ -n "$DATE" ]; then 6 | echo "Setting date to $DATE..." 7 | date --set="$DATE" 8 | fi 9 | 10 | PRE_MAKE_CFG=/var/ftp/usrflash/pre_make.cfg 11 | POST_MAKE_CFG=/var/ftp/usrflash/post_make.cfg 12 | USRALGO=/var/ftp/usrflash/usralgo.ko 13 | 14 | read -d '' PYSCRIPT <<"EOF" 15 | # Quick script to flatten project filenames so vim will recognize them 16 | import os 17 | import sys 18 | import re 19 | 20 | for line in sys.stdin.readlines(): 21 | line = line.rstrip() 22 | m = re.match('^(\/var/ftp/usrflash/.*?): (.*)$', line) 23 | if m: 24 | fn, rest = m.groups() 25 | print '%s: %s' % (os.path.split(fn)[1], rest) 26 | else: 27 | print line 28 | EOF 29 | 30 | function run_command { 31 | $@ | grep -v -e "UnlinkGatherThread" -e "*** EOF" | sed '/^[[:cntrl:]]$/d' > /dev/stdout 32 | } 33 | 34 | 35 | if [ -f $PRE_MAKE_CFG ] 36 | then 37 | echo "Running pre-make configuration..." 38 | run_command gpascii -i${PRE_MAKE_CFG} 39 | echo 40 | fi 41 | 42 | echo "Compiling all C source..." 43 | #find . -name Makefile -exec dirname {} \; | xargs -I '{}' make -C {} clean 44 | find /var/ftp/usrflash -name Makefile -exec dirname {} \; | xargs -I '{}' make -C {} 45 | 46 | if [ -f $USRALGO ] 47 | then 48 | echo Removing kernel module: $USRALGO 49 | rmmod $USRALGO 50 | # 2> /dev/null 51 | fi 52 | 53 | find /var/ftp/usrflash -name Makefile -exec dirname {} \; | xargs -I '{}' make -C {} before_projpp 54 | 55 | # Load the project, but strip out control characters in the response 56 | run_command projpp 57 | echo 58 | 59 | grep -v -e "unknown escape sequence" -e "PMAC_PROJECT" -e "redefined" -e "location of the previous" /var/ftp/usrflash/Project/Log/pp_error.log | python -c "${PYSCRIPT}" 60 | 61 | find /var/ftp/usrflash -name Makefile -exec dirname {} \; | xargs -I '{}' make -C {} after_projpp 62 | 63 | if [ -f $USRALGO ] 64 | then 65 | echo Inserting kernel module: $USRALGO 66 | insmod $USRALGO 67 | lsmod |grep usralgo 68 | fi 69 | 70 | if [ -f $POST_MAKE_CFG ] 71 | then 72 | echo "Running post-make configuration..." 73 | run_command gpascii -i${POST_MAKE_CFG} 74 | echo 75 | fi 76 | 77 | if [ -f /var/ftp/usrflash/load_delay.cfg ] 78 | then 79 | echo "Running delayed final configuration..." 80 | sleep 10 81 | run_command gpascii -i/var/ftp/usrflash/load_delay.cfg 82 | echo 83 | fi 84 | -------------------------------------------------------------------------------- /project/make_project.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | import os 3 | import sys 4 | import shlex 5 | import shutil 6 | 7 | if len(sys.argv) == 1: 8 | print('Usage: %s temp_project_path/ project_file1 [project_file2...]' % sys.argv[0]) 9 | print('NOTE: temp_project_path will first be removed then recreated when making a project.') 10 | sys.exit(1) 11 | 12 | PROJ_TEMPLATE = ''' 13 | # This section loads all Power PMAC programs & is run through the pre-processor/CmdProcessor. 14 | [PMAC_PROGRAMS] 15 | %(pmac_programs)s 16 | 17 | [LINUX_PROGRAMS] 18 | %(linux_programs)s 19 | 20 | [RTUSRCCODE] 21 | %(rtusrcode)s 22 | 23 | [PMAC_BUFFERS] 24 | TableBufSize=1048576 25 | UserBufSize=1048576 26 | LookAheadBufSize=16777216 27 | ProgramBufSize=16777216 28 | 29 | [CUSTOM_CONFIG_FILE] 30 | %(custom_config_file)s 31 | ''' 32 | 33 | remote_base_path = '/var/ftp/usrflash' 34 | 35 | 36 | def get_c_path(base_path, fn): 37 | no_ext = os.path.splitext(fn)[0] 38 | if fn.startswith('bgcplc'): 39 | return 'Project/C Language/CPLCs/%s' % no_ext 40 | elif fn.startswith('usr_'): 41 | return 'Project/C Language/Realtime Routines/%s' % no_ext 42 | else: 43 | raise 44 | 45 | 46 | def get_pmc_path(base_path, fn): 47 | options = [('Project/PMAC Script Language/Libraries', 'open subprog'), 48 | ('Project/PMAC Script Language/Motion Programs', None)] 49 | 50 | subdir = None 51 | file_contents = open(fn, 'rt').read() 52 | for path, to_grep in options: 53 | if to_grep is None or to_grep in file_contents: 54 | subdir = path 55 | break 56 | 57 | if subdir is None: 58 | raise ValueError('No default path / strings not found for %s' % fn) 59 | 60 | return subdir 61 | 62 | 63 | def get_cfg_path(base_path, fn): 64 | if fn in ('pre_make.cfg', 'post_make.cfg'): 65 | return '' 66 | elif fn.startswith('load_delay'): 67 | return '' 68 | else: 69 | return 'Project/Configuration' 70 | 71 | ext_paths = {'.plc': 'Project/PMAC Script Language/PLC Programs', 72 | '.pmh': 'Project/PMAC Script Language/Global Includes', 73 | '.pmc': get_pmc_path, 74 | '.cfg': get_cfg_path, 75 | '.ini': 'Project/Configuration', 76 | '.c': get_c_path, 77 | '.h': 'Project/C Language/Include', 78 | } 79 | 80 | 81 | def get_paths(base_path, fn, include_fn=False): 82 | ext = os.path.splitext(fn)[1] 83 | just_fn = os.path.split(fn)[1] 84 | 85 | if ext in ext_paths: 86 | subdir = ext_paths[ext] 87 | else: 88 | raise ValueError('Unknown file extension (%s) ignoring' % (fn)) 89 | 90 | if hasattr(subdir, '__call__'): 91 | subdir = subdir(base_path, fn) 92 | 93 | print('%s -> %s' % (fn, subdir)) 94 | try: 95 | local_dir = os.path.join(base_path, subdir) 96 | #print("Making path:", local_dir) 97 | os.makedirs(local_dir) 98 | except OSError: 99 | pass 100 | 101 | if include_fn: 102 | subdir = os.path.join(subdir, just_fn) 103 | local_dir = os.path.join(base_path, subdir) 104 | 105 | remote_dir = os.path.join(remote_base_path, subdir) 106 | 107 | return local_dir, remote_dir 108 | 109 | 110 | def create_makefile(local_path, release=True, template='bgcplc_makefile'): 111 | if release: 112 | dt_debug_flags = '-O2' 113 | build_type = 'Release' 114 | else: 115 | dt_debug_flags = '-g3' 116 | build_type = 'Debug' 117 | 118 | source_files = [f for f in os.listdir(local_path) 119 | if f.endswith('.c')] 120 | source_files = ' '.join(source_files) 121 | 122 | subdir = os.path.split(local_path)[-1] 123 | if subdir.startswith('bgcplc'): 124 | output_fn = '/var/ftp/usrflash/Project/C\ Language/CPLCs/user/libplcc%d.so' % int(subdir[-2:]) 125 | elif subdir.startswith('usr_'): 126 | output_fn = '/var/ftp/usrflash/Project/C\ Language/Realtime\ Routines/%s.so' % subdir 127 | else: 128 | raise NotImplementedError('Unknown type: %s' % subdir) 129 | 130 | if not os.path.exists(template): 131 | raise ValueError('Makefile template %s does not exist' % template) 132 | 133 | template = open(template, 'rt').read() 134 | template = template % locals() 135 | 136 | makefile_path = os.path.join(local_path, 'Makefile') 137 | with open(makefile_path, 'wt') as f: 138 | print(template, file=f) 139 | 140 | 141 | def fix_path(base_path, source): 142 | try: 143 | local_dir, remote_dir = get_paths(base_path, source) 144 | except Exception as ex: 145 | print('* Failed: %s (%s)' % (source, ex)) 146 | return 147 | 148 | source_fn = os.path.split(source)[1] 149 | local_file = os.path.join(local_dir, source_fn) 150 | remote_file = os.path.join(remote_dir, source_fn) 151 | 152 | shutil.copyfile(source, local_file) 153 | return local_file, remote_file 154 | 155 | 156 | def output_config(base_path, project_files, release=True): 157 | pmac_programs = [] 158 | linux_programs = [] 159 | rtusrcode = [] 160 | makefile_paths = set([]) 161 | for i, fn in enumerate(project_files): 162 | if not os.path.exists(fn): 163 | print('* Failed: Unable to open %s' % fn) 164 | continue 165 | 166 | local_file, remote_file = fix_path(base_path, fn) 167 | fn, file_ext = os.path.splitext(fn) 168 | if file_ext in ('.c', ): 169 | if 'Realtime' in remote_file: 170 | rtusrcode.append('file%d=%s' % (len(rtusrcode) + 1, remote_file)) 171 | else: 172 | linux_programs.append('file%d=%s' % (len(linux_programs) + 1, remote_file)) 173 | makefile_paths.add(os.path.split(local_file)[0]) 174 | elif file_ext in ('.h', ): 175 | # Don't add it to the project ini file 176 | pass 177 | else: 178 | if fn not in ('pre_make', 'post_make', 'load_delay'): 179 | pmac_programs.append('file%d=%s' % (len(pmac_programs) + 1, remote_file)) 180 | 181 | for path in makefile_paths: 182 | print('Creating makefile in', path) 183 | create_makefile(path, release=release) 184 | 185 | local_cfg, remote_cfg = get_paths(base_path, 'pp_proj.ini', include_fn=True) 186 | 187 | pmac_programs.append('last_file_number=%d' % i) 188 | pmac_programs = '\n'.join(pmac_programs) 189 | linux_programs = '\n'.join(linux_programs) 190 | rtusrcode = '\n'.join(rtusrcode) 191 | # rtusrcode = '' 192 | custom_config_file = '' 193 | 194 | print('Configuration in', local_cfg) 195 | f = open(local_cfg, 'wt') 196 | print(PROJ_TEMPLATE % locals(), file=f) 197 | f.close() 198 | 199 | base_path = sys.argv[1] 200 | project_files = ' '.join(sys.argv[2:]) 201 | project_files = shlex.split(project_files, ' ') 202 | 203 | if os.path.relpath(base_path) == '.': 204 | print('Do not use the script directory, use a subdirectory for the base path') 205 | sys.exit(1) 206 | 207 | output_config(base_path, project_files) 208 | print('Configuration done') 209 | -------------------------------------------------------------------------------- /project/plcs.plc: -------------------------------------------------------------------------------- 1 | open plc 7 2 | close 3 | 4 | //enable plc 7; 5 | 6 | -------------------------------------------------------------------------------- /project/subprog1.pmc: -------------------------------------------------------------------------------- 1 | // Sleep for time ms 2 | open subprog sleep(time) 3 | local EndTime; 4 | local count; 5 | 6 | EndTime = Sys.Time + time; 7 | do { 8 | count++; 9 | } 10 | 11 | while (EndTime > Sys.Time); 12 | close 13 | -------------------------------------------------------------------------------- /project/upload_config.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | 4 | if [ -z "${PPMAC_IP}" ] 5 | then 6 | echo "Usage: $0" 7 | echo "Be sure to set PPMAC_IP, PPMAC_PW, PPMAC_TEMPDIR, PPMAC_FILES environment variables" 8 | exit 1 9 | fi 10 | 11 | : ${PPMAC_IP:=10.0.0.98} 12 | : ${PPMAC_PW:=deltatau} 13 | : ${PPMAC_FILES:="plcs.plc subprog1.pmc piezo_motion.pmc $1"} 14 | : ${PPMAC_TEMPDIR:="proj_temp"} 15 | : ${PPMAC_OTHER:=""} 16 | 17 | echo "--------------------------------------------" 18 | echo "Power PMAC IP: $PPMAC_IP Password: $PPMAC_PW" 19 | echo "Project files: $PPMAC_FILES" 20 | echo "Temporary project directory: $PPMAC_TEMPDIR" 21 | echo "Other project directories/files: $PPMAC_OTHER" 22 | echo "--------------------------------------------" 23 | echo 24 | 25 | if [ -z "${PPMAC_IP}" ] 26 | then 27 | echo "Usage: $0" 28 | echo "Be sure to set PPMAC_IP, PPMAC_PW, PPMAC_TEMPDIR, PPMAC_FILES environment variables" 29 | exit 1 30 | fi 31 | 32 | mkdir $PPMAC_TEMPDIR 33 | cp load_project.sh $PPMAC_TEMPDIR 34 | 35 | if [ -n "${PPMAC_OTHER}" ] 36 | then 37 | cp -RL $PPMAC_OTHER $PPMAC_TEMPDIR 38 | fi 39 | 40 | echo "--- Copying configuration files" 41 | echo 42 | echo "--- Copying project, PLCs" 43 | echo python make_project.py ${PPMAC_TEMPDIR} ${PPMAC_FILES} 44 | python make_project.py ${PPMAC_TEMPDIR} ${PPMAC_FILES} 45 | sshpass -p $PPMAC_PW rsync -az ${PPMAC_TEMPDIR}/* root@${PPMAC_IP}:/var/ftp/usrflash 46 | sshpass -p $PPMAC_PW ssh root@${PPMAC_IP} "chmod 755 /var/ftp/usrflash/load_project.sh; /var/ftp/usrflash/load_project.sh '$(date)'" 47 | 48 | echo "--- Done" 49 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | 3 | try: 4 | from setuptools import setup 5 | except ImportError: 6 | try: 7 | from setuptools.core import setup 8 | except ImportError: 9 | from distutils.core import setup 10 | 11 | 12 | setup(name='ppmac', 13 | version='0.0.1', 14 | author='klauer', 15 | packages=['ppmac'], 16 | install_requires=['paramiko>=1.13', 'numpy>=1.8', 'six'], 17 | ) 18 | --------------------------------------------------------------------------------