Import LinuxCNC source tree

This commit is contained in:
Wang Dequan
2026-08-20 06:17:39 +08:00
commit 2c814634c3
9429 changed files with 10556013 additions and 0 deletions

13
scripts/.gitignore vendored Normal file
View File

@@ -0,0 +1,13 @@
linuxcnc
linuxcnc-checklink
linuxcnc_info
rip-environment
halrun
haltcl
realtime
rtapi.conf
runtests
linuxcncmkdesktop
linuxcnc_var
halcmd_twopass
setup_designer

118
scripts/cppcheck.sh Executable file
View File

@@ -0,0 +1,118 @@
#!/bin/bash
if ! command -v cppcheck > /dev/null; then
echo "E: Please install the program 'cppcheck' prior to executing this script."
exit 1
fi
nproc=2
if command -v nproc > /dev/null; then
nproc=$(nproc)
fi
# See if cppcheck accepts --check-level
EXHAUSTIVE=$(cppcheck --check-level=exhaustive --version > /dev/null 2>&1 && echo "--check-level=exhaustive")
CPPCHKOPT=( -j "$nproc" --force "$EXHAUSTIVE" --inline-suppr )
CPPCHKOPT+=( "--enable=warning,performance,portability" )
CPPCHKOPT+=( "-I$(realpath "$(dirname "$0")/../include")" )
CPPCHKOPT+=( --error-exitcode=1 )
if [ -n "$CPPCHECK_OPTS" ]; then
read -r -a OPTS <<< "$CPPCHECK_OPTS"
CPPCHKOPT+=( "${OPTS[@]}" )
fi
# Even cppcheck 2.3 (debian 11) supports c++17 (undocumented)
CCSTD=( --std=c11 --language=c )
CXSTD=( --std=c++17 --language=c++ )
CPPCHKCC=( "${CPPCHKOPT[@]}" "${CCSTD[@]}" )
CPPCHKCX=( "${CPPCHKOPT[@]}" "${CXSTD[@]}" )
# Only process individual files if passed on the command line.
if [ $# -gt 0 ]; then
retval=0
for f in "$@"; do
if [ -r "$f" ]; then
case "$f" in
*.hh|*.cc)
cppcheck -I"$(dirname "$f")" "${CPPCHKCX[@]}" "$f" || retval=1
;;
*.h|*.c)
cppcheck -I"$(dirname "$f")" "${CPPCHKCC[@]}" "$f" || retval=1
;;
esac
else
echo "Cannot read file '$f'"
retval=1
fi
done
exit $retval
fi
# Do the rest from the source directory
cd "$(dirname "$0")/../src" || { echo "Could not change directory to '$(dirname "$0")/../src'"; exit 1; }
docheck() {
local rv
rv=0
mapfile -t files < <(find "$1" -maxdepth 1 -name "*.c")
if [ "${#files[@]}" -gt 0 ]; then
cppcheck -I"$1" "${CPPCHKCC[@]}" "${files[@]}" || rv=1
fi
mapfile -t files < <(find "$1" -maxdepth 1 -name "*.cc")
if [ "${#files[@]}" -gt 0 ]; then
cppcheck -I"$1" "${CPPCHKCX[@]}" "${files[@]}" || rv=1
fi
return $rv
}
# Collected returns; will be zero if all succeed
result=0
# *** HAL files ***
echo "I (1/4): checking HAL folders with both C and C++ code"
while IFS= read -r -d '' d
do
# Don't care about examples
case "$d" in hal/user_comps/mb2hal/examples*) continue;; esac
echo "I (1/4): checking $d"
docheck "$d" || result=1
done < <(find hal/ -type d -not -name "*__pycache__" -print0)
# *** EMC files ***
echo "I (2/4): checking EMC folders with both C and C++ code"
while IFS= read -r -d '' d
do
# Will give Tcl problems
case "$d" in emc/usr_intf/axis/extensions*) continue;; esac
echo "I (2/4): checking $d"
docheck "$d" || result=1
done < <(find emc/ -type d -not -name "*__pycache__" -print0)
# *** NML files ***
echo "I (3/4): checking LIBNML folders with both C and C++ code"
while IFS= read -r -d '' d
do
echo "I (3/4): checking $d"
docheck "$d" || result=1
done < <(find libnml/ -type d -not -name "*__pycache__" -print0)
# *** RTAPI files ***
echo "I (4/4): checking RTAPI folders with both C and C++ code"
while IFS= read -r -d '' d
do
echo "I (4/4): checking $d"
docheck "$d" || result=1
done < <(find rtapi/ -type d -not -name "*__pycache__" -print0)
if [ $result -gt 0 ]; then
echo "ERROR: Issues found"
else
echo "No issues found"
fi
exit $result

47
scripts/decode_dmesg Executable file
View File

@@ -0,0 +1,47 @@
#!/usr/bin/env python3
"""\
This script decodes hex floats (e.g., in dmesg) into real floats. In the
future maybe it will do other emc2-specific dmesg decoding.
Usage:
dmesg | decode_dmesg
cat /var/log/kern.log | decode_dmesg > output.txt
"""
import re
import sys
def repl(m):
s, w, f, e = m.group("s", "w", "f", "e")
f = f or "0"
v = long(w, 16) + long(f, 16) * (16 ** -len(f))
try:
v *= 2.0**long(e)
except OverflowError:
v = float("inf")
if s: v = -v
return "%g" % v
pat = re.compile('(?i)(?P<s>-?)0[xX]'
'(?P<w>[0-9A-Fa-f])'
'(\\.(?P<f>[0-9A-Fa-f]*))?[Pp](?P<e>[+-]?[0-9]+)')
for line in sys.stdin:
line = pat.sub(repl, line)
sys.stdout.write(line)
# Copyright 2010 Jeff Epler <jepler@unpythonic.net>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

84
scripts/docs-anchor-check.py Executable file
View File

@@ -0,0 +1,84 @@
#!/usr/bin/env python3
# Flag section references relying on auto-generated AsciiDoc ids:
# <<_derived_id>> xrefs and link:...html#_derived_id URLs. Derived ids come
# from the section title, so the reference breaks when the title is
# translated or retitled. Pin the target with an explicit [[anchor]] instead.
# Pass 1 collects explicit anchor definitions in docs/src, pass 2 reports
# derived-id references with no explicit target.
# Warn-only unless --enforce or DOCS_ANCHOR_CHECK_ENFORCE is set.
import os
import re
import sys
import glob
# The script lives in scripts/, the docs are one level up at ../docs, so it runs from anywhere.
HERE = os.path.dirname(os.path.realpath(__file__, strict=True))
DOCS = os.path.normpath(os.path.join(HERE, '..', 'docs'))
SRC = os.path.join(DOCS, 'src')
# Explicit anchor definition forms, mirroring ANCHOR_DEF in
# docs/src/extensions/xref_resolver.rb, plus the anchor: macro.
ANCHOR_DEF = re.compile(r"""
\[\[ ([A-Za-z_][\w:.-]*) (?:,[^\]]*)? \]\] | # [[id]] or [[id,reftext]]
\[\# ([A-Za-z_][\w:.-]*) (?:[.%][^\]]*)? \] | # [#id]
\[ (?:[^,\]]*,\s*)* id\s*=\s*["']? ([A-Za-z_][\w:.-]*) ["']? [,\]] | # [id="foo"]
^anchor: ([A-Za-z_][\w:.-]*) \[\] | # anchor:id[]
^:id:\s* ([A-Za-z_][\w:.-]*) # :id: foo
""", re.X | re.M)
# Derived-id references: <<_foo>> / <<_foo,Title>> xrefs and
# link:...html#_foo[...] URL fragments. Namespaced anchors carry a ':', so a
# target that starts with '_' and has no ':' is a derived id by convention.
XREF = re.compile(r'<<(_[A-Za-z0-9][\w.-]*)(?:,.*?)?>>')
LINKURL = re.compile(r'link:[^\s\[]*#(_[A-Za-z0-9][\w.-]*)\[')
def collect(adoc_files):
defined = {} # anchor -> file
for path in adoc_files:
text = open(path, encoding='utf-8', errors='replace').read()
for m in ANCHOR_DEF.finditer(text):
anchor = next(g for g in m.groups() if g)
defined.setdefault(anchor, path)
return defined
def find_derived_refs(adoc_files, defined):
problems = []
for path in adoc_files:
lines = open(path, encoding='utf-8', errors='replace').read().splitlines()
for lineno, line in enumerate(lines, 1):
for regex in (XREF, LINKURL):
for m in regex.finditer(line):
target = m.group(1)
if target not in defined:
rel = os.path.relpath(path, DOCS)
problems.append((rel, lineno, m.group(0)[:60], target))
return problems
def main():
enforce = '--enforce' in sys.argv or os.environ.get('DOCS_ANCHOR_CHECK_ENFORCE')
adoc_files = sorted(glob.glob(f'{SRC}/**/*.adoc', recursive=True))
defined = collect(adoc_files)
problems = find_derived_refs(adoc_files, defined)
if not problems:
return 0
out = ['Derived-id section references with no explicit anchor target',
'(these break in translated docs when the target title is translated;',
'pin the target section with an explicit [[anchor]] and reference that):',
'']
for rel, lineno, ref, target in problems:
out.append(f'{rel}:{lineno}: {ref}')
out.append(f' target `[[{target}]]` is not explicitly defined anywhere in docs/src')
text = '\n'.join(out)
print(text)
if os.environ.get('GITHUB_ACTIONS'):
summary = os.environ.get('GITHUB_STEP_SUMMARY')
if summary:
with open(summary, 'a', encoding='utf-8') as f:
f.write('## Derived-id anchor check\n\n```\n' + text + '\n```\n')
level = 'error' if enforce else 'warning'
print(f'::{level} title=Derived-id anchor check::{len(problems)} reference(s) rely on auto-generated section ids, see job summary')
return 1 if enforce else 0
if __name__ == '__main__':
sys.exit(main())

View File

@@ -0,0 +1,12 @@
#!/bin/bash
if [ -n "$EMC2_HOME" ]; then
source "$EMC2_HOME"/scripts/githelper.sh
else
source "$(git rev-parse --show-toplevel)"/scripts/githelper.sh
fi
githelper "$1"
echo "$DEB_COMPONENT"

29
scripts/get-version-from-git Executable file
View File

@@ -0,0 +1,29 @@
#!/bin/bash
if [ -n "$EMC2_HOME" ]; then
source "$EMC2_HOME"/scripts/githelper.sh
else
source "$(git rev-parse --show-toplevel)"/scripts/githelper.sh
fi
githelper "$1"
if [ "$DEB_COMPONENT" = "scratch" ]; then
DESCRIBE=$(git describe --tags --exact-match 2>/dev/null)
if [ -n "$DESCRIBE" ]; then
echo "$DESCRIBE"
else
DESCRIBE=$(git describe --tags 2>/dev/null)
if [ -n "$DESCRIBE" ]; then
echo "$DESCRIBE"
else
BR=$(printf '%s' "$GIT_BRANCH" | tr '[:upper:]' '[:lower:]' | sed -E 's/[^-.+:~a-z0-9]/-/g; s/-{2,}/-/g; s/^-//; s/-$//')
echo "v$(git show HEAD:VERSION | cut -d ' ' -f 1)~${BR:-head}~$(git show --pretty=format:%h HEAD | head -1)"
fi
fi
else
# known branches get the "describe" of the most recent signed git tag,
# or of the most recent unsigned tag if no signed tags are found
git describe --match "$GIT_TAG"
fi

102
scripts/githelper.sh Normal file
View File

@@ -0,0 +1,102 @@
#
# This is a bash shell fragment, intended to be sourced by scripts that
# want to work with git in the emc2 repo.
#
# Sets GIT_BRANCH to the passed in branch name; if none is passed in it
# attempts to detect the current branch (this will fail if the repo is in a
# detached HEAD state).
#
# Sets DEB_COMPONENT based on the branch. Official release branches get
# their own component, all other branches go in "scratch".
#
# Sets GIT_TAG to the most recent signed tag (this will fall back to the
# most recent tag of any kind if no signed tag is found).
#
# shellcheck shell=bash
function githelper() {
if [ -z "$1" ]; then
GIT_BRANCH=$(git symbolic-ref -q --short HEAD 2>/dev/null)
if [ -z "$GIT_BRANCH" ]; then
GIT_BRANCH=$(git rev-parse --abbrev-ref HEAD)
fi
else
GIT_BRANCH="$1"
fi
case $GIT_BRANCH in
master)
MM=$(git show HEAD:VERSION | sed -E 's/^([0-9]+)\.([0-9]+).*/\1.\2/')
GIT_TAG_GLOB="v${MM}.*"
DEB_COMPONENT="master"
;;
# release branches have names matching "number.number", which is awkward to express as a glob
[0-9].[0-9] | [0-9].[0-9][0-9] | [0-9].[0-9][0-9][0-9] | [0-9][0-9].[0-9] | [0-9][0-9].[0-9][0-9] | [0-9][0-9].[0-9][0-9][0-9])
GIT_TAG_GLOB="v${GIT_BRANCH}.*"
DEB_COMPONENT=$GIT_BRANCH
;;
v2.5_branch)
GIT_TAG_GLOB="v2.5*"
DEB_COMPONENT="v2.5_branch"
;;
v2.4_branch)
GIT_TAG_GLOB="v2.4*"
DEB_COMPONENT="v2.4_branch"
;;
*)
GIT_TAG_GLOB="*"
# Disable unused variable warnings on DEB_COMPONENT
# shellcheck disable=SC2034
DEB_COMPONENT="scratch"
;;
esac
# use the gnupg keyring from our git repo to verify signatures on the release tags
GNUPGHOME=$(git rev-parse --show-toplevel)/gnupg
export GNUPGHOME
NEWEST_SIGNED_TAG_UTIME=-1
NEWEST_UNSIGNED_TAG_UTIME=-1
for TAG in $(git tag -l "$GIT_TAG_GLOB"); do
if ! git cat-file tag "$TAG" > /dev/null 2> /dev/null; then
continue
fi
TAG_UTIME=$(git cat-file tag "$TAG" | grep tagger | awk '{print $(NF-1)-$NF*36}')
if git tag -v "$TAG" > /dev/null 2> /dev/null; then
# it's a valid signed tag
if [ "$TAG_UTIME" -gt "$NEWEST_SIGNED_TAG_UTIME" ]; then
NEWEST_SIGNED_TAG=$TAG
NEWEST_SIGNED_TAG_UTIME=$TAG_UTIME
fi
else
# unsigned tag
if [ "$TAG_UTIME" -gt "$NEWEST_UNSIGNED_TAG_UTIME" ]; then
NEWEST_UNSIGNED_TAG=$TAG
NEWEST_UNSIGNED_TAG_UTIME=$TAG_UTIME
fi
fi
done
if [ "$NEWEST_SIGNED_TAG_UTIME" -gt -1 ]; then
# Disable unused variable warnings on GIT_TAG
# shellcheck disable=SC2034
GIT_TAG="$NEWEST_SIGNED_TAG"
return
fi
if [ "$NEWEST_UNSIGNED_TAG_UTIME" -gt -1 ]; then
echo "no signed tags found, falling back to unsigned tags" > /dev/null 1>&2
# Disable unused variable warnings on GIT_TAG
# shellcheck disable=SC2034
GIT_TAG="$NEWEST_UNSIGNED_TAG"
return
fi
echo "no annotated tags found, not even unsigned" > /dev/null 1>&2
}

82
scripts/gladevcp_demo Executable file
View File

@@ -0,0 +1,82 @@
#!/bin/bash
# This file is used for scripts that demonstrate gladevcp
# The scripts typically use an extension of .demo
# 1) start realtime
# 2) run glacevcp with supplied parameters
# 3) stop realtime
# Copyright: 2014
# Author: Dewey Garrett <dgarrett@panix.com>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
prog=$(basename "$0")
function usage () {
cat <<EOF
Usage:
$prog [gladevcp options]
gladevcp_demo: start realtime and invoke gladevcp
The help for gladvcp shows its options:
gladevcp --help
$(gladevcp -h)
EOF
exit 1
} ;# usage
function popup () {
msg="$*"
wish <<EOF &
wm withdraw .
tk_messageBox \
-title "$prog" \
-message "$msg" \
-icon error \
-type ok
destroy .
EOF
} ;# popup
# begin----------------------------------------------------
REALTIME=$(linuxcnc_var REALTIME)
$REALTIME status >/dev/null
status=$?
if [ "$status" = 0 ] ; then
msg="$prog: Realtime is already active"
echo "$msg"
popup "$msg"
exit 1
else
$REALTIME start
fi
case $# in
0) usage;;
*) ;;
esac
if [ -n "$debug" ] ; then
echo "debug=$debug"
echo "REALTIME=$REALTIME"
echo "ARGS=$*"
pwd
fi
gladevcp "$@"
halrun -U

1019
scripts/hal-histogram Executable file

File diff suppressed because it is too large Load Diff

410
scripts/hal_demo Executable file
View File

@@ -0,0 +1,410 @@
#!/bin/bash
#********************************************************************
# Description: hal_demo
#
# This script is used to test/demo the Hardware Abstraction Layer
#
# There are actually a number of demos, and right now they may
# change frequently. To avoid cluttering up the scripts directory,
# this file contains all the demos. Select the one you want by
# giving an argument. For example, "hal_demo jogwheel" will run
# the jogwheel demo.
#
# if you don't specify a demo, this prints a usage message
#
# Author:
# License: GPL Version 2
# System: Linux
#
# Copyright (c) 2004-2009 All rights reserved.
#*******************************************************************/
if [ -z "$1" ] ; then
echo "Usage: scripts/hal_demo <demo_name>"
echo "Run from the emc2 directory only"
# print the list of demos
scripts/hal_demo list
exit 0
fi
#
# the first demo isn't a real demo - it simply lists all the demos
#
if [ "$1" = "list" ] ; then
echo "Available demos are:"
echo " list - prints this list"
echo " load - loads hal core modules"
echo " unload - unloads hal code modules"
echo " parport1 - an introductory demo - step by step"
echo " siggen - another introductory demo - signal generator"
echo " scope - simple demo of halscope"
exit 0
fi
#
# The next two demos aren't really demos either - they simply
# load or unload the rtapi environment
#
# here is the "load" command
#
if [ "$1" = "load" ] ; then
# load the rtos and rtapi
if ! scripts/realtime start ; then
exit 1
fi
# done
echo "RTAPI core loaded"
exit 0
fi
#
# here is the "unload" command
#
if [ "$1" = "unload" ] ; then
# unload any left-over modules
bin/halcmd unloadrt all
# unload the rtos and rtapi
if ! scripts/realtime stop ; then
exit 1
fi
# done
echo "RTAPI core unloaded"
exit 0
fi
#
# We're almost ready for the real demos, first we must
# make sure the rtapi core is loaded
#
HAL_LIB_STR=$(/sbin/lsmod | awk '{print $1}' | grep -x hal_lib)
if [ -z "$HAL_LIB_STR" ] ; then
echo "All of the demos require the RTAPI and HAL_LIB modules"
echo "to be loaded. Use 'hal_demo load' to load them."
exit 1
fi
#
# The first demo: "parport1" simply tests the parallel port
# this one is a step by step, tutorial/intro to HAL concepts
#
# Don't care about read mangling backslashes, nothing read is used
# shellcheck disable=SC2162
if [ "$1" = "parport1" ] ; then
echo "(Comments that are part of the demo are in parenthesis.)"
echo "(This demo echos its commands to the screen so you can"
echo " see what each step does. Echoed commands start with '$')"
echo "Hit enter to install the parport module"
read
# change the 0278 if your port is at a different address
echo "$ bin/halcmd loadrt hal_parport cfg=\"0278\""
bin/halcmd loadrt hal_parport cfg="0278"
echo "Hit enter see the component list"
read
echo "$ bin/halcmd show comp"
bin/halcmd show comp
echo "Hit enter to create a 1mS thread"
read
echo "$ bin/halcmd loadrt threads name1=parport.thread period1=1000000"
bin/halcmd loadrt threads name1=parport.thread period1=1000000
echo "Hit enter see the threads and functions"
read
echo "$ bin/halcmd show thread"
bin/halcmd show thread
echo "(the period is in nano-seconds)"
echo "$ bin/halcmd show funct"
bin/halcmd show funct
echo "Hit enter to continue"
read
echo "(connecting the parport read and write"
echo " functions to the 1mS thread)"
echo "$ bin/halcmd addf parport.0.read parport.thread"
bin/halcmd addf parport.0.read parport.thread
echo "$ bin/halcmd addf parport.0.write parport.thread"
bin/halcmd addf parport.0.write parport.thread
echo "(start the realtime thread)"
echo "$ bin/halcmd start"
bin/halcmd start
echo "Hit enter see the threads and functions again"
read
echo "$ bin/halcmd show thread"
bin/halcmd show thread
echo "(thread now has functions attached)"
echo "$ bin/halcmd show funct"
bin/halcmd show funct
echo "(functions have a non-zero users count)"
echo "Hit enter to see the pins"
read
echo "$ bin/halcmd show pin"
bin/halcmd show pin
echo "Hit enter to see the parameters"
read
echo "$ bin/halcmd show param"
bin/halcmd show param
echo "Hit enter to create some signals"
read
echo "$ bin/halcmd newsig jumper1 bit"
bin/halcmd newsig jumper1 bit
echo "$ bin/halcmd newsig jumper2 bit"
bin/halcmd newsig jumper2 bit
echo "(signals created - lets look at them)"
echo "$ bin/halcmd show sig"
bin/halcmd show sig
echo "Hit enter to connect signals to pins"
read
echo "(use signal jumper1 to connect input pin 10 to output pin 2)"
echo "$ bin/halcmd linksp jumper1 parport.0.pin-10-in"
bin/halcmd linksp jumper1 parport.0.pin-10-in
echo "$ bin/halcmd linksp jumper1 parport.0.pin-02-out"
bin/halcmd linksp jumper1 parport.0.pin-02-out
echo "(connections made - lets take a look)"
echo "$ bin/halcmd show sig"
bin/halcmd show sig
echo "(If you have a breakout board or other access to the port"
echo " you can apply a signal to pin 10 and verify that pin 2"
echo " follows it - updated every 1mS )"
echo "Hit enter to add another output"
read
echo "$ bin/halcmd linksp jumper1 parport.0.pin-03-out"
bin/halcmd linksp jumper1 parport.0.pin-03-out
echo "(input pin 10 is now routed to output pins 2 and 3 )"
echo "$ bin/halcmd show sig"
bin/halcmd show sig
echo "Hit enter to invert the pin 3 output"
read
echo "(invert pin 3 by setting a parameter of the parport module)"
echo "$ bin/halcmd setp parport.0.pin-03-out-invert TRUE"
bin/halcmd setp parport.0.pin-03-out-invert TRUE
echo "(if you have access to the pins, you can verify"
echo " that pin 3 is now inverted)"
echo "Hit enter to see the parameters"
read
echo "$ bin/halcmd show param"
bin/halcmd show param
echo "Hit enter to try halmeter"
read
echo "(running halmeter, a GUI application - it should"
echo " appear in another window.)"
echo "(click on the select button, then select a pin, signal,"
echo " or parameter to observe. You can change what you are"
echo " observing at any time.)"
echo "(the meter updates approx. every 0.1 seconds - if you"
echo " have access to the pins, change the input and halmeter"
echo " will show the changes.)"
echo "(an interesting parameter to observe is parport.thread.time"
echo " which is the most recent execution time of the thread in"
echo " nanoseconds. you should be able to see it change a little"
echo " due to jitter and other factors."
echo "(Click exit to shut down halmeter.)"
echo "$ bin/halmeter"
bin/halmeter
echo "Hit enter to finish the demo"
read
echo "(deleting signals)"
echo "$ bin/halcmd delsig jumper1"
bin/halcmd delsig jumper1
echo "$ bin/halcmd delsig jumper2"
bin/halcmd delsig jumper2
echo "(unloading realtime modules)"
echo "$ bin/halcmd unloadrt all"
bin/halcmd unloadrt all
exit 0
fi
#
# The next demo: "siggen" tests the signal generator component
# and halmeter.
# this one is also a step by step tutorial
#
# Don't care about read mangling backslashes, nothing read is used
# shellcheck disable=SC2162
if [ "$1" = "siggen" ] ; then
echo "(Comments that are part of the demo are in parenthesis.)"
echo "(This demo echos its commands to the screen so you can"
echo " see what each step does. Echoed commands start with '$')"
echo "Hit enter to install the siggen module"
read
echo "$ bin/halcmd loadrt siggen"
bin/halcmd loadrt siggen
echo "Hit enter see the component list"
read
echo "$ bin/halcmd show comp"
bin/halcmd show comp
echo "Hit enter to create a 1mS thread"
read
echo "$ bin/halcmd loadrt threads name1=siggen.thread period1=1000000"
bin/halcmd loadrt threads name1=siggen.thread period1=1000000
echo "Hit enter see the threads and functions"
read
echo "$ bin/halcmd show thread"
bin/halcmd show thread
echo "(the period is in nano-seconds)"
echo "$ bin/halcmd show funct"
bin/halcmd show funct
echo "Hit enter to continue"
read
echo "(connecting the siggen update function to the 1mS thread)"
echo "$ bin/halcmd addf siggen.0.update siggen.thread"
bin/halcmd addf siggen.0.update siggen.thread
echo "(start the realtime thread)"
echo "$ bin/halcmd start"
bin/halcmd start
echo "Hit enter see the threads and functions again"
read
echo "$ bin/halcmd show thread"
bin/halcmd show thread
echo "(thread now has function attached)"
echo "$ bin/halcmd show funct"
bin/halcmd show funct
echo "(function has a non-zero users count)"
echo "Hit enter to see the pins"
read
echo "$ bin/halcmd show pin"
bin/halcmd show pin
echo "Hit enter to see the parameters"
read
echo "$ bin/halcmd show param"
bin/halcmd show param
echo "Hit enter to change the frequency to 0.1Hz"
read
echo "$ bin/halcmd setp siggen.0.frequency 0.1"
bin/halcmd setp siggen.0.frequency 0.1
echo "Hit enter to see the parameters"
read
echo "$ bin/halcmd show param"
bin/halcmd show param
echo "Hit enter to try halmeter"
read
echo "(running halmeter, a GUI application - it should"
echo " appear in another window.)"
echo "(it will initially display the sine signal, click"
echo " on the select button, then select a pin, signal, or"
echo " parameter to observe. You can change what you are"
echo " observing at any time.)"
echo "(the meter updates approx. every 0.1 seconds - if you"
echo " select one of the output pins, you should see it go"
echo " through a complete cycle every 10 seconds, since we"
echo " set the frequency to 0.1Hz.)"
echo "(Click exit to shut down halmeter.)"
echo "$ bin/halmeter pin siggen.0.sine"
bin/halmeter pin siggen.0.sine
echo "Hit enter to finish the demo"
read
echo "(unloading realtime modules)"
echo "$ bin/halcmd unloadrt all"
bin/halcmd unloadrt all
exit 0
fi
#
# The next demo: "scope" lets the user play with the scope
# using siggen as a signal source.
# this one is also a step by step tutorial
#
# Don't care about read mangling backslashes, nothing read is used
# shellcheck disable=SC2162
if [ "$1" = "scope" ] ; then
echo "The first part of this demo is identical to the siggen"
echo "demo. Hit enter to get started"
read
echo "First we load the siggen demo (not echoed)"
bin/halcmd loadrt siggen
bin/halcmd loadrt threads name1=siggen.thread period1=1000000
bin/halcmd addf siggen.0.update siggen.thread
bin/halcmd start
bin/halcmd setp siggen.0.frequency 10
echo "Then we load the realtime component of halscope:"
echo "$ bin/halcmd loadrt scope_rt"
bin/halcmd loadrt scope_rt
echo "Finally we start the user component (in the background"
echo "so that the foreground can continue to give instructions):"
echo "$ bin/halscope &"
bin/halscope &
echo "HALSCOPE should appear in a new window. Select this "
echo "window again and hit enter for step by step instructions"
read
echo "First we need to set the sample rate. Click on then name"
echo "of a realtime thread (this example has only one)."
echo "You can optionally change the multiplier if you don't"
echo "want to sample every time the thread runs. You can also"
echo "change the record length to allow for more channels. For"
echo "the demo, you want at least 4 channels, so change it."
echo "When you are done, click OK, then come back to this"
echo "window and hit enter for the next step."
read
echo "Now we need to select the pins/signals/parameters that"
echo "we are going to observe. The 16 buttons in a vertical"
echo "column are the channel select buttons. Click #1"
echo "In the dialog that pops up, select a pin - perhaps the"
echo "triangle output of the siggen. Note that the channel"
echo "number and name appear in the lower left of the scope"
echo "window. This is the selected (highlighted) channel."
echo "Now click on another channel number like #3, and select"
echo "another pin, perhaps the sine output. Then come back"
echo "to this window and hit enter for the next step"
read
echo "Now you have a couple channels. To start sampling, click"
echo "on NORMAL in the 'Run Mode' window in the upper right."
echo "The state display at the top right of the display will"
echo "change from IDLE to PRE-TRIG as the scope acquires"
echo "pre-trigger data. The status display will show a bar"
echo "that represents the portion of the record that has been"
echo "captured. The state will change to TRIGGER? when the"
echo "pre-trigger phase is done, to show that it is waiting "
echo "for a trigger. Currently the only working trigger source"
echo "is the manual one, so click the 'Force' button to cause"
echo "a trigger. You can also click on 'Auto', which causes"
echo "the scope to trigger automatically after a delay. Fully"
echo "functional triggering is under construction. When the"
echo "scope triggers, the state will change to TRIGGERED and"
echo "the bar will show progress as it acquires post-trigger"
echo "data. When it is done, the waveforms will display, and"
echo "the scope will begin acquiring data again. Hit enter"
echo "for the next step."
read
echo "The signals are likely to be near the top of the screen"
echo "if you used a low numbered channel. Use the position"
echo "slider in the vertical menu (lower right) to move the"
echo "selected signal (the green one) down. Use the gain"
echo "slider to scale it if you wish. Note the scale displayed"
echo "below the slider - it has a very wide range. When the"
echo "selected channel is adjusted to your liking, click on"
echo "the channel number for the other channel to select it."
echo "(Note the name and number at the bottom left.) Adjust"
echo "the second channels vertical position and gain, then"
echo "hit enter for the next step"
read
echo "Now that vertical is set, lets look at horizontal. The"
echo "horizontal controls are above the screen. Below the"
echo "sliders and sample rate display is a graphic that shows"
echo "the display as a large box, and the data record as a"
echo "smaller box (probably half or all black). Move the zoom"
echo "slider one click to the right. Note that the large box"
echo "is now shorter. Now move the position slider, and see"
echo "how the large box moves with respect to the smaller one."
echo "This display quickly shows you what portion of the record"
echo "you are viewing. Zoom in some more to clearly see the"
echo "sine and triangle waveforms. Hit enter for the next step."
read
echo "If you want to change the sample rate, use the 'Change"
echo "Rate/Len' button, which brings up the same dialog that"
echo "appeared when the program started. To add more channels,"
echo "click on additional channel number buttons. If you want"
echo "to turn a channel off, select it and click the 'Channel"
echo "Off' button (the signal source, position, etc, will be"
echo "remembered if you turn it back on again. To change the"
echo "signal captured by a channel, select the channel and click"
echo "the 'Source' button. An interesting item to look at is"
echo "the parameter 'siggen.thread.time', which is the time"
echo "needed by the thread, in nano-seconds. It is updated"
echo "every time the thread runs, and varies based on cache hits"
echo "and other loading. Its value will be in the thousands"
echo "of nanoseconds (a few microseconds), so the trace will"
echo "initially be off scale. Set the scale to 5K/div or so to"
echo "make it visible. Hit enter for the next step."
read
echo "To shut down the scope, use the [X] button in the corner"
echo "Hit enter to finish the demo"
read
bin/halcmd unloadrt all
exit 0
fi
#
#
# if we get here, the user asked for a non-existent demo
#
echo "Sorry, there is no demo called '$1'"
scripts/hal_demo
exit 1

View File

@@ -0,0 +1,5 @@
#!/bin/sh
HALLIB_DIR=@HALLIB_DIR@; export HALLIB_DIR
LINUXCNC_TCL_DIR=@EMC2_TCL_DIR@; export LINUXCNC_TCL_DIR
if [ $# -ne 1 ]; then echo 1>&2 Usage: "$0" inifile; exit 1; fi
exec haltcl -i "$1" @EMC2_TCL_LIB_DIR@/twopass.tcl

981
scripts/halreport Executable file
View File

@@ -0,0 +1,981 @@
#!/usr/bin/tclsh
#-----------------------------------------------------------------------
# Copyright: 2018
# Author: Dewey Garrett <dgarrett@panix.com>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#-----------------------------------------------------------------------
set hallib_dir [exec linuxcnc_var HALLIB_DIR]
source [file join $hallib_dir hal_procs_lib.tcl]
package require Hal
#-----------------------------------------------------------------------
# Notes:
# 1) supports components made by halcompile and numerous
# legacy components
# 2) Known unhandled components:
# at_pid -- naming conflicts with pid, seldom used
# boss_plc -- no manpage or docs (any users?)
# watchdog -- seldom used (no users in-tree)
# 3) deprecated/obsolete components
# counter
# supply
set ::HR(separator) \
"-----------------------------------------------------------------------"
#-----------------------------------------------------------------------
# Identificaion of functions used according to pin name.
# Default handling works for components that:
# 1) use names=|count= (.comp components created with halcompile)
# 2) have a *single* function
# Lists of other known components by category:
# motion module
set ::HR(comps,motion) [list motion axis joint spindle]
# kinematics modules that export hal pins:
# (kinematics are invoked by motion module (motmod))
set ::HR(comps,kinematics) [list tripodkins scarakins rotatekins maxkins \
genserkins genhexkins \
xyzbc-trt-kins xyzac-trt-kins \
rosekins 5axiskins pumakins \
rotarydeltakins lineardeltakins \
]
# Legacy components that:
# 1) do not use names=|count=
# 2) support multiple numbered instances
# 3) have a single,fixed function name numbered per instance
set ::HR(comps,legacy_comp) [list debounce mux-gen sampler streamer]
# userspace components including vismach guis
set ::HR(comps,userspace) [list ini iocontrol halui hal_manualtoolchange \
axisui touchy gscreen gmoccapy panelui \
pyvcp gladevcp \
xhc-hb04 \
pumagui puma560gui scaragui hexagui \
5axisgui max5gui maho600gui hbmgui\
xyzac-trt-gui xyzbc-trt-gui \
lineardelta rotarydelta \
3axis-tutorial \
]
# components marked deprecated/obsolete in docs
set ::HR(comps,deprecated) [list counter supply]
#-----------------------------------------------------------------------
# The following ::HR(COMPONENT_NAME,pins,FUNCT) identify
# component pins that are handled by different functions
# usually requiring multiple thread types (typ: base,servo)
set ::HR(encoder,pins,update-counters) [list \
phase-A \
phase-B \
phase-Z \
rawcounts \
x4-mode \
latch-input \
latch-rising \
latch-falling \
counter-mode \
]
set ::HR(encoder,pins,capture-position) [list \
reset \
min-speed-estimate \
velocity \
counts \
counts-latched \
position \
position-interpolated \
position-latched \
position-scale \
index-enable \
]
set ::HR(sim-encoder,pins,make-pulses) [list \
phase-A \
phase-B \
phase-Z \
rawcounts \
ppr \
]
set ::HR(sim-encoder,pins,update-speed) [list \
scale \
speed \
]
set ::HR(pwmgen,pins,make-pulses) [list \
pwm \
up \
down \
]
set ::HR(pwmgen,pins,update) [list \
curr-dc \
dither-pwm \
enable \
max-dc \
min-dc \
offset \
pwm-freq \
scale \
value \
]
set ::HR(stepgen,pins,make-pulses) [list \
counts \
dir \
step \
]
# slow: stepgen.update-freq & stepgen.capture-position
set ::HR(stepgen,pins,slow) [list \
enable \
position-cmd \
position-fb \
]
# all output pins, slow
set ::HR(encoder-ratio,pins,update) [list \
error \
]
# all input pins, fast:
set ::HR(encoder-ratio,pins,sample) [list \
master-ppr \
master-teeth \
slave-ppr \
slave-teeth \
master-A \
master-B \
slave-A \
slave-B \
]
#-----------------------------------------------------------------------
proc unadded_functs {} {
set header_len 2
set ans [hal show funct]
set lines [split $ans \n]
set lines [lreplace $lines 0 [expr $header_len -1]]
set lines [lreplace $lines end end]
set ct 0
set not_added {}
foreach line $lines {
if {"$line" == ""} continue
set users [lindex $line 4]
set fname [lindex $line 5]
if {$users == 0} {lappend not_added $fname}
}
return $not_added
} ;# unadded_functs
proc make_addf_list {} {
set ::HR(pin_list) [hal list pin]
set ::HR(threads) [hal list thread]
set ::HR(addf) {}
foreach thd $::HR(threads) {
set header_len 3
set ans [hal show thread $thd]
set lines [split $ans \n]
set lines [lreplace $lines 0 [expr $header_len -1]]
set lines [lreplace $lines end end]
set ct 0
foreach line $lines {
if {"$line" == ""} continue
lappend ::HR(addf) "[lindex $line 1] $thd"
}
}
} ;# make_addf_list
proc make_pin_alias_list {a_to_pin_name pin_to_a_name} {
upvar $a_to_pin_name a_to_pin
upvar $pin_to_a_name pin_to_a
set header_len 2
set ans [hal show alias]
set lines [split $ans \n]
set lines [lreplace $lines 0 [expr $header_len -1]]
set lines [lreplace $lines end end]
set ct 0
foreach line $lines {
if {"$line" == ""} continue
if {[string first Parameter $line] == 0} break
set line [string trim $line]
set a_to_pin([lindex $line 0]) [lindex $line 1]
set pin_to_a([lindex $line 1]) [lindex $line 0]
}
} ;# make_pin_alias_list
proc find_pin {p} {
if {[lsearch $::HR(pin_list) $p] >= 0} {return 1}
return 0
} ;# find_pin
proc find_standard_pin {pin_prefix instance candidate} {
foreach try [list "${pin_prefix}.${candidate}" \
"${pin_prefix}.${instance}.${candidate}"] {
if [find_pin "$try"] {return 1}
}
return 0
} ;# find_standard_pin
proc form_for_pin {pin_prefix instance psuffix} {
if {[find_pin ${pin_prefix}.$psuffix] } {return names}
if {[find_pin ${pin_prefix}.${instance}.${psuffix}]} {return count}
return ""
} ;#form_for_pin
proc find_funct {pin_prefix instance args} {
set DOT "."
if {$args == "{}" } {
set DOT ""
}
foreach candidate $args {
foreach try [list "${pin_prefix}${DOT}${candidate}" \
"${pin_prefix}.${instance}${DOT}${candidate}"] {
if {[lsearch $::HR(functs) "$try"] >= 0} {return $try}
}
}
return ""
} ;# find_funct
#-----------------------------------------------------------------------
proc funct_for_userspace_comps {pname} {
# known userspace components that export pins
foreach prefix $::HR(comps,userspace) {
if {[string first ${prefix}. $pname] == 0} {
lappend ans [list "notRT" "---" "$prefix"]
return $ans
}
}
return ""
} ;# funct_for_userspace_comps
proc funct_for_motion {pname dir pin_prefix instance pin_suffix} {
# 1) find by assumed unique pin_prefix per list
# 3) fixed-name functions: motion-command-handler,motion-controller
foreach prefix $::HR(comps,motion) {
if {[string first ${prefix}. $pname] == 0} {
return [list motion-command-handler motion-controller]
}
}
return ""
} ;# funct_for_motion
proc funct_for_kinematics {pname dir pin_prefix pin_suffix nstance} {
if {[lsearch $::HR(comps,kinematics) ${pin_prefix}] >= 0} {
return [list motion-command-handler motion-controller]
}
return ""
} ;#funct_for_kinematics
proc funct_for_pid {pname dir pin_prefix instance pin_suffix} {
# 1) num_chan=|count=
# 2) find by unique pin name: *.do-pid-calcs.time
# 3) fixed funct name: .do-pid-calcs
# NB: pid name clash/ambiguous with at_pid (seldom used)
set lidx [string last . $pname]
set fnames [string range $pname 0 [expr $lidx -1]]
if {[hal list pin ${fnames}.do-pid-calcs.time] != ""} {
return ${fnames}.do-pid-calcs
}
return ""
} ;# funct_for_pid
proc funct_for_legacy_comp {pname dir pin_prefix instance pin_suffix} {
# 1) fixed pin name prefix per list
# 2) funct by rule: pin==xxx.N.yyy.zzz, funct=xxx.N
foreach prefix $::HR(comps,legacy_comp) {
if {[string first ${prefix}. $pname] < 0} {continue}
set p_list [split $pname .]
return "[lindex $p_list 0].[lindex $p_list 1]"
}
return ""
} ;# funct_for_legacy_comp
proc funct_for_weighted_sum {pname dir pin_prefix instance pin_suffix} {
# 1) wsum_sizes=size[,size,...]
# 2) find by unique pin_prefix: wsum.
# 3) fixed funct name: process_wsums
if {[string first wsum. $pname] == 0} {
return process_wsums
}
return ""
} ;# funct_for_weighted_sum
proc funct_for_offset {pname dir pin_prefix instance pin_suffix} {
# NB: the offset component reads the offset pin on *both* functs
# 1) count=|names=
# 2) find by reqd_pin
# 3) funct names by rules for form and pin_suffix
set reqd_pin fb-out
set form [form_for_pin $pin_prefix $instance $reqd_pin]
switch $form {
names {set fprefix $pin_prefix}
count {set fprefix ${pin_prefix}.${instance}}
default {return ""}
}
set fnames ""
switch "$pin_suffix" {
fb-out -
fb-in {set fnames "${fprefix}.update-feedback"}
out -
in {set fnames "${fprefix}.update-output"}
offset {set fnames [list ${fprefix}.update-feedback \
${fprefix}.update-output]}
}
return "$fnames"
} ;# funct_for_offset
proc funct_for_ppmc {pname dir pin_prefix instance pin_suffix} {
# 1) find by unique pin name prefix: ppmc.
# 2) funct names by rule for dir
if {[string first "ppmc." $pname] >= 0} {
set fidx [string first . $pname]
set fprefix [string range $pname 0 [expr $fidx +1]]
switch $dir {
IN {set fnames ${fprefix}.write}
OUT {set fnames ${fprefix}.read}
* {return -code error "funct_for_ppmc: unexpected $dir $pname"}
}
return $fnames
}
return ""
} ;# funct_for_ppmc
proc funct_for_stepgen {pname dir pin_prefix instance pin_suffix} {
# 1) step_type-type0[,type1 ...]
# 2) funct names by rule for pin name
if {"$pin_prefix" != "stepgen"} return
if {[lsearch $::HR(stepgen,pins,make-pulses) $pin_suffix] >= 0} {
return "stepgen.make-pulses"
}
if {[lsearch $::HR(stepgen,pins,slow) $pin_suffix] >= 0} {
return [list stepgen.update-freq stepgen.capture-position]
}
return ""
} ;# funct_for_stepgen
proc funct_for_pwmgen {pname dir pin_prefix instance pin_suffix} {
# 1) output_type=type0[,type1 ...]
# 2) funct by rule per pin name suffix
if {"$pin_prefix" != "pwmgen"} return
if {[lsearch $::HR(pwmgen,pins,make-pulses) $pin_suffix] >= 0} {
return "pwmgen.make-pulses"
}
if {[lsearch $::HR(pwmgen,pins,update) $pin_suffix] >= 0} {
return "pwmgen.update"
}
return ""
} ;# funct_for_pwmgen
proc funct_for_matrix_kb {pname dir pin_prefix instance pin_suffix} {
# 1) config= [names=] (no num_chan= or count=)
# 2) find by unique reqd_pin
# 3) funct names per form
set reqd_pin keycode
if {[find_pin ${pin_prefix}.$reqd_pin]} {
return ${pin_prefix} ;# form: names
} elseif {[find_pin ${pin_prefix}.${instance}.$reqd_pin]} {
return ${pin_prefix}.${instance} ;# form: num_chan
}
return ""
} ;# funct_for_matrix_kb
proc funct_for_encoder {pname dir pin_prefix instance pin_suffix} {
# 0) encoder or sim_encoder
# 1) num_chan=|names=
# 2) find by unique reqd_pin
# 3) funct names per pin_suffix
if {[string first counter $pname] == 0} {return ""} ;# reject counter
set reqd_pin phase-B ;# encoder,sim_encoder,counter(deprecated)
if ![find_standard_pin $pin_prefix $instance $reqd_pin] {return ""}
set is_sim 0
if { [find_pin ${pin_prefix}.ppr] \
|| [find_pin ${pin_prefix}.${instance}.ppr] } {
set is_sim 1
}
if $is_sim {
if {[lsearch $::HR(sim-encoder,pins,make-pulses) $pin_suffix] >= 0} {
return "sim-encoder.make-pulses"
}
if {[lsearch $::HR(sim-encoder,pins,update-speed) $pin_suffix] >= 0} {
return "sim-encoder.update-speed"
}
} else {
if {[lsearch $::HR(encoder,pins,update-counters) $pin_suffix] >= 0} {
return "encoder.update-counters"
}
if {[lsearch $::HR(encoder,pins,capture-position) $pin_suffix] >= 0} {
return "encoder.capture-position"
}
}
return ""
} ;# funct_for_encoder
proc funct_for_encoder_ratio {pname dir pin_prefix instance pin_suffix} {
# 0) encoder or sim_encoder
# 1) num_chan=|names=
# 2) find by unique reqd_pin
# 3) funct names per pin_suffix
if {[string first counter $pname] == 0} {return ""}
set reqd_pin master-A
if ![find_standard_pin $pin_prefix $instance $reqd_pin] {return ""}
if {[lsearch $::HR(encoder-ratio,pins,sample) $pin_suffix] >= 0} {
return "encoder-ratio.sample"
}
if {[lsearch $::HR(encoder-ratio,pins,update) $pin_suffix] >= 0} {
return "encoder-ratio.update"
}
return ""
} ;# funct_for_encoder_ratio
proc funct_for_parport {pname dir pin_prefix instance pin_suffix} {
# 1) cfg=
# 2) find by reqd_pin
# 3) funct names by dir and rule
set reqd_pin pin-16-out
if ![find_standard_pin $pin_prefix $instance $reqd_pin] {return ""}
switch $dir {
IN { set fnames [find_funct $pin_prefix $instance write write-all]
if {"$fnames" == ""} {
return -code error "funct_for_parport: problem $dir $pname"
}
}
OUT { set fnames [find_funct $pin_prefix $instance read read-all]
if {"$fnames" == ""} {
return -code error "funct_for_parport: problem $dir $pname"
}
}
}
return $fnames
} ;# funct_for_parport
proc funct_for_hm2 {pname dir pin_prefix instance pin_suffix} {
# 1) config=
# 2) find by unique pin name prefix: hm2_*
# 3) funct names by rule for dir and gpio in pin name
# NB: not handled funct_name == read-request
# NB: not handled funct_name == trigger-encoders
# pin names: hm2_<BoardType>.<BoardNum>.*
# funct names: hm2_<BoardType>.<BoardNum>.function_name
set fnames ""
if {[string first hm2_ $pname] == 0} {
set pname_parse [string map {. " "} $pname]
set BoardType [lindex "$pname_parse" 0]
set BoardNum [lindex "$pname_parse" 1]
set funct_prefix "${BoardType}.${BoardNum}"
switch $dir {
IN {
if { ([string first .gpio. $pname] >=0 ) \
&& ([lsearch $::HR(functs) "${funct_prefix}.read_gpio"] >= 0)} {
return ${funct_prefix}.write_gpio
} elseif {[lsearch $::HR(functs) "${funct_prefix}.read"] >= 0} {
return ${funct_prefix}.write
} else {
return -code error "funct_for_hm2 $dir $pname"
}
}
OUT {
if { ([string first .gpio. $pname] >=0 ) \
&& ([lsearch $::HR(functs) "${funct_prefix}.write_gpio"] >= 0)} {
return ${funct_prefix}.read_gpio
} elseif {[lsearch $::HR(functs) "${funct_prefix}.write"] >= 0} {
return ${funct_prefix}.read
} else {
return -code error "funct_for_hm2 $dir $pname"
}
}
* {return -code error "funct_for_hm2: $dir $pname" }
}
}
return ""
} ;# funct_for_hm2
proc funct_for_deprecated {pname dir pin_prefix instance pin_suffix} {
# 1) marked obsolete or deprecated:
# 2) return "" and warn message
foreach prefix $::HR(comps,deprecated) {
if {[string first "${prefix}." $pname] >= 0} {
if ![info exists ::HR(warnings,$prefix)] {
lappend ::HR(warnings,$prefix) \
"$prefix component is deprecated/obsolete ($ man $prefix)"
}
return ""
}
}
} ;# funct_for_deprecated
proc funct_for_std_forms {pname dir pin_prefix instance pin_suffix} {
# typical for halcompile with names= or count= option
set fnames [find_funct $pin_prefix $instance ""]
if {"$fnames" != ""} {return $fnames}
# alternate function name: update
set fnames [find_funct $pin_prefix $instance update]
if {"$fnames" != ""} {return $fnames}
return ""
} ;# funct_for_std_forms
proc funct_for_pin {pname dir} {
# find funct name based on pin name and direction using
# a search of known component categories
# use real pin name when aliased:
if [info exists ::ALIAS_TO_PIN($pname)] {set pname $::ALIAS_TO_PIN($pname)}
set ans [funct_for_userspace_comps $pname]
if {"$ans" != ""} {return $ans}
set p_list [split $pname .]
set pin_prefix [lindex $p_list 0]
set instance [lindex $p_list 1]
set pin_suffix [lindex $p_list end]
# try to find function for pname by categories
# break when fnames found, else fnames == ""
foreach funct [list funct_for_motion \
funct_for_deprecated \
funct_for_pid \
funct_for_hm2 \
funct_for_ppmc \
funct_for_matrix_kb \
funct_for_encoder \
funct_for_encoder_ratio \
funct_for_pwmgen \
funct_for_stepgen \
funct_for_parport \
funct_for_offset \
funct_for_legacy_comp \
funct_for_weighted_sum \
funct_for_kinematics \
funct_for_std_forms \
] {
set fnames [$funct $pname $dir $pin_prefix $instance $pin_suffix]
if {"$fnames" != ""} break
}
if {"$fnames" == ""} {
# fnames not found, make guess by looking for loaded functions
# on all threads # that match pin_prefix:
# pin=xxx.yyy.zzz, funct candidate(s): xxx.yyy.*
foreach tname $::HR(thread_names) {
foreach f_candidate $::HR(funct_list,$tname) {
if {[string first $pin_prefix $f_candidate] == 0} {
lappend guess $f_candidate
}
}
} ;# for tname
set ufmt "%-30s funct: %s"
if [info exists guess] {
set fnames $guess
foreach fname $fnames {
set ftag "?-$fname"
lappend ::HR(pins,unknown_funct) \
[format "$ufmt" $pname $ftag]
}
} else {
# fnames not found and no guess
# some guis create userspace pins (no ordered function)
# but are not readily distinguished by name
if [info exists ::HR(ini,gui)] {
set ftag "?-gui:$::HR(ini,gui)"
lappend ans [list "notRT" "---" "$ftag"]
lappend ::HR(pins,unknown_funct) \
[format "$ufmt" $pname $ftag]
} else {
set ftag "Unknown"
lappend ans [list "" "" "$ftag"] ;# unhandled component
lappend ::HR(pins,unknown_funct) \
[format "$ufmt" $pname $ftag]
}
return $ans
}
}
# found fnames: find thread and position
foreach fname $fnames {
if {[lsearch $::HR(functs) "$fname"] < 0} {
lappend ans [list "???" "???" "!!$fname"]
} else {
foreach tname $::HR(thread_names) {
set pos [array names ::HR pos,$tname,$fname]
if {"$pos" != ""} {
set tag ""
if [info exists guess] {set tag "?-"}
lappend ans [list $tname $::HR($pos) $tag${fname}]
}
}
}
}
# no fname found and signal created using pin but no thread started
if ![info exists ans] {
foreach tname $::HR(thread_names) {
if {[lsearch $::HR(addf) {$fname $tname}] >= 0} {
set found_t
break
}
}
if [info exists found_t] {
lappend ans [list "!!Unexpected" "---" "$fname"]
} else {
lappend ans [list "!!NO_Thread" "---" "$fname"]
}
return $ans
}
return $ans ;# {threadname order functname} {...}
} ;# funct_for_pin
proc which_exe {name} {
# replaces /usr/bin/which deprecated in debian/unstable
foreach dir [split $::env(PATH) :] {
set f [file join $dir $name]
if [file executable $f] { return $f }
}
return -code error "$name: executable not found"
} ;# which_exe
proc make_report {fd} {
make_addf_list
make_pin_alias_list ::ALIAS_TO_PIN ::PIN_TO_ALIAS
#parray ::ALIAS_TO_PIN
#parray ::PIN_TO_ALIAS
set ::HR(date) [clock format [clock seconds] -format "%d%b%y %H:%M:%S"]
set found_linuxcnc "linuxcnc"
catch { set found_linuxcnc [which_exe linuxcnc] }
set this_script [info script]
set linuxcncversion ""
catch {set linuxcncversion [exec linuxcnc_var LINUXCNCVERSION]}
puts $fd "$::HR(date) $this_script\n"
puts $fd "This report: $::HR(report_file)"
puts $fd "LinuxCNC: $found_linuxcnc"
puts $fd "LinuxCNC Version: $linuxcncversion"
if {[string first /usr/bin $found_linuxcnc] < 0} {
set restore_dir [pwd]
cd [file dirname $this_script]
catch {puts $fd "git commit (RIP): [exec git rev-parse --short HEAD]" }
cd $restore_dir
}
puts $fd "uname -r: [exec uname -r]"
puts $fd "lsb_release -d: [exec lsb_release -d]"
if [info exists ::HR(INI_FILE_NAME)] {
puts $fd "INI_FILE_NAME: $::HR(INI_FILE_NAME)"
}
if [info exists ::HR(ini,gui)] {
puts $fd "INI gui: $::HR(ini,gui)"
}
puts $fd $::HR(separator)
puts $fd "
The following report shows each signal (SIG:) and its
output, input, and io pins (OUT:,IN:,IO:) followed by
the function name, thread_name, and the addf-order for
the function.
For critical signal paths (e.g., pid loops), the signal OUT
pin should be numerically lower in order than the order of
any timing-critical IN pins for the signal."
set ::HR(thread_names) [hal list thread]
set ::HR(functs) [hal list funct]
foreach tname $::HR(thread_names) {
lappend ::HR(funct_list,$tname) {}
set ct 1
foreach item $::HR(addf) {
set position ""
set function [lindex $item 0]
set thread [lindex $item 1]
if {"$thread" != "$tname"} continue
if {"$position" != ""} {
puts $fd "UNHANDLED addf position for $thread $function $position"
}
set ::HR(pos,$thread,$function) [format %03d $ct]
lappend ::HR(funct_list,$thread) "$function"
incr ct
}
}
set maxlenp 0
foreach pname [hal list pin] {
set lenp [string len $pname]
set lena 0
if [info exists ::PIN_TO_ALIAS($pname)] {
set lena [string len $::PIN_TO_ALIAS($pname)
}
if {$lenp > $maxlenp} {set maxlenp $lenp;set maxp $pname}
set lena [expr $lena +3] ;# (=)
if {$lena > $maxlenp} {set maxlenp $lena;set maxp $pname}
# the pin with max len might not be displayed
}
set maxlenf 0
foreach f [hal list funct] {
set lenf [string len $f]
if {$lenf > $maxlenf} {set maxlenf $lenf; set maxf $f}
}
set maxlenf [expr $maxlenf +2] ;# allow for tag
set fmt_lbl "%-7s"
set fmt_pin "%-${maxlenp}s"
set fmt_funct "%-${maxlenf}s"
set fmt_thd "%-12s"
set fmt_pos "%-3s"
set fmt "%-6s %-30s %-13s %-3s %-20s"
set fmt "${fmt_lbl} ${fmt_pin} ${fmt_funct} ${fmt_thd} ${fmt_pos}"
set X1 " ";set X2 " ";set X4 " "
set again_char "."
foreach sname [hal list signal] {
set out_ct 0;set in_ct 0; set io_ct 0
puts $fd ""
puts $fd [format "$fmt" "SIG:" "$sname" "" "" ""]
get_netlist i o io $sname
if {"$o" != ""} {
set pname $o
set first_funct 1
foreach ans [funct_for_pin $pname OUT] {
set tname [lindex $ans 0]
set position [lindex $ans 1]
set funct [lindex $ans 2]
if $first_funct {
set first_funct 0
puts $fd [format "$fmt" "${X2}OUT:" \
"${X2}$pname" "$funct" "$tname" "$position"]
} else {
set again [string repeat $again_char [string len $pname]]
puts $fd [format "$fmt" "" "${X2}$again" \
"$funct" "$tname" "$position"]
}
}
if [info exists ::ALIAS_TO_PIN($pname)] {
set pname $::ALIAS_TO_PIN($pname)
puts $fd [format "$fmt" "" "${X2}(=$pname)" "" "" ""]
}
incr out_ct
}
if {"$io" != ""} {
foreach pname $io {
set first_funct 1
foreach ans [funct_for_pin $pname IO] {
set tname [lindex $ans 0]
set position [lindex $ans 1]
set funct [lindex $ans 2]
if $first_funct {
set first_funct 0
puts $fd [format "$fmt" "${X2}IO:" \
"${X2}$pname" "$funct" "$tname" "$position"]
} else {
set again [string repeat $again_char [string len $pname]]
puts $fd [format "$fmt" "" "${X2}$again" \
"$funct" "$tname" "$position"]
}
}
if [info exists ::ALIAS_TO_PIN($pname)] {
set pname $::ALIAS_TO_PIN($pname)
puts $fd [format "$fmt" "" "${X1}(=$pname)" \
"$funct" "$tname" "$position"]
}
incr io_ct
}
}
if {"$i" != ""} {
foreach pname $i {
set first_funct 1
foreach ans [funct_for_pin $pname IN] {
set tname [lindex $ans 0]
set position [lindex $ans 1]
set funct [lindex $ans 2]
if $first_funct {
set first_funct 0
puts $fd [format "$fmt" "${X4}IN:" "${X4}$pname" \
"$funct" "$tname" "$position"]
} else {
set again [string repeat $again_char [string len $pname]]
puts $fd [format "$fmt" "" "${X4}$again" \
"$funct" "$tname" "$position"]
}
}
if [info exists ::ALIAS_TO_PIN($pname)] {
set pname $::ALIAS_TO_PIN($pname)
puts $fd [format "$fmt" "" "${X4}(=$pname)" "" "" ""]
}
incr in_ct
}
}
if {$out_ct == 0 && $io_ct == 0} {lappend no_out_list $sname}
if {$in_ct == 0 && $io_ct == 0} {lappend no_in_list $sname}
}
if [info exists no_out_list] {
puts $fd $::HR(separator)
puts $fd "Signals with no outputs (no out pin, no io pins):"
foreach s $no_out_list {
puts $fd " $s"
}
}
if [info exists no_in_list] {
puts $fd $::HR(separator)
puts $fd "Signals with no inputs (no in pins, no io pins):"
foreach s $no_in_list {
puts $fd " $s"
}
}
set thread_list [hal list thread]
if {"$thread_list" != ""} {
puts $fd $::HR(separator)
puts $fd "Function ordering by thread:"
foreach tname $thread_list {
puts $fd "\n$tname"
if {$::HR(funct_list,$tname) == "{}" } {
puts $fd " None"
} else {
foreach fnames $::HR(funct_list,$tname) {
if ![info exists ::HR(pos,$tname,$fnames)] {continue}
puts $fd [format " %3s %s" $::HR(pos,$tname,$fnames) $fnames]
}
}
}
}
set noaddf_list [unadded_functs]
if {$noaddf_list != ""} {
puts $fd $::HR(separator)
set msg "! Functions with no addf:"
puts $fd $msg
foreach fname $noaddf_list {
puts $fd " $fname"
}
}
puts $fd $::HR(separator)
if [info exists ::HR(pins,unknown_funct)] {
set msg "?-Uncertain function determination for pins: "
puts $fd "$msg"
foreach pname $::HR(pins,unknown_funct) {
puts $fd " $pname"
}
puts $fd $::HR(separator)
}
set warn_names [array names ::HR warnings,*]
if {$warn_names != ""} {
puts $fd Warning:
foreach warn $warn_names {
puts $fd " [string trim $::HR($warn) \{\}]"
}
puts $fd $::HR(separator)
}
set guess_names [array names ::HR guess,*]
if {$guess_names != ""} {
puts $fd "Guessed function names:"
foreach guess $guess_names {
puts $fd " [string trim $::HR($guess) \{\}]"
}
puts $fd $::HR(separator)
}
puts $fd "Notes:
1) Userspace functions are not ordered and are marked \"notRT\".
2) Most in-tree components important for timing-critical signal
paths are handled. When a component is not handled explicitly,
a function may be tagged as ?-function_name.
3) When alias pin names are used, the actual pin name is shown
below the aliased name and marked as (=real_pin_name).
"
#"
} ;# make_report
proc usage {} {
puts "\nUsage:
$::HR(prog,short) -h | --help (this help)
or
$::HR(prog,short) \[outfilename\]
"
exit 0
} ;# usage
proc config_options {} {
set ::HR(prog) $::argv0
set ::HR(prog,short) [file tail $::argv0]
set ::HR(report_file) stdout
set currentarg [lindex $::argv 0]
while {[llength $::argv] >0} {
# beware wish handling of reserved cmdline arguments
# to use -h: use -- -h,
# lreplace shifts argv for no. of items for each iteration
set currentarg [lindex $::argv 0]
switch -glob -- $currentarg {
-h - --help {usage}
-* {usage}
default {set ::HR(report_file) $currentarg}
}
set ::argv [lreplace $::argv 0 0]
} ;# while
} ;# config_options
proc ini_file_items {} {
if [info exists ::env(INI_FILE_NAME)] {
set ::HR(INI_FILE_NAME) $::env(INI_FILE_NAME)
} else {
set ans ""
catch {set ans [split [exec pgrep -a linuxcncsvr]]}
foreach item $ans {
if {"$item" == "-ini"} {set found_ini 1;continue}
if [info exists found_ini] {
set ::HR(INI_FILE_NAME) $item
break
}
}
}
if [info exists ::HR(INI_FILE_NAME)] {
set ::HR(INI_FILE_NAME) [file normalize $::HR(INI_FILE_NAME)]
set ::HR(ini,gui) "unknown"
if [catch {set ::HR(ini,gui) [exec inivar -var DISPLAY \
-sec DISPLAY \
-ini $::HR(INI_FILE_NAME)] \
} msg] {
puts msg=$msg
}
}
} ;# ini_file_items
#-----------------------------------------------------------------------
# begin
config_options
if {[llength [hal list comp]] <3} {
puts stdout "\n$::HR(prog,short): No components are loaded"
puts stdout "$::HR(prog,short): is LinuxCNC or another hal app running?\n"
exit 1
}
ini_file_items
set fd stdout ;# default
if {"$::HR(report_file)" != "stdout"} {
set ::HR(report_file) [file normalize $::HR(report_file)]
puts stdout "$::HR(prog,short): file=$::HR(report_file)\n"
if [catch {set fd [open $::HR(report_file) w]} msg] {
puts "$::HR(prog): $msg"
exit 1
}
}
if [catch {make_report $fd} msg] {
puts $fd "\n$::argv0: make_report_msg=\n$msg\n"
}
close $fd
exit 0

170
scripts/halrun.in Normal file
View File

@@ -0,0 +1,170 @@
#!/bin/bash
PATH=@EMC2_BIN_DIR@:$PATH
REALTIME=@REALTIME@
THIS=$(basename "$0")
export HAL_RTMOD_DIR=@EMC2_RTLIB_DIR@
help () {
halcmd -h
cat <<EOF
$THIS Usage:
$THIS [-I] [halcmd_opts] [filename[.hal|.tcl]]
$THIS -T [halcmd_opts] [filename[.hal|.tcl]]
$THIS -h this help
$THIS -U forcibly cause the realtime environment to exit
filename[.hal|.tcl] may also be specified as '-f filename[.hal|.tcl]'
halcmd_opts apply for .hal files only
Interactive if: no filename (runs halcmd)
or -I (runs halcmd)
or -T (runs haltcl)
EOF
}
INTERACTIVE=""
inifile=""
theargs=""
while getopts "ef:hi:kqsvIRQTUV" opt ; do
case $opt in
h) help; exit 0;;
U) halcmd -R
halcmd stop
halcmd unload all
$REALTIME stop
exit 0;;
f) filename=$OPTARG;;
i) inifile=$OPTARG;;
I) INTERACTIVE="halcmd -kf";;
T) INTERACTIVE="haltcl";;
e) theargs="$theargs -$opt";;
k) theargs="$theargs -$opt";;
q) theargs="$theargs -$opt";;
s) theargs="$theargs -$opt";;
v) theargs="$theargs -$opt";;
R) theargs="$theargs -$opt";;
Q) theargs="$theargs -$opt";;
V) theargs="$theargs -$opt";;
\?) echo ""
echo "For usage try: $THIS -h"
exit 1;;
esac
done
shift $((OPTIND - 1))
if [ $# -gt 1 ] ; then
echo "$THIS: too many arguments <$*>"
exit 1
fi
# filename can be specified two ways:
# as parameter for -f ('f filename')
# or as trailing parameter
if [ $# -gt 0 ] ; then
if [ -z "$filename" ] ; then
filename=$1
shift
else
echo "$THIS: Error: Specified '-f $1' and also <$filename>"
exit 1
fi
fi
if $REALTIME status > /dev/null; then
echo "$THIS: Realtime already running. Use 'halrun -U' to stop existing realtime session." 1>&2
exit 1
fi
HAVEFILE=false
IS_HALTCL=false
IS_INI=false
case $filename in
*.hal) HAVEFILE=true
if [ -n "$inifile" ] ; then
theargs="$theargs -i $inifile"
fi
# halcmd uses all $theargs:
set -- "$theargs -f $filename";;
*.tcl) HAVEFILE=true
IS_HALTCL=true
# haltcl uses only -i arg
if [ -n "$inifile" ] ; then
tclargs="-i $inifile"
else
tclargs=""
fi
# haltcl only uses inifilename and filename:
set -- "$tclargs $filename";;
*.ini) HAVEFILE=true
IS_INI=true
;;
"") # for nil filename, support interactive halcmd
# or haltcl if -T was used
if [ "$INTERACTIVE" != "haltcl" ] ; then
INTERACTIVE="halcmd $theargs -kf"
fi
;;
*) echo "$THIS: Unknown file extension for filename=<$filename>"
exit 1
;;
esac
case "$INTERACTIVE" in
halcmd*) if [ -n "$inifile" ] ; then
INTERACTIVE="$INTERACTIVE -i $inifile"
fi
;;
haltcl) if $IS_HALTCL ; then
if [ "$theargs" != "" ] ; then
echo "$THIS: args not accepted for haltcl <$theargs>"
exit 1
fi
else
# allowed -T (tcl interactive) with startup .hal file
:
fi
if [ -n "$inifile" ] ; then
INTERACTIVE="haltcl -i $inifile"
fi
;;
esac
$REALTIME start || exit $?
result=0
if $HAVEFILE ; then
if $IS_INI; then
if ! inivar -ini "$filename" -sec HAL -var TWOPASS > /dev/null 2>&1 ; then
echo "$THIS: An INI file may only be used if it specifies [HAL]TWOPASS"
result=1
else
halcmd_twopass "$filename"; result=$?
fi
elif $IS_HALTCL; then
# Arguments need to be word split
# shellcheck disable=SC2068
haltcl $@; result=$?
else
# shellcheck disable=SC2068
halcmd $@; result=$?
fi
fi
if [ -n "$INTERACTIVE" ]; then $INTERACTIVE; fi
halcmd stop || result=$?
halcmd unload all || result=$?
$REALTIME stop || result=$?
exit "$result"

43
scripts/haltcl.in Normal file
View File

@@ -0,0 +1,43 @@
#!@TCLSH@
package require Linuxcnc
if { [llength $argv] > 0 \
&& ( ([lindex $argv 0] == "-i") || ([lindex $argv 0] == "-ini") )} {
parse_ini [lindex $argv 1]
set argv [lrange $argv 2 end]
}
proc setp {p v} {
set v [uplevel [list expr $v]]
hal setp $p $v
}
proc sets {p v} {
set v [uplevel [list expr $v]]
hal sets $p $v
}
foreach c [hal --commands] {
if {[info commands $c] == {}} {
proc $c args "eval hal $c \$args"
}
}
proc show args {puts [eval hal show $args]}
if {[llength $argv] == 0} {
package require tclreadline
namespace eval tclreadline {
proc prompt1 {} {
return "haltcl: "
}
}
::tclreadline::Loop
}
set filename [lindex $argv 0]
set argv [lrange $argv 1 end]
set result [source $filename]

108
scripts/htmlcheck.sh Executable file
View File

@@ -0,0 +1,108 @@
#!/bin/bash
usage () {
P=${0##*/}
cat <<EOF
$P: Run html checks
Usage:
$P [-v] [-e] [file ...]
Run html checks
-w: Warn only
Returns zero on check error but shows warning and
generates CI annotation
-v: Verbose
-e: Check external links
EOF
}
WARN=0
VERBOSE=0
EXTERNAL=0
while getopts wveh opt; do
case "$opt" in
w) WARN=1 ;;
v) VERBOSE=1 ;;
e) EXTERNAL=1 ;;
h|?) usage; exit 0 ;;
*) usage; exit 1 ;;
esac
done
shift $((OPTIND-1))
if ! command -v checklink > /dev/null; then
echo "ERROR: checklink not found in PATH, install w3c-linkchecker for HTML link validation" 1>&2
echo "Can be downloaded from https://github.com/w3c/link-checker if there is no package for your distribution." 1>&2
echo "link-checker/bin/checklink is a perl script, nothing else is needed from the above repo." 1>&2
exit 1
fi
CHKOPT=( --follow-file-links )
if [ "$VERBOSE" -eq 0 ]; then
CHKOPT+=( --quiet )
fi
if [ "$EXTERNAL" -eq 0 ]; then
CHKOPT+=( --exclude "(http|https|irc)://" )
fi
#Note: grep is used to filer out an error message due to a bug in checklink in debian/ubuntu
#should be removed as soon as this package is fixed (upstream is already fine)
warnval=0
retval=0
if [ $# -gt 0 ]; then
# Only process individual files if passed on the command line.
for f in "$@"; do
if [ -r "$f" ]; then
checklink "${CHKOPT[@]}" "$f" 2>&1 | grep -vE 'Use of uninitialized value .* at .*checklink line [0-9]+'
ret="${PIPESTATUS[0]}"
if [ "$ret" -ne 0 ]; then
echo "'$f': File check: Fail"
if [ $WARN != 0 ]; then
warnval="$ret"
else
retval="$ret"
fi
else
echo "'$f': File check: OK"
fi
else
echo "Cannot read file '$f'"
retval=1
fi
done
else
#Otherwhise, recursively check docs/build/html/index.html
f="$(dirname "$0")/../docs/build/html/index.html"
if [ -r "$f" ]; then
CHKOPT+=( --recursive )
checklink "${CHKOPT[@]}" "$f" 2>&1 | grep -vE 'Use of uninitialized value .* at .*checklink line [0-9]+'
ret="${PIPESTATUS[0]}"
if [ "$ret" -ne 0 ]; then
echo "'$f': Recursive check: Fail"
if [ $WARN != 0 ]; then
warnval="$ret"
else
retval="$ret"
fi
else
echo "'$f': Recursive check: OK"
fi
else
echo "Cannot read file '$f', did you build the doc first?"
retval=1
fi
fi
#Generate a CI warning or error if running in CI
#See: https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-commands
if [ -n "${GITHUB_ACTIONS:-}" ]; then
if [ "$retval" != 0 ]; then
echo "::error title=HTML checks failed::checks failed"
elif [ "$warnval" != 0 ]; then
echo "::warning title=HTML checks failed::checks failed"
fi
fi
exit "$retval"

1051
scripts/latency-histogram Executable file

File diff suppressed because it is too large Load Diff

536
scripts/latency-plot Executable file
View File

@@ -0,0 +1,536 @@
#!/usr/bin/wish
# Notes:
# notusing y axis title because it coredumps with X BadMatch with wish8.5
# (it works with wish8.4, tcl8.5 introduced a problem for rotated text)
# see also:
# http://lists.fedoraproject.org/pipermail/package-announce/2010-January/034295.html
#-----------------------------------------------------------------------
# Copyright: 2011
# Author: Dewey Garrett <dgarrett@panix.com>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#-----------------------------------------------------------------------
package require Tk 8.5 ;# needed for clock milliseconds
proc popup {msg} { \
set answer [tk_messageBox \
-parent . \
-icon error \
-type ok \
-title "Message" \
-message "$msg" \
]
puts $msg
} ;# popup
if [catch {package require BLT} msg] {
if {[string first "version conflict" $msg] == 0} {
set msg "BLT version conflict!\n\n$msg"
} elseif {[string first "can't find package" $msg] == 0} {
set msg "package BLT not found!\nrun: sudo apt-get install blt"
} else {
set msg "unhandled error:\n$msg"
}
popup $msg
exit 1
}
proc which_exe {name} {
# replaces /usr/bin/which deprecated in debian/unstable
foreach dir [split $::env(PATH) :] {
set f [file join $dir $name]
if [file executable $f] { return $f }
}
return -code error "$name: executable not found"
} ;# which_exe
proc setdefaults {} {
if [catch { set ::sc(halrun) [which_exe halrun]} msg] {
puts $msg
exit 1
}
set ::sc(realtime) [exec linuxcnc_var REALTIME]
# note: program updates at 1 second intervals
set ::sc(xpoints) 60 ;# x axis points
set ::sc(xpoints,min) 15 ;#
set ::sc(xpoints,max) 15360 ;#
set ::sc(shiftby) 1 ;# x axis shiftby points
set ::sc(::yv1,smooth) linear ;# linear,step,...
set ::sc(::yv2,smooth) linear
set ::sc(::yv3,smooth) step
set ::sc(::yv4,smooth) step
set ::sc(::yv1,symbol) "" ;# square,circle,...
set ::sc(::yv2,symbol) ""
set ::sc(::yv3,symbol) circle
set ::sc(::yv4,symbol) square
set ::sc(::yv1,pixels) 0 ;# symbol size
set ::sc(::yv2,pixels) 0
set ::sc(::yv3,pixels) 2
set ::sc(::yv4,pixels) 2
set ::sc(::yv1,linewidth) 2
set ::sc(::yv2,linewidth) 2
set ::sc(::yv3,linewidth) 1
set ::sc(::yv4,linewidth) 1
set ::sc(::yv1,color) red
set ::sc(::yv2,color) blue
set ::sc(::yv3,color) green
set ::sc(::yv4,color) yellow
set ::sc(grid,color) white
set ::sc(grid,hide) no
set ::sc(color,background) black
set ::sc(clock,mode) relative ;# actual | relative
set ::sc(mem,max,percent) 1.0
set ::sc(pid) [pid]
set ::sc(data,source) hal_timedelta
# hal_timedelta defaults:
set ::sc(base,period,ns) 25000
set ::sc(servo,period,ns) 1000000
set ::sc(report,ms) 1000
set ::sc(report,ms,min) 100
set ::sc(report,ms,max) 10000
set ::sc(sample,ms) 10 ;# min value is 1 mS using tcl after cmd
;# not sure all cpus could do 1mS so use 10mS
set ::sc(show,sample,max) 1 ;# 1: show max .out value in report,ms interval
;# 0: show last .out value of report,ms interval
} ;# setdefaults
proc init {} {
set ::sc(label) "Latency (uSeconds) vs Time (seconds) Wall:"
switch $::sc(clock,mode) {
actual {set ::sc(button,timescale) 1}
relative {set ::sc(button,timescale) 0}
}
set sp [format %.0f [expr $::sc(servo,period,ns)/1000]]
set bp [format %.0f [expr $::sc(base,period,ns)/1000]]
wm title . "[file tail $::argv0] (servo: $sp uS, base: $bp uS)"
update_timescale
blt::vector create ::xv
blt::vector create ::yv1; set ::yv1(++end) 0.0
blt::vector create ::yv2; set ::yv2(++end) 0.0
blt::vector create ::yv3; set ::yv3(++end) 0.0
blt::vector create ::yv4; set ::yv4(++end) 0.0
switch $::sc(data,source) {
hal_timedelta {init_hal_timedelta
set ::xv(++end) [expr [clock milliseconds]/1000.]
}
default {return -code error "init: unknown data,source: <$::sc(data,source)>"}
}
wm protocol . WM_DELETE_WINDOW finish
} ;# init
proc init_hal_timedelta {} {
if [catch {set ans [eval exec $::sc(realtime) start]} msg] {
# see note on abomination it scripts/realtime.in
if {[string length "$msg"] > 1} {
puts stderr "$::argv0:msg<$msg>"
}
} else {
if {[string length "$ans"] > 1} {
puts stderr "$::argv0:ans<$ans>"
}
}
# augment ::auto_path for special case:
# 1) RIP build (no install)
# 2) linuxcnc script called from Application menu
if { [info exists ::env(LINUXCNC_TCL_DIR)]
&& ([lsearch $::auto_path $::env(LINUXCNC_TCL_DIR)] < 0)
} {
# prepend
set ::auto_path [lreplace $::auto_path 0 -1 $::env(LINUXCNC_TCL_DIR)]
}
package require Hal
set ::sc(v,name,pairs) { ::yv1 b:max \
::yv2 s:max \
::yv3 b:latency \
::yv4 s:latency }
set ::sc(servo,max,last) 0
set ::sc(base,max,last) 0
set ::sc(servo,out,sample,max) 0
set ::sc(base,out,sample,max) 0
} ;# init_hal_timedelta
proc check {} {
switch $::sc(data,source) {
hal_timedelta {}
default {return -code error "init: unknown data,source: <$::sc(data,source)>"}
}
} ;# check
proc mcheck {} {
# cautionary check on memory usage
# %mem "ratio of process's resident set size to the physical mem in percent"
set mempercent [eval exec ps --no-headers --pid $::sc(pid) -o %mem]
if {$mempercent > $::sc(mem,max,percent)} {
set msg "Memory used is ${mempercent}%, Exiting"
popup $msg
exit 1
}
after 10000 mcheck
} ;# mcheck
proc start {} {
switch $::sc(data,source) {
hal_timedelta {start_hal_timedelta}
default {return -code error "init: unknown data,source: <$::sc(data,source)>"}
}
} ;# start
proc start_hal_timedelta {} {
hal loadrt threads name1=base period1=$::sc(base,period,ns) \
name2=servo period2=$::sc(servo,period,ns)
hal loadrt timedelta names=base:time,servo:time
hal addf base:time base
hal addf servo:time servo
hal net servo_max servo:time.max
hal net servo_jitter servo:time.jitter
hal net servo_out servo:time.out
hal net base_max base:time.max
hal net base_jitter base:time.jitter
hal net base_out base:time.out
hal start
set ::sc(start,time) [expr [clock milliseconds]/1000.]
set ::sc(after,hal,sample) [after $::sc(sample,ms) hal_sample]
set ::sc(after,hal,report) [after $::sc(report,ms) hal_report]
after 2000 [list $::sc(stripchart) axis configure x -hide no]
} ;# start_hal_timedelta
proc hal_sample {} {
set ::sc(base,out,sample) [hal getp base:time.out]
set ::sc(servo,out,sample) [hal getp servo:time.out]
# save the largest sampled .out value:
if {$::sc(base,out,sample) > $::sc(base,out,sample,max)} {
set ::sc(base,out,sample,max) $::sc(base,out,sample)
}
if {$::sc(servo,out,sample) > $::sc(servo,out,sample,max)} {
set ::sc(servo,out,sample,max) $::sc(servo,out,sample)
}
# reschedule:
set ::sc(after,hal,sample) [after $::sc(sample,ms) hal_sample]
} ;# hal_sample
proc hal_report {} {
set ::sc(base,max) [hal getp base:time.max]
set ::sc(servo,max) [hal getp servo:time.max]
set ::sc(base,out) [hal getp base:time.out]
set ::sc(servo,out) [hal getp servo:time.out]
if $::sc(show,sample,max) {
set ::sc(base,lat) $::sc(base,out,sample,max)
set ::sc(servo,lat) $::sc(servo,out,sample,max)
} else {
set ::sc(base,lat) $::sc(base,out)
set ::sc(servo,lat) $::sc(servo,out)
}
# This script undersamples the hal threads so it can miss a latency
# spike that bumps (*,max). Detect explicitly whenever an increase
# in (*,max) occurs during the (report,ms) interval:
if { $::sc(base,max) > $::sc(base,max,last) } {
set ::sc(base,lat) $::sc(base,max)
}
if { $::sc(servo,max) > $::sc(servo,max,last) } {
set ::sc(servo,lat) $::sc(servo,max)
}
set ::xv(++end) [expr [clock milliseconds]/1000.]
set ::yv1(++end) [expr ($::sc(base,max) - $::sc(base,period,ns) )/1000]
set ::yv2(++end) [expr ($::sc(servo,max) - $::sc(servo,period,ns) )/1000]
set ::yv3(++end) [expr ($::sc(base,lat) - $::sc(base,period,ns) )/1000]
set ::yv4(++end) [expr ($::sc(servo,lat) - $::sc(servo,period,ns) )/1000]
if {[info exists ::env(verbose_latency_plot)]} {
set ::sc(base,jitter) [hal getp base:time.jitter]
set ::sc(servo,jitter) [hal getp servo:time.jitter]
puts [format "%7.0f %7.0f %7.0f %8.0f %8.0f %8.0f %8.0f %9.0f"\
$::sc(base,max) \
$::sc(base,out) \
$::sc(base,jitter) \
$::sc(base,lat) \
$::sc(servo,max) \
$::sc(servo,out) \
$::sc(servo,jitter) \
$::sc(servo,lat) \
]
} ;# if verbose
set ::sc(base,max,last) $::sc(base,max)
set ::sc(servo,max,last) $::sc(servo,max)
set ::sc(base,out,sample,max) 0
set ::sc(servo,out,sample,max) 0
# reschedule:
set ::sc(after,hal,report) [after $::sc(report,ms) hal_report]
} ;# hal_report
proc restart {} {
switch $::sc(data,source) {
hal_timedelta {restart_hal_timedelta}
default {return -code error "init: unknown data,source: <$::sc(data,source)>"}
}
} ;# restart
proc restart_hal_timedelta {} {
$::sc(stripchart) axis configure x -hide yes
after cancel $::sc(after,hal,sample)
after cancel $::sc(after,hal,report)
hal setp servo:time.reset 1
hal setp base:time.reset 1
::xv set {}
::yv1 set {}; ::yv2 set {}; ::yv3 set {}; ::yv4 set {}
set ::sc(start,time) [expr [clock milliseconds]/1000.]
set ::xv(++end) $::sc(start,time)
update ;# !!
hal setp servo:time.reset 0
hal setp base:time.reset 0
set ::sc(after,hal,sample) [after $::sc(sample,ms) hal_sample]
set ::sc(after,hal,report) [after $::sc(report,ms) hal_report]
after 2000 [list $::sc(stripchart) axis configure x -hide no]
} ;# restart_hal_timedelta
proc setup_stripchart {} {
wm minsize . 400 300
pack [frame .f1] -fill both -expand 1
set ::sc(stripchart) [blt::stripchart .f1.s \
-plotbackground $::sc(color,background) \
-height 2i \
]
pack $::sc(stripchart) -expand yes -fill both
$::sc(stripchart) grid configure \
-hide $::sc(grid,hide) \
-color $::sc(grid,color)
$::sc(stripchart) legend configure \
-anchor n \
-position leftmargin
# note: hide x axis label until autoranging completed
$::sc(stripchart) axis configure x \
-autorange $::sc(xpoints) \
-shiftby $::sc(shiftby) \
-command xfmt \
-hide 1
$::sc(stripchart) axis configure y \
-autorange 0
foreach {v name} $::sc(v,name,pairs) {
$::sc(stripchart) element create $name \
-xdata ::xv \
-ydata $v \
-color $::sc($v,color) \
-linewidth $::sc($v,linewidth) \
-smooth $::sc($v,smooth) \
-symbol $::sc($v,symbol) \
-pixels $::sc($v,pixels)
}
pack [frame .f2] -side top -anchor w -fill x -expand 0
pack [frame .f3] -side left -anchor e -fill none -expand 0
pack [button .f3.b1 -text "R"\
-padx 2 -pady 0 \
-command restart \
] -side left -expand 0
pack [label .f3.l2 -text "Pts:"] -side left -expand 0
pack [entry .f3.e -textvariable ::sc(xpoints) \
-width 5 -justify right -state readonly] \
-side left -fill none -expand 0
pack [button .f3.decrement \
-padx 3 -pady 0 -text "-" -command "xpoints decrease"] \
-side left -fill none -expand 1
pack [button .f3.increment \
-padx 0 -pady 0 -text "+" -command "xpoints increase"] \
-side left -fill none -expand 1
set ::sc(button,decrease) .f3.decrement
set ::sc(button,increase) .f3.increment
pack [frame .f4] -side right -anchor w -fill x -expand 0
pack [checkbutton .f4.b -command update_timescale\
-variable ::sc(button,timescale)
] -side right -anchor w -fill x -expand 0
pack [label .f4.l1 -text "$::sc(label)"]\
-side left -anchor w -fill none -expand 1
} ;# setup_stripchart
proc update_timescale {} {
switch $::sc(button,timescale) {
0 {set ::sc(clock,mode) relative}
1 {set ::sc(clock,mode) actual}
}
} ;# update_timescale
proc finish {} {
switch $::sc(data,source) {
hal_timedelta {finish_hal_timedelta}
default {return -code error "init: unknown data,source: <$::sc(data,source)>"}
}
} ;# finish
proc finish_hal_timedelta {} {
after cancel $::sc(after,hal,sample)
after cancel $::sc(after,hal,report)
hal stop
hal delsig servo_max
hal delsig servo_jitter
hal delsig servo_out
hal delsig base_max
hal delsig base_jitter
hal delsig base_out
hal delf base:time base
hal delf servo:time servo
hal unloadrt timedelta
hal unloadrt threads ;# doesn't seem to work
#puts [hal show funct] ;# gone
#puts [hal show thread] ;# not gone
eval exec $::sc(halrun) -U
} ;# finish_hal_timedelta
proc xfmt {widget x} {
switch $::sc(clock,mode) {
relative { return [format %.0f [expr int($x - $::sc(start,time) + 0.5)]] }
actual { return [clock format $x -format "%H:%M:%S"] }
default { return -code error \
"xfmt: unknown clock,mode <$::sc(clock,mode)>"}
}
} ;# xfmt
proc xpoints {input} {
switch $input {
increase { set ::sc(xpoints) [expr $::sc(xpoints) * 2] }
decrease { set ::sc(xpoints) [expr $::sc(xpoints)/2] }
}
if {$::sc(xpoints) <= $::sc(xpoints,min)} {
set ::sc(xpoints) $::sc(xpoints,min)
$::sc(button,decrease) configure -state disabled
} else {
$::sc(button,decrease) configure -state normal
}
if {$::sc(xpoints) >= $::sc(xpoints,max)} {
set ::sc(xpoints) $::sc(xpoints,max)
$::sc(button,increase) configure -state disabled
} else {
$::sc(button,increase) configure -state normal
}
$::sc(stripchart) axis configure x -autorange $::sc(xpoints)
} ;# xpoints
proc usage {} {
set prog [file tail $::argv0]
puts ""
puts "Usage:"
puts " $prog --help | -? (this)"
puts " $prog --hal \[Options\]"
puts ""
puts "Options:"
puts " --base nS (base thread interval, default: $::sc(base,period,ns))"
puts " --servo nS (servo thread interval, default: $::sc(servo,period,ns))"
puts " --time mS (report interval, default: $::sc(report,ms))"
puts " --relative (relative clock time (default))"
puts " --actual (actual clock time)"
} ;# usage
proc config {} {
while {[llength $::argv] >0} {
# beware wish handling of reserved cmdline arguments
# to use -h: use -- -h,
# lreplace shifts argv for no. of items for each iteration
set currentarg [lindex $::argv 0]
switch -- $currentarg {
-? - --help {usage;exit 0}
-H - --hal {set ::sc(data,source) hal_timedelta}
--actual {set ::sc(clock,mode) actual}
--relative {set ::sc(clock,mode) relative}
-b - --base {set t [lindex $::argv 1]
set ::sc(base,period,ns) $t
set ::argv [lreplace $::argv 0 0]
}
-s - --servo {set t [lindex $::argv 1]
set ::sc(servo,period,ns) $t
set ::argv [lreplace $::argv 0 0]
}
-t - --time {set t [lindex $::argv 1]
if {$t > $::sc(report,ms,max)} {
set t $::sc(report,ms,max)
puts "--time value too big, setting to $t"
}
if {$t < $::sc(report,ms,min)} {
set t $::sc(report,ms,min)
puts "--time value too small, setting to $t"
}
set ::sc(report,ms) $t
set ::argv [lreplace $::argv 0 0]
}
default {lappend unknownargs $currentarg}
}
set ::argv [lreplace $::argv 0 0]
} ;# while
if [info exists unknownargs] {
puts "\nIgnoring unknown args: <$unknownargs>"
}
if {$::sc(base,period,ns) > $::sc(servo,period,ns)} {
set msg "Require base thread period <= servo thread period"
puts $msg
wm withdraw .
popup $msg
exit 1
}
if {$::sc(servo,period,ns) >= [expr $::sc(sample,ms) * 1000000]} {
set msg "Require servo thread period < sample interval $::sc(sample,ms) mS"
puts $msg
wm withdraw .
popup $msg
exit 1
}
} ;# config
# allow re-sourcing for testing
if ![info exists ::sc(xpoints)] {
setdefaults
config
check
init
mcheck
setup_stripchart
start
} else {
puts "already running"
}

199
scripts/latency-test Executable file
View File

@@ -0,0 +1,199 @@
#!/bin/bash
SCRIPT_LOCATION=$(dirname "$(readlink -f "$0")");
if [ -f "$SCRIPT_LOCATION/rip-environment" ] && [ -z "$EMC2_HOME" ]; then
. "$SCRIPT_LOCATION/rip-environment"
fi
T=$(mktemp -d)
trap 'cd / && [ -d "$T" ] && rm -rf "$T"' SIGINT SIGTERM EXIT
cd "$T" || { echo "E: Cannot change directory to '$T'"; exit 1; }
node=$(uname -n)
machine=$(uname -m)
date=$(date +%d%b%Y)
krelease=$(uname -r)
kversion=$(uname -v)
calc() { awk "BEGIN { print ($1); }" < /dev/null; }
icalc() { awk "BEGIN { printf \"%.0f\n\", ($1); }" < /dev/null; }
parse_time () {
case $1 in
-) echo "0" ;;
*ns) icalc "${1%ns}" ;;
*us|*µs) icalc "1000*${1%us}" ;;
*ms) icalc "1000*1000*${1%ms}" ;;
*s) icalc "1000*1000*1000*${1%s}" ;;
*) if [ "$1" -lt 1000 ]; then icalc "1000*$1"; else icalc "$1"; fi ;;
esac
}
human_time () {
if [ "$1" -eq 0 ]; then echo "-"
elif [ "$1" -ge 1000000000 ]; then echo "$(calc "$1"/1000/1000/1000)s"
elif [ "$1" -ge 1000000 ]; then echo "$(calc "$1"/1000/1000)ms"
elif [ "$1" -ge 1000 ]; then echo "$(calc "$1"/1000)µs"
else echo "$1ns"
fi
}
usage () {
echo "Usage:"
echo " latency-test [base-period [servo-period]]"
echo " or:"
echo " latency-test [servo-period] # for single thread"
echo " or:"
echo " latency-test -h | --help # (this text)"
echo ""
echo "Example:"
echo " latency-test 50000 1000000"
echo ""
echo "Defaults: base-period=${BASE}nS servo-period=${SERVO}nS"
echo "Equivalently: base-period=$(human_time "$BASE") servo-period=$(human_time "$SERVO")"
echo ""
echo "Times may be specified with suffix \"s\", \"ms\", \"us\" \"µs\", or \"ns\""
echo "Times without a suffix and less than 1000 are taken to be in us;"
echo "other times without a suffix are taken to be in ns"
echo ""
echo "If two periods are specified then the shortest time period will"
echo "always be the base-period"
echo ""
echo "The worst-case latency seen in any run of latency-test"
echo "is written to the file ~/.latency"
exit 1
}
BASE=$(parse_time 25us); SERVO=$(parse_time 1ms)
case $1 in
-h|--help) usage;;
esac
case $# in
0) ;;
1) BASE=0; SERVO=$(parse_time "$1") ;;
2) BASE=$(parse_time "$1"); SERVO=$(parse_time "$2") ;;
*) usage;;
esac
if [ "$BASE" -gt "$SERVO" ]; then TEMP=$BASE; BASE=$SERVO; SERVO=$TEMP; fi
if [ "$BASE" -eq "$SERVO" ]; then BASE=0; fi
BASE_HUMAN=$(human_time "$BASE")
SERVO_HUMAN=$(human_time "$SERVO")
# --- status-bar fields (colour red only when they need attention) ---
# realtime: type from 'realtime verify'; non-zero exit means none.
RED="#cc4444"
if RT_TEXT=$(realtime verify 2>/dev/null); then
RT_FG=""
else
RT_FG="foreground=\"$RED\""
fi
GOV=$(cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor 2>/dev/null)
[ -z "$GOV" ] && GOV="gov?"
if [ "$GOV" = performance ]; then GOV_FG=""; else GOV_FG="foreground=\"$RED\""; fi
if grep -q 'isolcpus=' /proc/cmdline 2>/dev/null; then
ISOL_TEXT=$(tr ' ' '\n' < /proc/cmdline | grep '^isolcpus=' | head -1)
else
ISOL_TEXT="no_isolcpus"
fi
if [ "$BASE" -eq 0 ]; then
cat > lat.hal <<EOF
loadrt threads name1=slow period1=$SERVO
loadrt timedelta count=1
addf timedelta.0 slow
start
loadusr -Wn lat pyvcp lat.xml
net sl timedelta.0.max => lat.sl
net sj timedelta.0.jitter => lat.sj
net st timedelta.0.out => lat.st
net reset lat.reset => timedelta.0.reset
waitusr lat
loadusr -w bash latexit.sh
EOF
else
cat > lat.hal <<EOF
loadrt threads name1=fast period1=$BASE name2=slow period2=$SERVO
loadrt timedelta count=2
addf timedelta.0 fast
addf timedelta.1 slow
start
loadusr -Wn lat pyvcp lat.xml
net sl timedelta.1.max => lat.sl
net sj timedelta.1.jitter => lat.sj
net st timedelta.1.out => lat.st
net bl timedelta.0.max => lat.bl
net bj timedelta.0.jitter => lat.bj
net bt timedelta.0.out => lat.bt
net reset lat.reset => timedelta.0.reset timedelta.1.reset
waitusr lat
loadusr -w bash latexit.sh
EOF
fi
cat > lat.xml <<EOF
<pyvcp>
<title title="LinuxCNC / HAL Latency Test"/>
<axisoptions/>
<table>
<tablerow/><tablespan columns="5"/><label wraplength="5i" justify="left">
<text>
"""
$machine on host $node $date
Kernel-release=$krelease
Kernel-version=$kversion
Let this test run for a few minutes, then note the maximum Jitter. You will use it while configuring LinuxCNC.
While the test is running, you should "abuse" the computer. Move windows around on the screen. Surf the web. Copy some large files around on the disk. Play some music. Run an OpenGL program such as glxgears. The idea is to put the PC through its paces while the latency test checks to see what the worst case numbers are."""
</text>
</label>
<tablerow/>
<label/>
<label text="Max Interval (ns)" font="Helvetica 12"/>
<label text="Max Jitter (ns)" font="Helvetica 12 bold"/>
<label text="Last interval (ns)" font="Helvetica 12"/>
<tablerow/><label text="Servo thread ($SERVO_HUMAN):"/><s32 halpin="sl"/><s32 halpin="sj" font="Helvetica 12 bold"/><s32 halpin="st"/>
EOF
if [ "$BASE" -ne 0 ]; then
cat >> lat.xml <<EOF
<tablerow/><label text="Base thread ($BASE_HUMAN):"/><s32 halpin="bl"/><s32 halpin="bj" font="Helvetica 12 bold"/><s32 halpin="bt"/>
EOF
fi
cat >> lat.xml <<EOF
<tablerow/>
<button text="Reset Statistics" halpin="reset" />
<tablerow/><tablesticky sticky="we"/><tablespan columns="5"/>
<table flexible_columns="[1,2,3]" uniform_columns="aaa">
<tablesticky sticky="we"/>
<tablerow/>
<label text="$RT_TEXT" $RT_FG relief="sunken" bd="1" anchor="w" padx="6"/>
<label text="$GOV" $GOV_FG relief="sunken" bd="1" anchor="center" padx="6"/>
<label text="$ISOL_TEXT" relief="sunken" bd="1" anchor="e" padx="6"/>
</table>
</table>
</pyvcp>
EOF
if [ "$BASE" -eq 0 ]; then
SIGNALS="halcmd gets sj"
else
SIGNALS="halcmd gets sj; halcmd gets bj"
fi
cat > latexit.sh <<EOF
L=\$(($SIGNALS
if [ -f "$HOME"/.latency ]; then cat "$HOME"/.latency; fi
) | sort -n | tail -1)
echo \$L > "$HOME"/.latency
EOF
halrun lat.hal

1016
scripts/linuxcnc.in Normal file

File diff suppressed because it is too large Load Diff

179
scripts/linuxcnc_info.in Executable file
View File

@@ -0,0 +1,179 @@
#!/bin/bash
# This script creates a file in /tmp and
# opens it with first visual editor found in a search list for editors
# Copyright: 2014
# Author: Dewey Garrett <dgarrett@panix.com>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
ofile=/tmp/linuxcnc_info.txt
PROG=$(basename "$0")
usage () {
cat <<EOF
$PROG creates file $ofile
USAGE:
$PROG [options]
options:
-s silent (suppress automatic opening of file)
-h help
EOF
exit 1
}
git_commit () {
dir=$(dirname "$0")
if [ "${dir}" = "/usr/bin" ] ; then
echo NA
else
( cd "$dir" && git rev-parse --short HEAD 2>/dev/null )
fi
}
# try to find a visual editor to show created file
# use list with existing preference first, then other common editors
editors="$VISUAL gedit mousepad geany nedit nano gvim abiword"
VIEWER=""
for e in $editors ; do
if [ -x "$(command -v "$e")" ] ; then
VIEWER=$e
break #use first editor found
fi
done
while getopts hs OPT; do
case $OPT in
s) VIEWER="";;
h) usage;;
*) usage;;
esac
done
[ -z "$VIEWER" ] && echo "No VIEWER: output to $ofile"
# all subsequent output:
exec 1>$ofile
exec 2>&1
function show () {
if [ -z "$1" ] ; then
echo
return
fi
name=$1
shift
value=$*
printf "%20s: %s\n" "$name" "$value"
}
function parse_cpuinfo () {
grep "$1" < /proc/cpuinfo | head -1 | cut -d: -f2-
}
function parse_after_colon () {
echo "$*"|cut -d: -f2-
}
function tryversion () {
prog="$1"
if [ "$(command -v "$prog")" ] ; then
ans=$($prog --version 2>/dev/null)
if [ -z "$ans" ] ; then
echo "?"
else
echo "$ans"
fi
else
echo "not_in_PATH"
fi
}
cat <<EOF
The file: $ofile
can be posted to a forum or a web site like:
http://pastebin.com
in order to provide information about the linuxcnc
system and configuration.
EOF
[ -n "$VIEWER" ] && echo "VIEWER=$VIEWER"
show " Date" "$(date)"
show " UTC Date" "$(date -u)"
show " this program" "$0"
show " uptime" "$(uptime)"
show " lsb_release -sa" "$(lsb_release -sa 2>/dev/null)"
show " linuxcnc" "$(command -v linuxcnc)"
show " pwd" "$(pwd -P)"
show " USER" "$USER"
show " LOGNAME" "$LOGNAME"
show " HOME" "$HOME"
show " EDITOR" "$EDITOR"
show " VISUAL" "$VISUAL"
show " LANGUAGE" "$LANGUAGE"
show " TERM" "$TERM"
show " COLORTERM" "$COLORTERM"
show " DISPLAY" "$DISPLAY"
show " DESKTOP" "$DESKTOP_SESSION"
show " display size" "$(parse_after_colon "$(xdpyinfo|grep dimensions)")"
show " PATH" "$PATH"
echo
echo "uname items:"
show " nodename -n" "$(uname -n)"
show " kernel-name -s" "$(uname -s)"
show " kernel-vers -v" "$(uname -v)"
show " machine -m" "$(uname -m)"
show " processor -p" "$(uname -p)"
show " platform -i" "$(uname -i)"
show " oper system -o" "$(uname -o)"
show ""
echo "/proc items:"
show " cmdline" "$(< /proc/cmdline)"
show " model name" "$(parse_cpuinfo "model name")"
show " cores" "$(parse_cpuinfo "cpu cores")"
show " cpu MHz" "$(parse_cpuinfo "cpu MHz")"
show " parport" "$(grep parport < /proc/ioports)"
show " serial" "$(grep serial < /proc/ioports)"
echo
echo "Versions:"
show " gcc" "$(gcc --version|head -1)"
show " python" "$(@PYTHON@ --version 2>&1)"
show " git" "$(tryversion git)"
show " git commit" "$(git_commit)"
show " tcl" "$(echo "puts $::tcl_version"|tclsh)"
show " tk" "$(echo "puts $::tk_version;destroy ."|wish)"
show " glade" "$(tryversion glade)"
echo
echo "linuxcnc_var all:"
echo
for n in $(linuxcnc_var all) ; do
show "${n%%=*}" "${n##*=}"
done
# Not everybody runs Debian based distros
echo
if command -v dpkg > /dev/null; then
echo "dpkg -l '*linuxcnc*':"
dpkg -l '*linuxcnc*'
else
echo "No 'dpkg' available, different distro, probably."
fi
echo
[ -n "$VIEWER" ] && $VIEWER "$ofile"
exit 0

104
scripts/linuxcnc_var.in Normal file
View File

@@ -0,0 +1,104 @@
#!/bin/bash
# maintainer: when adding items update three places:
# usage(), show_all(), and show_item()
#
# note: report LINUXCNCVERSION=$EMC2VERSION (as does linuxcnc.in)
# Copyright: 2014
# Author: Dewey Garrett <dgarrett@panix.com>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
function usage () {
cat <<EOF
Retrieve Linuxcnc Variables
Usage:
$(basename "$0") [ varname | all ]
Varnames supported:
LINUXCNCVERSION
LINUXCNC_AUX_GLADEVCP
LINUXCNC_AUX_EXAMPLES
REALTIME
RTS
HALLIB_DIR
PYTHON
Option 'all' returns varname=value for all supported varnames
EOF
exit 1
}
function show_all () {
echo "LINUXCNCVERSION=@EMC2VERSION@
LINUXCNC_AUX_GLADEVCP=@LINUXCNC_AUX_GLADEVCP@
LINUXCNC_AUX_EXAMPLES=@LINUXCNC_AUX_EXAMPLES@
REALTIME=@REALTIME@
RTS=@RTS@
HALLIB_DIR=@HALLIB_DIR@
PYTHON=@PYTHON@"
}
function show_item () {
case $1 in
LINUXCNCVERSION) echo @EMC2VERSION@;;
LINUXCNC_AUX_GLADEVCP) echo @LINUXCNC_AUX_GLADEVCP@;;
LINUXCNC_AUX_EXAMPLES) echo @LINUXCNC_AUX_EXAMPLES@;;
REALTIME) echo @REALTIME@;;
RTS) echo @RTS@;;
HALLIB_DIR) echo @HALLIB_DIR@;;
PYTHON) echo @PYTHON@;;
all) show_all;;
*) echo UNKNOWN; exit 1;;
esac
}
case $# in
0) usage;;
1) show_item "$1";;
*) usage;;
esac
exit 0
# Example shell usage to populate environment in a sourced script:
# for line in $(linuxcnc_var all) ; do
# name=${line%%=*}
# value=${line##*=}
# echo "name=$name value=$value"
# export "$name"="$value"
# done
# Example tcl usage:
# foreach line [exec linuxcnc_var all] {
# set l [split $line =]
# set name [lindex $l 0]
# set value [lindex $l 1]
# set V($name) $value
# }
# parray V
# Example python usage:
# import subprocess
# s = subprocess.Popen(['linuxcnc_var','all']
# ,stdout=subprocess.PIPE
# ,stderr=subprocess.PIPE
# p,e = s.communicate()
# v={}
# for line in p.split('\n'):
# if line == '': continue
# name = line.split('=')[0]
# value = line.split('=')[1]
# v[name] = value
# print(v)

View File

@@ -0,0 +1,35 @@
#!/bin/sh
# file types supported: .ini, .demo
EMC_BIN_DIR=@EMC2_BIN_DIR@
EMC_SCRIPT_DIR=@EMC2_HOME@/scripts
if [ -x $EMC_BIN_DIR/linuxcnc ]; then
EMC=$EMC_BIN_DIR/linuxcnc
else
EMC=$EMC_SCRIPT_DIR/linuxcnc
fi
filename="$1"
name="$2"
comment="$3"
case ${filename##*.} in
ini) cat <<EOF
[Desktop Entry]
Name=$name
Exec=$EMC '$filename'
Type=Application
Comment=$comment
Icon=linuxcncicon
EOF
;;
demo) cat <<EOF
[Desktop Entry]
Name=$name
Exec=$filename
Type=Application
Comment=$comment
Icon=linuxcncicon
EOF
;;
esac

41
scripts/make-docs-pdf-index Executable file
View File

@@ -0,0 +1,41 @@
#!/bin/bash
#
# Build a flat aggregate of every language's PDF under docs/build/html/pdf/
# (hardlinks, cheap and idempotent) plus an index listing. The PDFs
# themselves live in the per-language trees docs/build/html/<lang>/pdf/; this
# flat folder keeps the legacy /pdf/ URL working with all languages side by
# side.
#
set -e
# Assume we're running in src/, we can't call `git rev-parse
# --show-toplevel` because when building from dsc we don't have a
# git repo.
TOPLEVEL=${PWD}/..
HTML="${TOPLEVEL}/docs/build/html"
DEST="${HTML}/pdf"
mkdir -p "${DEST}"
# Hardlink every per-language PDF into the flat folder. Names already carry a
# _<lang> suffix so there are no collisions. mindepth 3 skips DEST itself
# (docs/build/html/pdf/*.pdf is only two levels below html/).
find "${HTML}" -mindepth 3 -path '*/pdf/*.pdf' -print0 \
| while IFS= read -r -d '' F; do
ln -f "${F}" "${DEST}/$(basename "${F}")"
done
TITLE="LinuxCNC PDF docs ($(cat "${TOPLEVEL}"/VERSION))"
PDF_INDEX="${DEST}/index.html"
rm -f "${PDF_INDEX}"
{
echo "<html>"
echo "<head><title>${TITLE}</title></head>"
echo "<body><h1>${TITLE}</h1><p>"
for F in $(cd "${DEST}" && find . -maxdepth 1 -name '*.pdf' | sort); do
echo " <a href=\"${F#./}\">${F#./}</a><br>"
done
echo "</body>"
echo "</html>"
} >> "${PDF_INDEX}"

36
scripts/man2adoc-migrate Executable file
View File

@@ -0,0 +1,36 @@
#!/bin/sh
# Used 2025-07 for one-time migration away from groff manual pages to asciidoc manual
# pages converted to groff by the build process.
# Convert all non-link manual pages to adoc
for m in docs/man/man?/*; do
test -L "$m" && continue
n=$(echo "$m" |sed s%docs/man%docs/src/man%).adoc
if [ -e "$n" ] || [ -e "$n".in ] || grep -q '^.so' "$m"; then
echo "E: Not converting $m due to roff include or existing adoc version"
else
echo "I: Converting '$m' to '$n'"
pandoc -f man -t asciidoc "$m" -o "$n"
git add "$n"
git rm --quiet "$m"
fi
done
# Make sure they can be converted back to manpage format
for n in docs/src/man/man?/*.adoc; do
#echo "Making sure $n got required header"
b=$(basename "$n" .adoc)
name=$(echo "$b"|cut -d. -f1)
s=$(echo "$b"|cut -d. -f2)
if ! head -1 "$n" | grep -q "= $name ($s)" ||
! "/usr/bin/asciidoc" --backend docbook \
-a "a2x-format=manpage" --doctype manpage \
--attribute "mansource=LinuxCNC" \
--attribute "manmanual=LinuxCNC Documentation" \
--verbose \
--out-file x.man "$m"
then
(echo "= $name($s)"; echo; cat "$n") > "$n.new" && mv "$n.new" "$n"
git add "$n"
fi
done

156
scripts/manpage-name-check.py Executable file
View File

@@ -0,0 +1,156 @@
#!/usr/bin/env python3
# Report man pages whose rendered NAME drifted from their filename.
# asciidoctor takes the .TH name from the page's NAME section, the build pins the filename with -o, so a translated NAME just leaves the two disagreeing.
# Run after building the docs. Argument: the built man tree (default docs/build/man).
# Quiet when nothing drifted. When something has, it prints each page with the
# string to search in Weblate and its current value, so a translator can fix it.
# Never fails the build unless --enforce or MANPAGE_NAME_CHECK_ENFORCE is set:
# under GitHub Actions it writes the same list to the job
# summary and emits one warning pointing at it. The NAME lines are managed in
# Weblate, so fixes go there.
import os
import re
import sys
import glob
# The script lives in scripts/, the docs are one level up at ../docs, so it runs from anywhere.
HERE = os.path.dirname(os.path.realpath(__file__, strict=True))
DOCS = os.path.normpath(os.path.join(HERE, '..', 'docs'))
MAN = os.path.join(DOCS, 'build/man')
PO = os.path.join(DOCS, 'po')
SRC = os.path.join(DOCS, 'src/man')
def th_name(troff):
"""The .TH page name, lowercased with the roff backslash stripped, or None for an .so alias stub."""
with open(troff, encoding='utf-8', errors='replace') as f:
if f.readline().startswith('.so '):
return None
f.seek(0)
for line in f:
m = re.match(r'\.TH "([^"]*)"', line)
if m:
return m.group(1).replace('\\', '').lower()
return None
def name_line(page):
"""The first line of the NAME section of a page's AsciiDoc source, or None for a comp page rendered straight to troff."""
for adoc in sorted(glob.glob(f'{SRC}/man*/{page}.*.adoc')):
lines = open(adoc, encoding='utf-8', errors='replace').read().splitlines()
for i, line in enumerate(lines):
if line.strip() == '== NAME':
for rest in lines[i + 1:]:
if rest.strip():
return rest.strip()
return None
_po = {}
def po(lang):
"""Parse docs/po/<lang>.po into {msgid: msgstr}, joining the continuation lines that wrap one string."""
if lang not in _po:
d = {}
mid = mstr = mode = None
for line in open(f'{PO}/{lang}.po', encoding='utf-8', errors='replace'):
if line.startswith('msgid '):
if mid is not None:
d[mid] = mstr or ''
mid, mstr, mode = line[6:].strip().strip('"'), None, 'id'
elif line.startswith('msgstr '):
mstr, mode = line[7:].strip().strip('"'), 'str'
elif line.startswith('"'):
s = line.strip().strip('"')
if mode == 'id':
mid += s
elif mode == 'str':
mstr += s
elif not line.strip() and mid is not None:
d[mid] = mstr or ''
mid = mstr = mode = None
if mid is not None:
d[mid] = mstr or ''
_po[lang] = d
return _po[lang]
def weblate_string(lang, page):
"""Return (search, current): the text to search for in Weblate and its current translation."""
d = po(lang)
th = page.upper()
# A comp page's name is a troff .TH string, searched by the uppercase name.
if d.get(th, '').strip() and d[th].strip().lower() != page.lower():
return th, d[th]
# An AsciiDoc page's name lives in the NAME line, searched by its text.
nl = name_line(page)
if nl:
cur = d.get(nl)
if cur is None: # the source line and the msgid can differ in trailing text
for mid, mstr in d.items():
if mid.startswith(page) and ' - ' in mid:
nl, cur = mid, mstr
break
return nl, cur if cur is not None else '(search the text)'
return page, '(unknown)'
def drifted(man):
"""Yield (lang, page) for every built man page whose .TH name does not match its filename."""
for dirpath, _dirs, files in os.walk(man):
for fn in files:
if not re.search(r'\.\d$', fn):
continue
th = th_name(os.path.join(dirpath, fn))
page = re.sub(r'\.\d+$', '', fn)
if th is None or th == page.lower():
continue
lang = os.path.relpath(dirpath, man).split(os.sep)[0]
yield ('en' if re.fullmatch(r'man\d+', lang) else lang), page
def collect(man):
"""All drifted rows as (lang, search, current), sorted, English dropped."""
rows = []
for lang, page in drifted(man):
if lang == 'en': # English is the reference, it never drifts
continue
search, current = weblate_string(lang, page)
rows.append((lang, search, current))
rows.sort()
return rows
def print_local(rows):
print(f'# {len(rows)} man-page NAME mismatches. Search the "search" text in the Weblate docs component, language in brackets.')
for lang, search, current in rows:
print(f'\n[{lang}]')
print(f' search: {search}')
print(f' current: {current}')
def write_summary(rows, fh):
fh.write(f'## Manpage NAME mismatches: {len(rows)}\n\n')
fh.write('Translated NAME lines that drifted from the command name.\n')
fh.write('Search the "search" text in the Weblate docs component for the language and fix it there.\n\n')
fh.write('| lang | search | current |\n')
fh.write('| ---- | ------ | ------- |\n')
for lang, search, current in rows:
cells = (c.replace('|', '\\|') for c in (lang, search, current))
fh.write('| %s | %s | %s |\n' % tuple(cells))
def main(argv):
enforce = '--enforce' in argv or os.environ.get('MANPAGE_NAME_CHECK_ENFORCE')
args = [a for a in argv if a != '--enforce']
man = args[1] if len(args) > 1 else MAN
# A built tree has troff pages; without them the docs are not built and a
# silent "all clean" would read as success when nothing was actually scanned.
if not any(re.search(r'\.\d$', f) for _root, _dirs, files in os.walk(man) for f in files):
print(f'{man}: no built man pages found, build the docs first (make manpages docs).', file=sys.stderr)
return 1
rows = collect(man)
if not rows:
return 0 # quiet when nothing drifted
print_local(rows)
summary = os.environ.get('GITHUB_STEP_SUMMARY')
if summary:
with open(summary, 'a', encoding='utf-8') as fh:
write_summary(rows, fh)
if os.environ.get('GITHUB_ACTIONS'):
print(f'::warning title=manpage NAME drift::{len(rows)} manpage NAME line(s) disagree with their filename, see the job summary for the list to fix in Weblate')
return 1 if enforce else 0
if __name__ == '__main__':
sys.exit(main(sys.argv))

151
scripts/monitor-xhc-hb04 Executable file
View File

@@ -0,0 +1,151 @@
#!/usr/bin/tclsh
#-----------------------------------------------------------------------
# Copyright: 2014
# Author: Dewey Garrett <dgarrett@panix.com>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#-----------------------------------------------------------------------
set ::progname [file tail $::argv0]
set ::periodic_delay_ms 2000
set ::popup_hold_ms 0 ;# use 0 for forever
set ::debug 0
proc bool_from_string {v} {
# hal getp returns TRUE,FALSE
switch [string tolower $v] {
true {set v 1}
false {set v 0}
}
return $v
} ;# bool_from_string
proc dputs {msg} {
if $::debug {
puts stderr $::progname:debug:$msg
}
} ;# dputs
proc start_monitor {} {
package require Hal
package require Tk
wm withdraw .
set ::popupw [toplevel .p]
wm withdraw $::popupw
wm title $::popupw $::progname
wm protocol $::popupw WM_DELETE_WINDOW hide
pack [label ${::popupw}.t -text [now] -font bold]
pack [label ${::popupw}.l -text "startup" -font bold]
pack [button ${::popupw}.b -text "OK" -command dismiss] -anchor e
if [catch {set value [hal getp xhc-hb04.connected]} msg ] {
# this can occur if loadusr xhc-hb04 fails,
puts "$::progname: xhc-hb04 comp not loaded"
puts "$::progname: <$msg>"
puts "$::progname: exiting"
return
}
set ::connected false
if [catch { set ::connected [hal getp xhc-hb04.connected]
} msg] {
puts "$::progname: connected? <$msg>"
}
set ::connected [bool_from_string $::connected]
set ::require_pendant false
if [catch { set ::require_pendant [hal getp xhc-hb04.require_pendant]
} msg] {
puts "$::progname: require_pendant? <$msg>"
puts "$::progname: pendant connection not required, Continuing"
}
set ::require_pendant [bool_from_string $::require_pendant]
dputs "startup:connected=$::connected required=$::require_pendant"
if {!$::require_pendant && !$::connected} {
popup "\nrequire_pendant==0\nPendant not connected at startup"
}
after $::periodic_delay_ms check
} ;# start_monitor
proc now {} {
return [clock format [clock seconds]]
} ;# now
proc popup {msg} {
dputs $msg
$::popupw.l configure -text [now]
$::popupw.l configure -text $msg
center $::popupw
if {$::popup_hold_ms != 0} {
after $::popup_hold_ms [list wm iconify $::popupw]
}
} ;# popup
proc center {w} {
update idletasks
set x [expr [winfo screenwidth $w]/2 \
- [winfo reqwidth $w]/2 - [winfo vrootx [winfo parent $w]]]
set y [expr [winfo screenheight $w]/2 \
- [winfo reqheight $w]/2 - [winfo vrooty [winfo parent $w]]]
wm geom $w +$x+$y
wm deiconify $w
} ;# center
proc hide {} {
wm withdraw $::popupw
} ;# hide
proc dismiss {} {
wm withdraw $::popupw
} ;# dismiss
proc check {} {
if [catch { set ::connected_new [hal getp xhc-hb04.connected]
} msg] {
puts "$::progname <$msg>"
}
set ::connected_new [bool_from_string $::connected_new]
dputs "check:connected_new=$::connected_new required=$::require_pendant"
set allow_recheck 1
if {$::connected_new != $::connected} {
if $::connected_new {
set msg "\nConnected to pendant"
} else {
set msg "\nLost connection to pendant"
}
if $::require_pendant {
if {$::connected_new} {
set msg "$msg\nUnexpected"
} else {
# xhc-hb04 will timeout and exit so no more checks
set msg "$msg\nReconnect not supported for require_pendant==1"
set allow_recheck 0
}
}
dputs "$::progname: $msg"
popup $msg
}
set ::connected $::connected_new
if $allow_recheck {after $::periodic_delay_ms check}
} ;# check
# begin -----------------------------------------------------
start_monitor

920
scripts/moveoff_gui Executable file
View File

@@ -0,0 +1,920 @@
#!/usr/bin/tclsh
# library procs
# Note: use linuxcnc_var script since this program can be
# started without using the linuxcnc script and
# ::env(HALLIB_DIR) will not exist
set hallib_dir [exec linuxcnc_var HALLIB_DIR]
source [file join $hallib_dir hal_procs_lib.tcl]
source [file join $hallib_dir util_lib.tcl]
# A gui to demonstrate the use of the moveoff component for
# applying Hal-only offsets.
# For more info:
# $ moveoff_gui --help -- command line options
# $ man moveoff_gui -- additional info
# $ man moveoff -- about the moveoff component
#-----------------------------------------------------------------------
# Copyright: 2014
# Authors: Dewey Garrett <dgarrett@panix.com>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#-----------------------------------------------------------------------
proc wmposition {top} {
set geo [wm geometry $top]
return [string range $geo [string first + $geo] end]
} ;# wmposition
proc wmrestore {w position} {
if {[wm state $w] == "withdrawn"} {
wm deiconify $w
wm geometry $w $position
}
} ;# wmrestore
proc wmcenter w {
wm withdraw $w
update idletasks
set x [expr [winfo screenwidth $w]/2 \
- [winfo reqwidth $w]/2 - [winfo vrootx [winfo parent $w]]]
set y [expr [winfo screenheight $w]/2 \
- [winfo reqheight $w]/2 - [winfo vrooty [winfo parent $w]]]
wm geom $w +$x+$y
wm deiconify $w
} ;# wmcenter
proc withdraw_with_save_loc {top} {
set ::MV(location) [wmposition $top]
wm withdraw $top
} ;# withdraw_with_save_loc
proc restore_using_save_loc {top} {
wmrestore $top $::MV(location)
} ;# restore_using_save_loc $::MV(top)
proc qid {} {
# unique identifier
if ![info exists ::MV(qid)] { set ::MV(qid) 0 }
return [incr ::MV(qid)]
} ;# qid
proc get_move_enable {} {
# special case boolean, used for setting
# ::MV(enable,offsets) which is the -variable
# for the Enable checkbutton and may be
# managed externally
# ensure it is 1|0 for comparisons always
if {[hal getp $::m.move-enable]} {
return 1
} else {
return 0
}
} ;# get_move_enable
proc do_poll {} {
set ::MV(enable,offsets) [get_move_enable]
set apply_offsets [hal getp $::m.apply-offsets]
set offset_applied [hal getp $::m.offset-applied]
set at_limit [hal getp $::m.waypoint-limit]
if {$apply_offsets != $::MV(old,apply_offsets)} {
if {$apply_offsets} {
if !$::MV(no_display) {restore_using_save_loc $::MV(top)}
if { $::MV(control_move_enable) \
&& $::MV(auto_enable_apply_offsets) \
} {
set ::MV(enable,offsets) 1; hal setp $::m.move-enable 1
}
} else {
# apply-offsets deasserted ==> moveoff component will remove offsets
zero_all_offset_inputs
withdraw_with_save_loc $::MV(top)
}
}
if {$::MV(enable,offsets) != $::MV(old,enable,offsets)} {
foreach aname $::MV(axes) {
if $::MV(enable,offsets) {
$::MV(button,apply,$aname) configure -state normal
} else {
$::MV(button,apply,$aname) configure -state disabled
}
}
if { !$::MV(enable,offsets) \
&& !$::MV(entry,keep_on_disable)} {
foreach letter {x y z a b c u v w} {
set ::MV(offset,$letter) [format "$::MV(offset,format)" 0]
}
}
}
set status_msg ""
if {$at_limit} {
set status_msg "Waypoint limit (Disable required)"; set bg orange
} else {
if $offset_applied {
if $::MV(enable,offsets) {
set status_msg "OFFSETS ACTIVE"; set bg red
$::MV(button,enable) conf -state normal
} else {
set status_msg "Removing offsets"; set bg yellow
$::MV(button,enable) conf -state disabled
}
} else {
if $::MV(enable,offsets) {
set status_msg "Offsets Enabled"
set bg cyan
$::MV(button,enable) conf -state normal
} else {
set status_msg "Offsets Disabled"; set bg green
$::MV(button,enable) conf -state normal
}
}
}
# move_enable deasserted while apply_offsets true
# Note: apply_offsets included in case external connection
# deasserts it
if { ($::MV(enable,offsets) != $::MV(old,enable,offsets)) \
&& !$::MV(enable,offsets) \
&& $::MV(opt,resume_withdelay) \
&& ($::MV(old,enable,offsets) != -1) \
&& $apply_offsets \
} {
# move_enable deasserted ==> moveoff component will remove offsets
after 0 request_resume_after_delay
}
if { ![hal getp motion.motion-enabled]} {
set bg white
set status_msg "${status_msg} --- Motion Off"
if { $::MV(control_move_enable) } {
set ::MV(enable,offsets) 0; hal setp $::m.move-enable 0
$::MV(button,enable) conf -state disabled
zero_all_offset_inputs
}
}
if {"$status_msg" != $::MV(old,status_msg)} {
set ::MV(label,applied,text) $status_msg
$::MV(label,applied) configure -state normal -bg $bg
if { !$::MV(opt,no_resume_inhibit) } {
if $offset_applied {
disallow_resume
} else {
allow_resume
}
}
}
foreach aname $::MV(axes) {
set jnum $::MV($aname,jnum)
set ::MV(current,$aname) [format "$::MV(current,format)" \
[hal getp $::m.offset-current-${jnum}]]
}
set waypoint_pct [hal getp $::m.waypoint-percent-used]
set waypoint_msg "Waypoint Usage: ${waypoint_pct} %"
set ::MV(label,message,text) "$waypoint_msg"
if {"$status_msg" != $::MV(old,waypoint_msg)} {
if {$waypoint_pct >= $::MV(waypoint,threshold,low)} {
pack $::MV(label,message,frame) -expand 1 -fill x
if {$waypoint_pct > $::MV(waypoint,threshold,high)} {
$::MV(label,message) conf -bg red
} else {
$::MV(label,message) conf -bg "#d9d9d9"
}
} else {
$::MV(label,message) conf -bg "#d9d9d9"
pack forget $::MV(label,message,frame)
}
}
set ::MV(old,apply_offsets) $apply_offsets
set ::MV(old,enable,offsets) $::MV(enable,offsets)
set ::MV(old,status_msg) $status_msg
set ::MV(old,waypoint_msg) $waypoint_msg
after $::MV(poll,ms) do_poll
} ;# do_poll
proc request_resume_after_delay {} {
if [get_move_enable] {
return ;# could get canceled by another writer
}
set offset_applied [hal getp $::m.offset-applied]
if { !$offset_applied} {
resume_after_delay
} else {
#reschedule
after $::MV(resume,delay,sample,ms) request_resume_after_delay
}
} ;# request_resume_after_delay
proc resume_after_delay {} {
withdraw_with_save_loc $::MV(top)
set dly [format %.1f $::MV(resume,delay,secs)]
set ::MV(resume,msg) "Auto Resume in $dly secs"
set t [toplevel .resuming]
set ::MV(resume,widget) $t
wm title $t "$::MV(prog) Auto Resume"
set msg_fsize $::MV(font,size)
pack [label $t.l -textvar ::MV(resume,msg) \
-font [list Helvetica $msg_fsize bold] \
] -side top -fill both
set cancel_fsize [expr $::MV(font,size) + 8]
if !$::MV(opt,no_cancel_autoresume) {
pack [button $t.b -text "Cancel Auto Resume" -bd 5 \
-font [list Helvetica $cancel_fsize bold] \
-command cancel_auto_resume \
] -expand 1 -fill both
}
if $::MV(no_display) {
# use window manager placement for auto resume cancel widget
} else {
# use the same geometry as the toplevel for the popup:
wm geometry $t [wm geometry $::MV(top)]
}
set ::MV(resume,delay,remaining,ms) [expr 1000 *$::MV(resume,delay,secs)]
after $::MV(resume,delay,sample,ms) pulse_resume_wait
} ;# resume_after_delay
proc pulse_resume_wait {} {
set dly_ms $::MV(resume,delay,remaining,ms)
if { ![hal getp halui.program.is-paused] } {
# some other actor resumed
after $::MV(resume,pulse,ms) clear_resume
return
}
if {$dly_ms <= 0} {
hal setp halui.program.resume 1
after $::MV(resume,pulse,ms) clear_resume
destroy $::MV(resume,widget)
} else {
set dly_secs [format %.1f [expr $dly_ms/1000.]]
set ::MV(resume,msg) "Auto resume in $dly_secs secs"
set ::MV(resume,delay,remaining,ms) [expr $dly_ms \
- $::MV(resume,delay,sample,ms)]
set ::MV(resume,cancel,id) \
[after $::MV(resume,delay,sample,ms) pulse_resume_wait]
}
} ;# pulse_resume
proc cancel_auto_resume {} {
catch {after cancel $::MV(resume,cancel,id)}
clear_resume
if !$::MV(no_display) {restore_using_save_loc $::MV(top)}
# no competing app connected to $::m.move-enable
if { $::MV(control_move_enable) } {
set ::MV(enable,offsets) 1; hal setp $::m.move-enable 1
zero_all_offset_inputs
}
} ;# cancel_auto_resume
proc clear_resume {} {
hal setp halui.program.resume 0
destroy $::MV(resume,widget)
} ;# pulse_resume
proc do_offset {aname} {
if { ![hal getp motion.motion-enabled] } {return}
set jnum $::MV($aname,jnum)
hal setp $::m.offset-in-${jnum} $::MV(offset,$aname)
set ::MV(offset,$aname) [format "$::MV(offset,format)" \
$::MV(offset,$aname)]
} ;# do_offset
proc bump_offset {aname value} {
if { ![hal getp motion.motion-enabled] } {return}
set jnum $::MV($aname,jnum)
switch $value {
plus {set ::MV(offset,$aname) [format "$::MV(offset,format)" \
[expr $::MV(offset,$aname) + $::MV(increment)]]
}
zero {set ::MV(offset,$aname) 0
}
minus {set ::MV(offset,$aname) [format "$::MV(offset,format)" \
[expr $::MV(offset,$aname) - $::MV(increment)]]
}
}
hal setp $::m.offset-in-${jnum} $::MV(offset,$aname)
} ;# bump_offset
proc toggle_enable_backtrack {args} {
if {$::MV(enable,backtrack)} {
hal setp $::m.backtrack-enable 1
} else {
hal setp $::m.backtrack-enable 0
}
} ;# toggle_enable_backtrack
proc toggle_enable_offsets {args} {
if {$::MV(enable,offsets)} {
hal setp $::m.move-enable 1
} else {
hal setp $::m.move-enable 0
}
zero_all_offset_inputs
} ;# toggle_enable_offsets
proc zero_all_offset_inputs {} {
if {! $::MV(control_move_enable)} { return }
foreach aname $::MV(axes) {
set ::MV(offset,$aname) [format "$::MV(offset,format)" 0]
set jnum $::MV($aname,jnum)
hal setp $::m.offset-in-${jnum} 0.0
}
} ;# zero_all_offset_inputs
proc make_gui {} {
set t $::MV(top)
wm withdraw $::MV(top)
set f1 [frame $t.[qid] -relief groove -bd 4]
pack $f1 -fill x -expand 1 -side top
set ::MV(enable,offsets) [get_move_enable]
set ::MV(button,enable) noop ;# anticipate possible external control
if $::MV(control_move_enable) {
set f1a [frame $f1.[qid] -relief ridge -bd 2]
pack $f1a -fill x -expand 1 -side top
set ::MV(button,enable) [checkbutton $f1a.[qid] \
-text "Enable Offsets" \
-anchor w \
-variable ::MV(enable,offsets) \
-command toggle_enable_offsets]
pack $::MV(button,enable) -side left -fill x -expand 1 -anchor w
if {[llength $::MV(axes)] > 1} {
set ::MV(enable,backtrack) 1
set ::MV(button,backtrack) [checkbutton $f1a.[qid] \
-text "Backtrack" \
-anchor e \
-variable ::MV(enable,backtrack) \
-command toggle_enable_backtrack]
pack $::MV(button,backtrack) -side left -fill x -expand 1 -anchor w
} else {
set ::MV(enable,backtrack) 0 ;# no backtrack for single axis
}
hal setp $::m.backtrack-enable $::MV(enable,backtrack)
pack [label $f1.[qid] -text Increment:] -side left
set ::MV(increment) [lindex $::MV(increments) 0] ;# default
foreach inc $::MV(increments) {
pack [radiobutton $f1.[qid] -variable ::MV(increment) \
-text $inc -value $inc \
] -side left
}
}
foreach aname $::MV(axes) {
set jnum $::MV($aname,jnum)
set f2 [frame $t.[qid]]
pack $f2 -fill x -expand 1
set ::MV(button,apply,$aname) noop
set ::MV(entry,offset,$aname) noop
if $::MV(show,entry) {
set ::MV(button,apply,$aname) [button $f2.[qid] -bd 2 -padx 2 -pady 2\
-text "$aname Offset" \
-command [list do_offset $aname]]
pack $::MV(button,apply,$aname) -side left -anchor w
set ::MV(entry,offset,$aname) [entry $f2.[qid] \
-width 10 \
-textvariable ::MV(offset,$aname) \
-justify right \
]
pack $::MV(entry,offset,$aname) -side left -anchor w -fill x
bind $::MV(entry,offset,$aname) <Return> [list do_offset $aname]
}
if $::MV(show,increments) {
set ::MV(bump,minus,$aname) [button $f2.[qid] -bd 2 \
-width $::MV(button,increment,width) \
-text "-" \
-command [list bump_offset $aname minus]]
pack $::MV(bump,minus,$aname) -side left
set ::MV(bump,zero,$aname) [button $f2.[qid] -bd 2 \
-width $::MV(button,increment,width) \
-text "0" \
-command [list bump_offset $aname zero]]
pack $::MV(bump,zero,$aname) -side left
set ::MV(bump,plus,$aname) [button $f2.[qid] -bd 2 \
-width $::MV(button,increment,width) \
-text "+" \
-command [list bump_offset $aname plus]]
pack $::MV(bump,plus,$aname) -side left
}
if !$::MV(show,entry) {
set Aname [string toupper $aname]
pack [label $f2.[qid] -text "Current $Aname Offset:"] -side left
}
set ::MV(label,current,$aname) [label $f2.[qid] \
-width 10 -bd 0 \
-fg red -bg black\
-textvariable ::MV(current,$aname) \
-justify right \
]
pack $::MV(label,current,$aname) -side left -anchor w -fill x -expand 1
}
set f3 [frame $t.[qid] -relief sunken -bd 4]
pack $f3 -fill x -expand 1
set ::MV(label,applied,text) ""
set ::MV(label,applied) [label $f3.l \
-width 30 \
-anchor w \
-state normal \
-textvariable ::MV(label,applied,text) ]
pack $::MV(label,applied) -side left -fill x -expand 1
set f4 [frame $t.[qid] -relief sunken -bd 4]
pack $f4 -fill x -expand 1
set ::MV(label,message,frame) $f4
pack forget $::MV(label,message,frame)
set ::MV(label,message,text) "Remove offsets before resuming"
set ::MV(label,message) [label $f4.l \
-width 30 \
-state normal \
-textvariable ::MV(label,message,text) ]
pack $::MV(label,message) -side left -fill x -expand 1
if {$::MV(location) == "center"} {
set ::MV(location) [wmcenter $::MV(top)]
}
wm resizable $t 0 0
} ;# make_gui
proc noop {args} {
} ;# noop
proc bye {} {
if 0 {
set offset_applied [hal getp $::m.offset-applied]
if $offset_applied {
puts "$::MV(prog):Disallow window delete while offset applied"
return
}
set txt "Are you Sure?\n
You probably should resume in the main GUI"
set ans [tk_messageBox -type okcancel \
-title "Close $::MV(prog)" \
-icon question \
-message "$txt"
]
if {"$ans" == "cancel"} return
destroy $::MV(top)
destroy .
return
}
puts "$::MV(prog):Disallow window delete"
return
} ;# bye
proc bitpin_exists {pattern} {
# return unique name iff unique bit pin matching pattern exists
set ans [string trim [hal list pin -tbit $pattern]]
if {[llength $ans] == 1} {return "$ans"}
return ""
} ;# bitpin_exists
proc connect_pin_to_sig {pinname new_signame} {
if {[is_connected $pinname existing_signame] != "not_connected"} {
set use_signame $existing_signame
} else {
set use_signame $new_signame
}
set msg ""
if {"$existing_signame" != ""} {
set msg "(attaching)"
}
puts "$::MV(prog):net $use_signame $pinname $msg"
hal net $use_signame $pinname
return "$use_signame"
} ;# connect_pin_to_sig
proc disallow_resume {} {
set resume_inhibit_pin [bitpin_exists *.resume-inhibit]
if {"$resume_inhibit_pin" == ""} return
hal setp $resume_inhibit_pin 1
} ;# disallow_resume
proc allow_resume {} {
set resume_inhibit_pin [bitpin_exists *.resume-inhibit]
if {"$resume_inhibit_pin" == ""} return
hal setp $resume_inhibit_pin 0
} ;# allow_resume
proc set_defaults {} {
# housekeeping:
set ::MV(control_move_enable) 1
set ::MV(no_display) 0
set ::MV(show,entry) 1
set ::MV(show,increments) 1
set ::m mv ;# expected name of the moveoff component
# (loadrt moveoff names=mv)
set ::MV(old,apply_offsets) -1
set ::MV(old,enable,offsets) -1
set ::MV(old,status_msg) -1
set ::MV(old,waypoint_msg) -1
set ::MV(offset,format) "%g"
set ::MV(current,format) $::MV(offset,format)
# defaults:
set ::MV(parm,axes) xyz ;# not a list,no spaces
set ::MV(font) {Helvetica 14 bold}
set ::MV(font,family) [lindex $::MV(font) 0]
set ::MV(font,size) [lindex $::MV(font) 1]
set ::MV(font,weight) [lindex $::MV(font) 2]
set ::MV(location) center ;# start position: center | +x+y (in pixels)
# example set ::MV(location) +10+10
# example set ::MV(location) center
foreach letter {x y z a b c u v w} {
set ::MV(offset,$letter) [format "$::MV(offset,format)" 0] ;# initial value
}
set ::MV(increments) {0.001 0.01 0.10 1.0} ;# increments for +/- buttons
set ::MV(opt,mode) onpause
set ::MV(opt,debug) 0
set ::MV(opt,resume_withdelay) 0
set ::MV(opt,noentry) 0
set ::MV(opt,no_resume_inhibit) 0
set ::MV(opt,no_pause_requirement) 0
set ::MV(opt,no_cancel_autoresume) 0
set ::MV(opt,no_display) 0
set ::MV(resume,pulse,ms) 100
set ::MV(resume,delay,sample,ms) 500
set ::MV(resume,delay,secs) 5
# defaults with no cmdline opts:
set ::MV(auto_enable_apply_offsets) 0 ;# for immediate enable
set ::MV(poll,ms) 1000 ;# polling interval
set ::MV(button,increment,width) 3 ;# width in chars
set ::MV(waypoint,threshold,low) 50 ;# percent
set ::MV(waypoint,threshold,high) 80 ;# percent
set ::MV(entry,keep_on_disable) 0 ;# default 0 is remove them
} ;# set_defaults
proc verify_context {} {
# return "" if ok, else errtxt
if !$::MV(opt,no_pause_requirement) {
if {"" == [bitpin_exists halui.program.is-paused]} {
return "linuxcnc and halui must be running\n
For info:\n$::MV(prog) --help | more"
}
}
if {"" == [bitpin_exists $::m.apply-offsets]} {
return "moveoff component must be loaded with name: $::m"
}
switch [is_connected $::m.apply-offsets sig] {
not_connected {}
is_input { return \
"$::MV(prog):$::m.apply-offsets must not be connected <$sig>"
}
default {return "is_connected:$::m.apply-offsets unexpected"}
}
switch [is_connected $::m.move-enable sig] {
not_connected {puts \
"$::MV(prog):$::m.move-enable not connected, Providing controls"
set ::MV(control_move_enable) 1
foreach name {apply-offsets backtrack-enable} {
if {[is_connected $::m.$name] != "not_connected"} {
return "Error: $::M.$name is already connected"
}
}
foreach aname $::MV(axes) {
set jnum $::MV($aname,jnum)
set pname $::m.offset-in-${jnum}
if {[is_connected $pname] != "not_connected"} {
return "Error: $pname is already connected"
}
}
}
is_input {
set ::MV(control_move_enable) 0
set msg "$::m.move-enable already connected <$sig>, no controls"
if $::MV(opt,no_display) {
set ::MV(no_display) 1
set msg "${msg}, no_display"
}
puts "$::MV(prog): $msg"
}
default {return "is_connected:move-enable unexpected"}
}
if { $::MV(opt,resume_withdelay) \
&& ([is_connected halui.program.resume] != "not_connected") } {
return "halui.program.resume is connected cannot use -autoresume <$sig>"
}
return ""
} ;# verify_context
proc get_parms {} {
# return "" or errtxt
while {[llength $::argv] >0} {
# beware wish handling of reserved cmdline arguments
# to use -h: use -- -h,
# lreplace shifts argv for no. of items for each iteration
set opt [lindex $::argv 0]
switch -- $opt {
-h - -? -
--help {usage}
-noentry {set ::MV(opt,noentry) 1
set ::MV(show,entry) 0
set ::MV(show,increments) 1
set ::argv [lreplace $::argv 0 0]
}
-axes {set ::MV(parm,axes) [lindex $::argv 1]
set ::argv [lreplace $::argv 0 1]
}
-inc {lappend incrlist [lindex $::argv 1]
set ::argv [lreplace $::argv 0 1]
}
-size {set ::MV(font) [list $::MV(font,family) \
[lindex $::argv 1] $::MV(font,weight)]
set ::argv [lreplace $::argv 0 1]
}
-loc {set ::MV(location) [lindex $::argv 1]
set ::argv [lreplace $::argv 0 1]
}
-autoresume {set ::MV(opt,resume_withdelay) 1
set ::argv [lreplace $::argv 0 0]
}
-delay {set ::MV(resume,delay,secs) [lindex $::argv 1]
set ::argv [lreplace $::argv 0 1]
}
-mode {set ::MV(opt,mode) [lindex $::argv 1]
set ::argv [lreplace $::argv 0 1]
}
-no_resume_inhibit {set ::MV(opt,no_resume_inhibit) 1
set ::argv [lreplace $::argv 0 0]
}
-no_pause_requirement {set ::MV(opt,no_pause_requirement) 1
set ::argv [lreplace $::argv 0 0]
}
-no_cancel_autoresume {set ::MV(opt,no_cancel_autoresume) 1
set ::argv [lreplace $::argv 0 0]
}
-no_display {set ::MV(opt,no_display) 1
set ::argv [lreplace $::argv 0 0]
}
-debug {set ::MV(opt,debug) 1
set ::argv [lreplace $::argv 0 0]
}
default {usage "Unknown option <$opt>"}
}
}
set debug_get_parms 0
if {$debug_get_parms} {
if [info exists incrlist] {puts "incrlist=$incrlist"}
puts " axes=$::MV(parm,axes)"
puts " font=$::MV(font)"
puts " loc=$::MV(location)"
puts " resume,delay,secs=$::MV(resume,delay,secs)"
puts "opt,resume_withdelay=$::MV(opt,resume_withdelay)"
puts " opt,noentry=$::MV(opt,noentry)"
puts " opt,debug=$::MV(opt,debug)"
puts " opt,mode=$::MV(opt,mode)"
}
if [info exists ::MV(font)] { option add *Font $::MV(font) }
if [info exists ::MV(parm,axes)] {
set ::MV(parm,axes) [string tolower $::MV(parm,axes)]
set plist [split $::MV(parm,axes) ""] ;# xQyz-->{x Q y z}
foreach letter $plist {
if {[string first $letter xyzabcuvw] < 0} {
return "unknown axis letter <$letter>"
}
}
# make a list in usual order: eg from xyz to {x y z}
foreach letter {x y z a b c u v w} {
if {[string first $letter $::MV(parm,axes)] >= 0} {
lappend ::MV(axes) $letter
}
}
}
if {[llength $::MV(axes)] > 9} {
# size limit of the component
return "too many axes specified, limit is 9"
}
if [info exists incrlist] {
set ::MV(increments) [lsort -real -increasing $incrlist]
}
if {[llength $::MV(increments)] > 4} {
return "too many increments, limit is 4"
}
switch $::MV(opt,mode) {
onpause {}
always { if $::MV(opt,resume_withdelay) {
puts "$::MV(prog):Incompatible -mode always and -autoresume"
puts "$::MV(prog):Disabling -autoresume"
#return "Incompatible -mode always and -autoresume"
}
set ::MV(opt,resume_withdelay) 0 ;# force for mode -always
set ::MV(opt,no_resume_inhibit) 1 ;# force for mode -always
}
default {return "Unknown mode <$::MV(opt,mode)>"}
}
return "" ;# ok
} ;# get_parms
proc set_restrictions_on_widgets {} {
foreach aname $::MV(axes) {
set ans [is_connected $::m.offset-in-$::MV($aname,jnum) sig]
if {$ans == "is_input"} {
puts "$::MV(prog):$aname input is already connected <$sig>"
set ::MV(show,entry) 0
set ::MV(show,increments) 0
}
}
} ;# set_restrictions_on_widgets
proc cross_reference {} {
# return "" or errtxt
foreach aname $::MV(axes) {
set jnum [joint_number_for_axis $aname]
set ::MV($aname,jnum) $jnum
set ::MV($jnum,aname) $aname ;# cross-ref
if [catch {hal getp $::m.offset-current-${jnum}} msg ] {
return "axis:$aname index=$jnum $msg"
}
}
return "" ;# ok
} ;# cross_reference
proc error_popup {msg} { \
set answer [tk_messageBox \
-parent . \
-icon error \
-type ok \
-title "$::MV(prog) Error" \
-message "$msg" \
]
puts "$msg"
} ;# popup
#-----------------------------------------------------------------------
proc usage { {errtxt ""} } {
foreach item {resume_withdelay \
noentry \
no_resume_inhibit \
no_pause_requirement \
no_cancel_autoresume \
no_display \
} {
if $::MV(opt,$item) {
set default_$item inuse
} else {
set default_$item notused
}
}
puts stdout \
"
Usage:
$::MV(prog) \[Options\]
Options:
\[--help | -? | -- -h \] (This text)
\[-mode \[onpause | always\]\] (default: $::MV(opt,mode))
(onpause: show gui when program paused)
(always: show gui always)
\[-axes axisnames\] (default: $::MV(parm,axes) (no spaces))
(letters from set of: x y z a b c u v w)
(example: -axes z)
(example: -axes xz)
(example: -axes xyz)
\[-inc incrementvalue\] (default: $::MV(increments) )
(specify one per -inc (up to 4) )
(example: -inc 0.001 -inc 0.01 -inc 0.1 )
\[-size integer\] (default: $::MV(font,size)
(Overall gui popup size is based on font size)
\[-loc center|+x+y\] (default: $::MV(location))
(example: -loc +10+200)
\[-autoresume\] (default: $default_resume_withdelay)
(resume program when move-enable deasserted)
\[-delay delay_secs\] (default: $::MV(resume,delay,secs) (resume delay))
Options for special cases:
\[-noentry\] (default: $default_noentry)
(don\'t create entry widgets)
\[-no_resume_inhibit\] (default: $default_no_resume_inhibit)
(do not use a resume-inhibit-pin)
\[-no_pause_requirement\] (default: $default_no_pause_requirement)
(no check for halui.program.is-paused)
\[-no_cancel_autoresume\] (default: $default_no_cancel_autoresume)
(useful for retracting offsets with simple)
(external controls)
\[-no_display\] (default: $default_no_display)
(Use when both external controls and external)
(displays are in use)
Note: If the moveoff move-enable pin ($::m.move-enable) is connected when
$::MV(prog) is started, external controls are required and only
displays are provided.
"
#"vim
if $::MV(opt,debug) {parray ::MV}
if {"$errtxt" != ""} {
puts "$::MV(prog):$errtxt"
exit 1
}
exit 0
} ;# usage
#-----------------------------------------------------------------------
# begin
if ![info exists ::MV(top)] {
package require Tk
wm withdraw .
package require Hal
set ::MV(prog) [file tail $::argv0]
set_defaults
set errtxt [get_parms]
if {"$errtxt" != ""} {
error_popup "get_parms: $errtxt"
usage "$errtxt"
}
set errtxt [cross_reference]
if {"$errtxt" != ""} {
error_popup "cross_reference: $errtxt"
usage "$errtxt"
}
set errtxt [verify_context]
if {"$errtxt" != ""} {
error_popup "verify_context:\n$errtxt"
if $::MV(opt,debug) {parray ::MV}
exit 1
} else {
puts "$::MV(prog):verify_context: ok"
}
set_restrictions_on_widgets ;# conditionally disable some controls
# connect power-on (to existing signal if necessary)
set psigname [connect_pin_to_sig motion.motion-enabled mvoff_gui:power_on]
connect_pin_to_sig $::m.power-on $psigname
set titletxt "$::MV(prog) $::MV(opt,mode)"
if {$::MV(control_move_enable)} {
set titletxt "$titletxt local"
} else {
set titletxt "$titletxt external"
}
switch $::MV(opt,mode) {
always { hal setp $::m.apply-offsets 1 }
onpause { set signame [connect_pin_to_sig halui.program.is-paused \
mvoff_gui:apply_offsets]
connect_pin_to_sig $::m.apply-offsets $signame
if {$::MV(opt,resume_withdelay)} {
set titletxt "$titletxt autoresume:on"
} else {
set titletxt "$titletxt autoresume:off"
}
}
default {puts "$::MV(prog):Unexpected mode: $::MV(opt,mode)"}
}
set ::MV(top) [toplevel .t]
wm title $::MV(top) "$titletxt"
wm protocol $::MV(top) WM_DELETE_WINDOW bye
make_gui
do_poll
if $::MV(opt,debug) {parray ::MV}
}

123
scripts/platform-is-supported Executable file
View File

@@ -0,0 +1,123 @@
#!/usr/bin/env python3
#
# This script returns 0 when run on a platform supported by the current
# branch of LinuxCNC, and 1 when run on an unsupported platform. It is
# intended to guide build automation on whether or not to try to build.
#
# Copyright: 2014, 2016
# Author: Sebastian Kuzminsky <seb@highlab.com>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
import os
import sys
import subprocess
import re
if sys.version_info.minor < 9:
raise SystemExit("System Python is too old")
supported_oses = [ 'linux' ]
supported_cpus = [ 'amd64', 'i386', 'arm' ]
supported_kernel_flavors = [ 'rtai', 'rtpreempt', 'vanilla' ]
def detect_os():
return os.uname()[0].lower()
def detect_kernel_flavor(uname):
try:
f = open("/boot/config-%s" % uname)
except IOError:
print("no kernel configuration found for %s, assuming vanilla" % uname)
return 'vanilla'
l = f.read(-1)
f.close()
config_ipipe = re.search('^CONFIG_IPIPE', l, re.MULTILINE)
config_xeno = re.search('^CONFIG_XENO_', l, re.MULTILINE)
config_rtpreempt = re.search('^CONFIG_PREEMPT_RT', l, re.MULTILINE)
if config_ipipe and not config_xeno and not config_rtpreempt:
return 'rtai'
elif config_ipipe and config_xeno and not config_rtpreempt:
return 'xenomai'
elif not config_ipipe and not config_xeno and config_rtpreempt:
return 'rtpreempt'
else:
return 'vanilla'
os = detect_os()
if os == 'linux':
cpu = subprocess.check_output(['dpkg-architecture', '-qDEB_HOST_ARCH_CPU']).strip().decode()
distributor = subprocess.check_output(['lsb_release', '--id', '--short']).strip().decode()
release = subprocess.check_output(['lsb_release', '--release', '--short']).strip().decode()
try:
major, minor = re.split('\.', release)
except ValueError as e:
major = release
minor = "0"
try:
release_major = int(major)
release_minor = int(minor)
except ValueError as e:
# Debian Unstable and Testing do not have a release number, the value is 'n/a'
release_major = 666
release_minor = 0
uname = subprocess.check_output(['uname', '-r']).strip().decode()
kernel_flavor = detect_kernel_flavor(uname)
else:
print("unknown OS:", os)
sys.exit(1)
print("os =", os)
print("cpu =", cpu)
print("distributor =", distributor)
print("release =", release)
print(" major =", release_major)
print(" minor =", release_minor)
print("uname = %s (%s)" % (uname, kernel_flavor))
if os not in supported_oses:
print("unsupported OS!")
sys.exit(1)
if cpu not in supported_cpus:
print("unsupported CPU!")
sys.exit(1)
if distributor == 'Ubuntu':
if release_major < 22:
print("release is too old!")
sys.exit(1)
if distributor == 'Debian':
if release_major < 12:
print("release is too old!")
sys.exit(1)
if kernel_flavor not in supported_kernel_flavors:
print("unsupported kernel flavor")
sys.exit(1)
print("this platform is supported!")
sys.exit(0)

129
scripts/pyvcp_demo Executable file
View File

@@ -0,0 +1,129 @@
#!/bin/bash
# This file is used for scripts that demonstrate pyvcp
# The scripts typically use an extension of .demo
# 1) start realtime
# 2) run pyvcp with supplied xml and hal files
# 3) stop realtime
# Copyright: 2014
# Author: Dewey Garrett <dgarrett@panix.com>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
prog=$(basename "$0")
function usage () {
cat <<EOF
Usage:
$prog filename1.xml filename2.hal [compname]
If not provided, use compname == pyvcp
EOF
exit 1
} ;# usage
function popup () {
msg="$*"
wish <<EOF &
wm withdraw .
tk_messageBox \
-title "$prog" \
-message "$msg" \
-icon error \
-type ok
destroy .
EOF
} ;# popup
# begin----------------------------------------------------
REALTIME=$(linuxcnc_var REALTIME)
$REALTIME status >/dev/null
status=$?
if [ "$status" = 0 ] ; then
msg="$prog: Realtime is already active"
echo "$msg"
popup "$msg"
exit 1
else
$REALTIME start
fi
case $# in
0|1) usage;;
2) XMLFILE=${1}
HALFILE=${2}
COMPNAME=pyvcp
;;
3) XMLFILE=${1}
HALFILE=${2}
COMPNAME=${3}
;;
*) usage;;
esac
if [ -n "$debug" ] ; then
echo "debug=$debug"
echo "REALTIME=$REALTIME"
echo "XMLFILE=$XMLFILE"
echo "HALFILE=$HALFILE"
echo "COMPNAME=$COMPNAME"
fi
# to allow relative includes
cd "$(dirname "$XMLFILE")" || { echo "E: Could not change directory to '$(dirname "$XMLFILE")'"; exit 1; }
pyvcp -c "$COMPNAME" "$XMLFILE" &
pyvcpjob=$!
ct=0
# wait for pins to be created before creating signals
while true ; do
halcmd show|grep "${COMPNAME}.*ready" >/dev/null 2>&1
status=$?
sleep 1
if [ "$status" = 0 ] ; then
#echo "ready after ct=$ct"
break
fi
if [ $ct -ge 10 ] ; then
echo "$0: hal-pyvcp startup failed"
exit 1
fi
ct=$((ct + 1))
done
# so HALFILE can source relative files
cd "$(dirname "$HALFILE")" || { echo "E: Could not change directory to '$(dirname "$HALFILE")'"; exit 1; }
halmsg=/tmp/pyvcp_demo_halcmd.err
halcmd -f "$HALFILE" >| "$halmsg" 2>&1
halstatus=$?
if [ "$halstatus" != 0 ] ; then
IFS=$'\n' # split lines
newmsg=
# put extra blank line between lines of output
while read -r line; do
newmsg="$newmsg
$line
"
done < "$halmsg"
popup "$newmsg"
kill $pyvcpjob
halrun -U
exit 1
fi
wait $pyvcpjob
halrun -U
exit 0

317
scripts/realtime.in Normal file
View File

@@ -0,0 +1,317 @@
#!/bin/bash
### BEGIN INIT INFO
# Provides: realtime
# Required-Start: $remote_fs $syslog
# Required-Stop: $remote_fs $syslog
# Default-Start:
# Default-Stop: 0 1 6
# Short-Description: enable realtime capabilities when needed
# Description: The rtai module is loaded when needed and
# removed when the job has ended and is used throughout the LinuxCNC
# infrastructure. The script has no effect for the PREEMPT realtime kernel.
# The script is not meant to be started a boot time - it could be, though.
# X-Interactive: false
### END INIT INFO
#
# @configure_input@
# on @DATE@
#
export LANG=C
GREP=@GREP@
PS=@PS@
CheckKernel() {
# Shellcheck doesn't know about substitution
# shellcheck disable=SC2194
case "@KERNEL_VERS@" in
"")
;;
*)
if [ "$(uname -r)" != "@KERNEL_VERS@" ]; then
cat 1>&2 << EOF
RTAPI requires the real-time kernel @KERNEL_VERS@ to run. Before running
this realtime application, reboot and choose this kernel at the boot menu.
EOF
exit 1
fi
;;
esac
}
RUN_IN_PLACE=@RUN_IN_PLACE@
if [ $RUN_IN_PLACE = yes ]; then
# When using RIP make sure PATH includes the directory where rtapi_app
# resides
PATH=@EMC2_HOME@/bin:$PATH; export PATH
fi
CheckConfig(){
# Shellcheck doesn't know about substitution
# shellcheck disable=SC2034
prefix=@prefix@
# shellcheck disable=SC2034
exec_prefix=@exec_prefix@
sysconfdir=@sysconfdir@
if [ "$RUN_IN_PLACE" = "yes" ]; then
RTAPICONF=
# check in the LinuxCNC scripts directory
# $0 is the command to run this script
# strip the script name to leave the path
SCRIPT_DIR=${0%/*}
# the path might be relative
# convert it to an absolute path
SCRIPT_DIR=$(realpath "$SCRIPT_DIR")
# now look for rtapi.conf there
if [ -f "$SCRIPT_DIR/rtapi.conf" ] ; then
RTAPICONF=$SCRIPT_DIR/rtapi.conf
fi
else
if [ -f "$sysconfdir/linuxcnc/rtapi.conf" ]; then
RTAPICONF=$sysconfdir/linuxcnc/rtapi.conf
fi
fi
if [ -z "$RTAPICONF" ] ; then
echo "Missing rtapi.conf. Check your installation." 1>&2
exit 1
fi
INSMOD="@INSMOD@"
RMMOD="@RMMOD@"
LSMOD="@LSMOD@"
FUSER="@FUSER@"
# Import the config
source "$RTAPICONF"
if [ ! -e "$RTLIB_DIR/.runinfo" ] && [ -e "$RTLIB_DIR/emc2/.runinfo" ]; then
# installed system: we don't want our modules mixed up with rtai
RTLIB_DIR=$RTLIB_DIR/emc2
fi
# Export the module path specified in the config.
export MODPATH
# Generate module lists for loading and unloading
# lists contain RTOS modules plus RTAPI and HAL
# unload list is in reverse order
# any module names that are symlinks are resolved to their real names
MODULES_LOAD=
MODULES_UNLOAD=
case $RTPREFIX in
uspace)
SHM_DEV=/dev/zero
;;
esac
for MOD in $MODULES ; do
eval MOD="\${MODPATH_${MOD}}"
if [ -z "$MOD" ]; then continue; fi
if [ -L "$MOD" ]; then
MOD=${MOD%/*}/$(readlink "$MOD")
fi
MODULES_LOAD="$MODULES_LOAD $MOD"
MOD="${MOD##*/}"
MOD="${MOD%.*}"
MODULES_UNLOAD="$MOD $MODULES_UNLOAD"
done
case $RTPREFIX in
*rtai*)
MODULES_LOAD="$MODULES_LOAD $RTLIB_DIR/rtapi$MODULE_EXT $RTLIB_DIR/hal_lib$MODULE_EXT"
MODULES_UNLOAD="hal_lib rtapi $MODULES_UNLOAD"
SHM_DEV=/dev/rtai_shm
;;
*)
MODULES_UNLOAD=
;;
esac
}
CheckStatus(){
case $RTPREFIX in
uspace)
if rtapi_app ping; then
echo rtapi_app is running
return 0
elif [ -n "$($PS -o stat= -o comm= -C rtapi_app | $GREP -v '^Z')" ]; then
echo rtapi_app is running but not reachable
return 1
else
echo rtapi_app is not running
return 1
fi
;;
*)
# check loaded/unloaded status of modules
unset NOTLOADED
for MOD in $MODULES_UNLOAD ; do
if "$LSMOD" | awk '{print $1}' | $GREP -x "$MOD" >/dev/null ; then
echo "$MOD is loaded"
else
echo "$MOD is not loaded"
NOTLOADED=NOT
fi
done
if [ -z "$NOTLOADED" ]; then
return 0
else
return 1
fi
;;
esac
}
CheckMem(){
# check for user space processes using shared memory
if [ -e /dev/mbuff ] ; then
# device file exists, check for processes using it
if $FUSER -s /dev/mbuff 2>/dev/null; then
# at least one process is using it
echo "ERROR: Can't remove RTLinux modules, kill the following process(es) first"
$FUSER -v /dev/mbuff
exit 1
fi
elif [ -e /dev/rtai_shm ] ; then
# device file exists, check for processes using it
if $FUSER -s /dev/rtai_shm 2>/dev/null; then
# at least one process is using it
echo "ERROR: Can't remove RTAI modules, kill the following process(es) first"
$FUSER -v /dev/rtai_shm
exit 1
fi
fi
}
Load(){
CheckKernel
case $RTPREFIX in
uspace)
rtapi_app start
;;
*rtai*)
for MOD in $MODULES_LOAD ; do
if ! [ -d "/sys/module/$(basename "$MOD" .ko)" ]; then
$INSMOD "$MOD" || return $?
fi
done
if [ "$DEBUG" != "" ] && [ -w /proc/rtapi/debug ] ; then
echo "$DEBUG" > /proc/rtapi/debug
fi
;;
esac
}
CheckLoaded(){
# this abomination is needed because udev sometimes doesn't
# have the device ready for us in time.
n=0
while [ $n -lt 100 ]; do
[ -w "$SHM_DEV" ] && return 0
echo "." 1>&2
sleep .1
n=$((n + 1))
done
echo "Can't write to $SHM_DEV - aborting" 1>&2
exit 1
}
Unload(){
CheckKernel
case $RTPREFIX in
uspace)
rtapi_app exit
# wait 5 seconds for rtapi_app to die and be reaped by its parent
START=$SECONDS
local NPROCS
while [ 5 -gt $((SECONDS-START)) ]; do
NPROCS=$(ps -C rtapi_app -o stat= -o comm= | $GREP -v '^Z' | wc -l)
if [ "$NPROCS" -eq 0 ]; then
break
fi
sleep 0.1
done
NPROCS=$(ps -C rtapi_app -o stat= -o comm= | $GREP -v '^Z' | wc -l)
if [ "$NPROCS" -gt 0 ]; then
echo "ERROR: rtapi_app failed to die" 1>&2
fi
ipcrm -M 0x48414c32 2>/dev/null ;# HAL_KEY
ipcrm -M 0x90280A48 2>/dev/null ;# RTAPI_KEY
ipcrm -M 0x48484c34 2>/dev/null ;# UUID_KEY
;;
esac
for module in $MODULES_UNLOAD ; do
$RMMOD "$module"
done
}
Verify(){
HAS_REALTIME=false
case $RTPREFIX in
uspace)
rtapi_app check_rt && HAS_REALTIME=true
;;
*rtai*)
echo "RTAI"
HAS_REALTIME=true
;;
esac
if [ $HAS_REALTIME = true ]; then
return 0
else
return 1
fi
}
CheckUnloaded(){
# checks to see if all modules were unloaded
STATUS=
for module in $MODULES_UNLOAD ; do
# check to see if the module is installed
if "$LSMOD" | awk '{print $1}' | $GREP -x "$module" >/dev/null ; then
echo "ERROR: Could not unload '$module'"
STATUS=error
fi
done
if [ -n "$STATUS" ] ; then
exit 1
fi
}
CMD=$1
case "$CMD" in
start|load)
CheckConfig
Load || exit $?
CheckLoaded
;;
restart|force-reload)
CheckConfig
CheckMem
Unload
CheckUnloaded
Load || exit $?
CheckLoaded
;;
stop|unload)
CheckConfig
CheckMem
Unload || exit $?
;;
status)
CheckConfig
CheckStatus || exit $?
;;
verify)
CheckConfig
Verify || exit $?
;;
*)
echo "Usage: $0 {start|load|stop|unload|restart|force-reload|status|verify}" >&2
exit 1
;;
esac
exit 0

139
scripts/rip-environment.in Normal file
View File

@@ -0,0 +1,139 @@
#!/bin/bash
# Execute this file in the context of your shell, such as with
# . @EMC2_HOME@/scripts/rip-environment
# and your shell environment will be properly configured to run commands like
# halcompile, halcmd, halrun, iosh, and python with the emc modules available.
#
# Alternately, use it as a wrapper to invoke a linuxcnc command that requires
# the environment to be set:
# @EMC2_HOME@/scripts/rip-environment latency-test
#
# Copyright 2006, 2007, 2008, 2009, 2014 Jeff Epler <jepler@unpythonic.net>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
case "$0" in
rip-environment|*/rip-environment)
as_command=true
if [ $# -eq 0 ]; then
cat <<-EOF
This script can be used in one of two ways.
It can be loaded in the context of your shell, by executing
. $0
after this, commands like 'linuxcnc', 'halrun' and so on refer to the
version in this directory, instead of to an installed version of
linuxcnc (if any)
Second, it can be used to run a command in this directory without
modifying the environment of the calling shell:
$0 command...
such as
$0 halrun foo.hal
$0 linuxcnc configs/.../foo.ini
EOF
exit 1
fi ;;
*) as_command=false
esac
# This evaluates to [ "yes" = "yes" ], which is fine.
# shellcheck disable=SC2050
if [ "yes" != "@RUN_IN_PLACE@" ]; then
echo "This script is only useful on run-in-place systems."
return
fi
case "$PATH" in
@EMC2_BIN_DIR@:*|*:@EMC2_BIN_DIR@:*)
if ! $as_command; then
echo "This script only needs to be run once per shell session."
return
fi ;;
esac
LINUXCNC_RIP_FLAG="True"; export LINUXCNC_RIP_FLAG
EMC2_HOME=@EMC2_HOME@; export EMC2_HOME
EMC2VERSION="@EMC2VERSION@"; export EMC2VERSION
LINUXCNCVERSION="@EMC2VERSION@"; export LINUXCNCVERSION
LINUXCNC_NCFILES_DIR="@EMC2_NCFILES_DIR@"; export LINUXCNC_NCFILES_DIR
LINUXCNC_EMCSH=@WISH@; export LINUXCNC_EMCSH
PATH=@EMC2_BIN_DIR@:$EMC2_HOME/scripts:$EMC2_HOME/tcl:"$PATH"
GLADE_ICON_THEME_PATH=$EMC2_HOME/share/glade/pixmaps:"$GLADE_ICON_THEME_PATH"; export GLADE_ICON_THEME_PATH
if [ -z "$TCLLIBPATH" ]; then
TCLLIBPATH=$EMC2_HOME/tcl
else
TCLLIBPATH="$EMC2_HOME/tcl $TCLLIBPATH"
fi
if [ -z "$LD_LIBRARY_PATH" ]; then
LD_LIBRARY_PATH=$EMC2_HOME/lib
else
LD_LIBRARY_PATH=$EMC2_HOME/lib:"$LD_LIBRARY_PATH"
fi
if [ -z "$PYTHONPATH" ]; then
PYTHONPATH=$EMC2_HOME/lib/python
else
PYTHONPATH=$EMC2_HOME/lib/python:"$PYTHONPATH"
fi
if [ -z "$MANPATH" ]; then
if type -path manpath > /dev/null 2>&1; then
MANPATH=$EMC2_HOME/docs/build/man:"$(manpath)"
else
MANPATH=$EMC2_HOME/docs/build/man:/usr/local/man:/usr/local/share/man:/usr/share/man
fi
else
MANPATH=$EMC2_HOME/docs/build/man:"$MANPATH"
fi
if [ -z "$GLADE_CATALOG_SEARCH_PATH" ]; then
GLADE_CATALOG_SEARCH_PATH=$EMC2_HOME/lib/python/gladevcp
else
GLADE_CATALOG_SEARCH_PATH=$EMC2_HOME/lib/python/gladevcp:"$GLADE_CATALOG_SEARCH_PATH"
fi
if ! $as_command; then
# TODO: if these completion directives give trouble for any versions
# of bash actively in use, protect them with checks of BASH_VERSINFO
if [ "${BASH_VERSINFO[0]}" -eq 2 ]; then
complete -o dirnames -f -X '!*.ini' emc axis mdi
complete -o dirnames -f -X '!*.hal' halrun halcmd
complete -W 'start stop restart status' realtime
complete -C "halcmd -C" halcmd
else
_iningc () {
case "$3" in
*.ini) mapfile -t COMPREPLY < <(compgen -o plusdirs -f -X '!*.ngc' -- "$2") ;;
*) mapfile -t COMPREPLY < <(compgen -o plusdirs -f -X '!*.ini' -- "$2")
esac
}
complete -o plusdirs -F _iningc emc axis
complete -o plusdirs -f -X '!*.ini' mdi
complete -o plusdirs -f -X '!*.hal' halrun halcmd
complete -W 'start stop restart status' realtime
complete -C "halcmd -C" halcmd
fi
if [ -f "$EMC2_HOME/src/Makefile" ]; then
build () { make -C "$EMC2_HOME/src" "$@"; }
fi
fi
export PYTHONPATH MANPATH LD_LIBRARY_PATH TCLLIBPATH GLADE_CATALOG_SEARCH_PATH
if $as_command; then exec "$@"; fi

54
scripts/rtapi.conf.in Normal file
View File

@@ -0,0 +1,54 @@
# DO NOT EDIT THIS FILE !
#
# @configure_input@
# on @DATE@
#
# A few parameters from emc2/Makefile.inc
EMC2_HOME=@EMC2_HOME@
KERNELDIR=@KERNELDIR@
RTLIB_DIR=@EMC2_RTLIB_DIR@
MODULE_EXT=@MODEXT@
RTPREFIX=@RTPREFIX@
KERNEL_VERS=@KERNEL_VERS@
# Sets the default DEBUG level
DEBUG='1'
# Path to the realtime kernel modules
MODPATH=@MODULE_DIR@
# List of realtime kernel modules to be loaded
if [ "@RTAI@" = "3" ] ; then \
MODULES="adeos rtai_hal rtai_ksched rtai_fifos rtai_sem rtai_math"
RTAI=@RTAI@
MODPATH_adeos=@MODPATH_adeos@
MODPATH_rtai_hal=@MODPATH_rtai_hal@
MODPATH_rtai_ksched=@MODPATH_rtai_ksched@
MODPATH_rtai_fifos=@MODPATH_rtai_fifos@
MODPATH_rtai_sem=@MODPATH_rtai_sem@
MODPATH_rtai_math=@MODPATH_rtai_math@
elif [ "@RTAI@" = "4" ] ; then \
MODULES="rtai_hal rtai_sched rtai_fifos rtai_sem rtai_math"
RTAI=@RTAI@
MODPATH_rtai_hal=@MODPATH_rtai_hal@
MODPATH_rtai_sched=@MODPATH_rtai_sched@
MODPATH_rtai_fifos=@MODPATH_rtai_fifos@
MODPATH_rtai_sem=@MODPATH_rtai_sem@
MODPATH_rtai_math=@MODPATH_rtai_math@
elif [ "@RTAI@" = "5" ] ; then \
MODULES="rtai_hal rtai_sched rtai_math"
RTAI=@RTAI@
MODPATH_rtai_hal=@MODPATH_rtai_hal@
MODPATH_rtai_sched=@MODPATH_rtai_sched@
MODPATH_rtai_math=@MODPATH_rtai_math@
else
case $(uname -r) in
*-rtai*)
MODULES="rtai_hal rtai_sched"
MODPATH_rtai_hal=/usr/realtime-$(uname -r)/modules/rtai_hal.ko
MODPATH_rtai_sched=/usr/realtime-$(uname -r)/modules/rtai_sched.ko
;;
*) MODULES=
esac
fi

155
scripts/run-auto-pid-tuner Executable file
View File

@@ -0,0 +1,155 @@
#!/usr/bin/env python3
#
# Copyright 2022 Petter Reinholdtsen
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301 USA.
"""
LinuxCNC automated PID auto-tuner
=================================
Automatically conduct a automatic PID tuning using the pid
LinuxCNC component.
This script first home the axis, then move it to the middle of its range.
Also extend the f-error value used by the PID controller. Next, disable
the PID controller (tune-mode=1), start the tuning process (tune-start=1)
and wait for it to finish before enabling the PID controller again.
This script assume the pid component is configured like this:
loadrt pid names=pid.x,pid.y,pid.z
It also assume the axis ranges are correct to be able to position the axes
in their middle positions.
"""
import time
import linuxcnc
import linuxcnc_util
import hal
axis = 'x'
axisid = 0
# Some values that need to be replaced during tuning
replace = {
# f"pid.{axis}.tune-cycles": "0x40", # Increase number of back and forth movement (default 50)
f"pid.{axis}.tune-effort": "0.2", # Set effort, should start low
f"ini.{axisid}.ferror": "100", # Extend to not trigger following error
f"ini.{axisid}.min_ferror": "100", # Extend to not trigger following error
}
def set_mode(mode):
s.poll()
if linuxcnc.MODE_MDI != s.task_mode:
c.mode(linuxcnc.MODE_MDI)
c.wait_complete()
s.poll()
def is_homed():
isHomed=True
for i,h in enumerate(s.homed):
if i >= s.joints: break
isHomed = isHomed and h
return isHomed
c = linuxcnc.command()
s = linuxcnc.stat();
e = linuxcnc.error_channel()
l = linuxcnc_util.LinuxCNC(command=c, status=s, error=e)
s.poll()
# Bootstrap hal library
hc = hal.component("hal-watcher")
error = []
# Make sure machine is turned on and out of estop
if linuxcnc.STATE_ESTOP == s.task_state or linuxcnc.STATE_OFF == s.task_state:
error.append("error: You need to start the machine and make sure ESTOP is not active")
effortpin = f"pid.{axis}.tune-effort"
try:
v = hal.get_value(effortpin)
except RuntimeError as e:
error.append(f"error: failed to find at_pin specific pin {effortpin} in HAL")
error.append(f"error: Did you remember to replace 'loadrt pid' with 'loadrt at_pid' for axis {axis}?")
if error:
print("\n".join(error))
exit(1)
# Start homing, wait for it to complete
set_mode(linuxcnc.MODE_MANUAL)
if not is_homed():
print("Requesting axis homing.")
c.teleop_enable(0)
c.wait_complete()
c.home(-1)
c.wait_complete()
l.wait_for_home([1, 1, 1, 0, 0, 0, 0, 0, 0])
else:
print("Already homed, skipping axis homing.")
# Locate mid range and move to the middle, switch to MDI mode while moving
midpos = (
s.joint[axisid]["max_position_limit"]
+ s.joint[axisid]["min_position_limit"]
) / 2
print(f"Moving {axis} to position {midpos}")
set_mode(linuxcnc.MODE_MDI)
c.mdi(f"G0 {axis}{midpos}")
c.wait_complete()
set_mode(linuxcnc.MODE_MANUAL)
print("info: Will start PID auto tuning in 5 seconds")
time.sleep(5)
# Replace some old values
oldvals = {}
for pin in replace.keys():
print(f"Keeping {pin}")
oldvals[pin] = hal.get_value(pin)
hal.set_p(pin, replace[pin])
# Run PID autotuning
print("Starting PID tuning")
hal.set_p(f"pid.{axis}.tune-mode", "1")
hal.set_p(f"pid.{axis}.tune-start", "1")
startpin = f"pid.{axis}.tune-start"
tunerunning = hal.get_value(startpin)
while hal.get_value(startpin) == tunerunning:
time.sleep(0.1)
hal.set_p(f"pid.{axis}.tune-mode", "0")
print(f"PID tuning of axis {axis} completed.")
# Give axis time to return to where it want to be before restoring old
# error limits. Reissuing G0 from above to be able to use
# wait_complete() to detect when it is done.
set_mode(linuxcnc.MODE_MDI)
c.mdi(f"G0 {axis}{midpos}")
c.wait_complete()
set_mode(linuxcnc.MODE_MANUAL)
# Reinsert old values
for pin in replace.keys():
hal.set_p(pin, "%s" % oldvals[pin])

394
scripts/runtests.in Executable file
View File

@@ -0,0 +1,394 @@
#!/bin/bash
# When running with "-v", the test itself runs in a pipeline with tee, and
# without pipefail we get the exit value from tee instead of from the test.
set -o pipefail
# The linuxcnc starter script sometimes tries to display X windows if
# DISPLAY is set. We never want that while running tests, so unset it.
unset DISPLAY
# Some of our tests emit locale-sensitive strings, so reset the locale
# to a sane default.
export LC_ALL=C
export LANGUAGES=
TOPDIR="$(realpath "$(dirname "$0")/..")"
# $prefix is used in the substitutions of @...@ strings below
# shellcheck disable=SC2034
prefix=@prefix@
# Shellcheck does not know about substitutions
# shellcheck disable=SC2050
if [ "@RUN_IN_PLACE@" = yes ]; then
. "$TOPDIR/scripts/rip-environment" >&/dev/null
export HEADERS=@EMC2_HOME@/include
export LIBDIR=${TOPDIR}/lib
export REALTIME=realtime
else
# Set $EMC2_HOME to $prefix for tests that depend on it
export SYSTEM_BUILD=1
export EMC2_HOME=@EMC2_HOME@
export HEADERS=@includedir@/linuxcnc
export LIBDIR=@EMC2_HOME@/lib
export LINUXCNC_EMCSH=@WISH@
export REALTIME=@REALTIME@
export SUDO=sudo
fi
export PYTHON_CPPFLAGS="@PYTHON_CPPFLAGS@"
export PYTHON_EXTRA_LIBS="@PYTHON_EXTRA_LIBS@"
export PYTHON_EXTRA_LDFLAGS="@PYTHON_EXTRA_LDFLAGS@"
export PYTHON_LIBS="@PYTHON_EXTRA_LIBS@"
RUNTESTS="$(readlink -f "$0")"
export RUNTESTS
NUM=0
FAIL=0; FAIL_NAMES=""
XFAIL=0
SHMERR=0
SKIP=0
VERBOSE=0
clean () {
find "$*" \( -name "stderr" -or -name "result" \
-or -name "*.var" -or -name "*.var.bak" \) \
-print0 | xargs -0 rm -f
}
wait_for_result_close() {
# Test for the 'result' and 'stderr' files in the current testdir to be
# closed. The 'checkresult' script cannot be run if these files are still
# in use and would cause a race condition.
# This function should be called with the test's directory as CWD.
presult="$(realpath -e -q "./result")"
pstderr="$(realpath -e -q "./stderr")"
if [ -z "$presult" ] || [ -z "$pstderr" ]; then
echo "Internal error: Missing 'result' or 'stderr' in wait_for_result_close()"
exit 2
fi
timeoutcnt=0
while true; do
lsof -- "$presult" > /dev/null 2>&1; resresult=$?
lsof -- "$pstderr" > /dev/null 2>&1; resstderr=$?
if [ $resresult -ne 0 ] && [ $resstderr -ne 0 ]; then
# Neither 'result' nor 'stderr' are open anymore
break
fi
if [ $timeoutcnt -ge 30 ]; then
if [ $resresult -eq 0 ]; then
echo "*** Timeout waiting for 'result' file to close"
fi
if [ $resstderr -eq 0 ]; then
echo "*** Timeout waiting for 'stderr' file to close"
fi
echo "*** Test results may be invalid when checked."
return 1
fi
sleep 1
timeoutcnt=$((timeoutcnt + 1))
done
return 0
}
run_shell_script () {
testname=$(basename "$1")
testdir=$(dirname "$1")
pushd "$testdir" > /dev/null || exit 2
if [ $VERBOSE -eq 1 ]; then
(bash -x "$testname" | tee result) 3>&1 1>&2 2>&3 | tee stderr
else
bash -x "$testname" > result 2> stderr
fi
exitcode=$?
wait_for_result_close
popd > /dev/null || exit 2
return "$exitcode"
}
run_executable () {
testname=$(basename "$1")
testdir=$(dirname "$1")
pushd "$testdir" > /dev/null || exit 2
if [ $VERBOSE -eq 1 ]; then
(./"$testname" | tee result) 3>&1 1>&2 2>&3 | tee stderr
else
./"$testname" > result 2> stderr
fi
exitcode=$?
wait_for_result_close
popd > /dev/null || exit 2
return "$exitcode"
}
run_without_overruns () {
testname=$(basename "$1")
testdir=$(dirname "$1")
for i in $(seq 10); do
if [ "$i" != 1 ]; then echo "--- $testdir: overrun detected in sampler, re-running test" 1>&2 ; fi
pushd "$testdir" > /dev/null || exit 2
if [ $VERBOSE -eq 1 ]; then
(halrun -f "$testname" | tee result) 3>&1 1>&2 2>&3 | tee stderr
else
halrun -f "$testname" > result 2> stderr
fi
exitcode=$?
wait_for_result_close
popd > /dev/null || exit 2
if ! grep -q '^overrun$' "$testdir/result"; then return "$exitcode"; fi
done
echo "--- $testdir: $i overruns detected, giving up" 1>&2
return 1
}
run_test() {
testname="$1"
case "$testname" in
*.hal) run_without_overruns "$testname" ;;
*.sh) run_shell_script "$testname" ;;
*) run_executable "$testname" ;;
esac
}
SHMEM_KEY=( "0x00000064" "0x48414c32" "0x48484c34" "0x90280a48" "0x130cf406" "0x434c522b" )
SHMEM_USE=( "Emc motion" "Hal" "UUID" "Rtapi" "Hal scope" "Classicladder" )
test_shmem() {
# Test if there are any shared memory segments left. These will
# interfere with performing tests as we cannot be assured of a clean
# start for each test if a segment exists.
mapfile -t keys < <(ipcs -m | grep -Ei "^\\s*($(IFS='|'; echo "${SHMEM_KEY[*]}"))" | awk '{print $1}')
if [ "${#keys[@]}" -gt 0 ]; then
echo "There are one or more shared memory segments currently allocated."
echo "This indicates that there is a LinuxCNC instance running or it"
echo "did not cleanup before exit."
echo "You should run 'ipcs -m' and look for the following keys to cleanup:"
for i in "${!SHMEM_KEY[@]}"; do
for j in "${keys[@]}"; do
if [ "$j" = "${SHMEM_KEY[$i]}" ]; then
echo "${SHMEM_KEY[$i]} - ${SHMEM_USE[$i]} key"
break
fi
done
done
echo
echo "You should remove the key(s) with 'ipcrm -M <key>' if LinuxCNC is"
echo "not running and no processes are attached to the segment(s)."
return 1
else
return 0
fi
}
test_and_remove_shmem() {
ret=0
for i in "${!SHMEM_KEY[@]}"; do
# ipcs returns the following columns:
# key shmid owner perms bytes nattch status
read -r -a SHM < <(ipcs -m | grep -Ei "^\\s*${SHMEM_KEY[$i]}")
if [ "${#SHM[@]}" -ge 6 ]; then
echo "*** SHMERR: Shared memory segment ${SHMEM_KEY[$i]} (${SHMEM_USE[$i]} key) was not removed. Removing..."
ipcrm -M "${SHMEM_KEY[$i]}"
# Check number of attached processes. It should be zero.
if [ "${SHM[5]}" -ne 0 ]; then
echo "*** SHMERR: Shared memory segment ${SHMEM_KEY[$i]} has at least one attached process."
echo "*** SHMERR: Manual intervention is required to solve the situation."
return 2
fi
ret=1
fi
done
return $ret
}
TMPDIR=$(mktemp -d /tmp/runtest.XXXXXX)
trap 'rm -rf "$TMPDIR"' 0 1 2 3 15
run_tests () {
if ! test_shmem; then
exit 1;
fi
find "$@" -name test.hal -or -name test.sh -or -name test \
| sort > "$TMPDIR/alltests"
while read -r testname; do
testdir=$(dirname "$testname")
# check if there's a "musthave" file with prerequisites from config.h
if [ -e "$testdir/musthave" ] ; then
# one prerequisite per line
while IFS= read -r prereq ; do
if ! grep --quiet --regexp "^#define HAVE_$prereq.*" "$TOPDIR/src/config.h" ; then
echo "Skipping test for missing prerequisite \"$prereq\": $testdir" 1>&2
SKIP=$((SKIP + 1))
continue 3
fi
done < "$testdir/musthave"
fi
# skip test if there's a "skip" file
if [ -e "$testdir/skip" ]; then
if ! [ -x "$testdir/skip" ] || ! "$testdir/skip"; then
echo "Skipping disabled test: $testdir" 1>&2
SKIP=$((SKIP + 1))
continue
fi
fi
if $NOSUDO && [ -e "$testdir/control" ] && \
grep Restrictions: "$testdir/control" | grep -q sudo; then
if ! [ -x "$testdir/skip" ] || ! "$testdir/skip"; then
echo "Skipping sudo test: $testdir" 1>&2
SKIP=$((SKIP + 1))
continue
fi
fi
NUM=$((NUM + 1))
TEST_DIR=$(readlink -f "$testdir")
export TEST_DIR
echo "Running test: $testdir" 1>&2
if test -n "$SYSTEM_BUILD"; then
# Tell `halcompile` where to install comps
USER_MODULE_DIR=$(readlink -f "$testdir") \
PATH=$(readlink -f "$testdir"):$PATH \
run_test "$testname"
else
run_test "$testname"
fi
exitcode=$?
if [ "$exitcode" -ne 0 ]; then
reason="test run exited with $exitcode"
else
if [ -e "$testdir/checkresult" ]; then
"$testdir/checkresult" "$testdir/result"
exitcode=$?
reason="checkresult exited with $exitcode"
elif [ -f "$testdir/expected" ]; then
cmp -s "$testdir/expected" "$testdir/result"
exitcode=$?
reason="result differed from expected"
if [ "$exitcode" -ne 0 ]; then
diff -u "$testdir/expected" "$testdir/result" > "$TMPDIR/diff"
SIZE=$(wc -l < "$TMPDIR/diff")
if [ "$SIZE" -lt 40 ]; then
cat "$TMPDIR/diff"
else
OMIT=$((SIZE-40))
head -40 "$TMPDIR/diff"
echo "($OMIT more lines omitted)"
fi
fi
else
exitcode=1
reason="Neither expected nor checkresult existed"
fi
fi
if [ "$exitcode" -ne 0 ]; then
echo "*** $testdir: XFAIL: $reason"
if test $PRINT = 1; then
echo "************** result:"
tail -500 "$testdir/result" | sed 's/^/ /'
echo "************** stderr:"
tail -500 "$testdir/stderr" | sed 's/^/ /'
echo "**************"
fi
if [ -f "$testdir/xfail" ]; then
XFAIL=$((XFAIL + 1))
if [ $NOCLEAN -eq 0 ]; then
rm -f "$testdir/stderr" "$testdir/result" \
"$testdir"/*.var "$testdir"/*.var.bak
fi
else
FAIL=$((FAIL + 1))
FAIL_NAMES="$FAIL_NAMES"$'\n'"$testdir"
fi
if test $STOP = 1; then
break
fi
else
if [ -f "$testdir/xfail" ]; then
echo "*** $testdir: XPASS: Passed, but was expected to fail"
else
if [ $NOCLEAN -eq 0 ]; then
rm -f "$testdir/stderr" "$testdir/result" \
"$testdir"/*.var "$testdir"/*.var.bak
fi
fi
fi
if ! test_and_remove_shmem; then
if [ $? -eq 2 ]; then
# Cannot remove attached segments. Fail hard.
exit 1
fi
SHMERR=$((SHMERR + 1))
fi
done < "$TMPDIR/alltests"
SUCC=$((NUM-FAIL-XFAIL))
echo "Runtest: $NUM tests run, $SUCC successful, $FAIL failed + $XFAIL expected, $SKIP skipped, $SHMERR shmem errors"
if [ "$FAIL" -ne 0 ]; then
echo "Failed: $FAIL_NAMES"
exit 1;
else
exit 0;
fi
}
usage () {
P=${0##*/}
cat <<EOF
$P: Run HAL test suite items
Usage:
$P [-n] [-s] [-p] tests
Run tests. With '-n', do not remove temporary files for successful
tests. With '-s', stop after any failed test. With '-p', print
stderr and result files.
$P -c tests
Remove temporary files from an earlier test run.
$P -u
Only run tests that require normal user access. Skip tests
requiring root or sudo.
$P -v
Show stdout and stderr (normally it's hidden).
EOF
}
CLEAN_ONLY=0
NOCLEAN=0
NOSUDO=false
STOP=0
PRINT=0
while getopts cnuvsph opt; do
case "$opt" in
c) CLEAN_ONLY=1 ;;
n) NOCLEAN=1 ;;
u) NOSUDO=true ;;
v) VERBOSE=1 ;;
s) STOP=1 ;;
p) PRINT=1 ;;
h|?) usage; exit 0 ;;
*) usage; exit 1 ;;
esac
done
shift $((OPTIND-1))
if [ $# -eq 0 ]; then
if [ -f test.hal ] || [ -f test.sh ]; then
set -- .
else
set -- "$TOPDIR/tests"
fi
fi
if [ $CLEAN_ONLY -eq 1 ]; then
clean "$@"
else
run_tests "$@"
fi

115
scripts/setup_designer.in Executable file
View File

@@ -0,0 +1,115 @@
#!/bin/bash
# install base packages for displaying qtvcp screens and sounds
echo -e '\ninstalling common base packages'
sudo apt-get install -y gstreamer1.0-tools espeak espeak-ng sound-theme-freedesktop
echo -e '\ninstalling base packages for python3'
sudo apt-get install -y python3-opengl python3-pyqt5 python3-pyqt5.qsci python3-pyqt5.qtsvg python3-pyqt5.qtopengl python3-opencv python3-dbus python3-dbus.mainloop.pyqt5 python3-espeak python3-pyqt5.qtwebkit python3-xlib python3-numpy python3-cairo python3-gi-cairo python3-poppler-qt5
echo -e '\ninstalling python3 tools'
sudo apt-get install -y pyqt5-dev-tools
# prompt to install designer
while true; do
# clear
echo -e '\nDo you wish to install Qt Designer for creating or modifying a GUI'
echo ' 1 = No, exit'
echo ' 2 = Yes, for a Run In Place installation'
echo ' 3 = Yes, for a package installation'
read -r -p $'\nSelection? ' response
case $response in
[1]* ) echo -e '\nInstallation complete, QtVCP screens are now available.\n'
exit;;
[2]* ) break;;
[3]* ) break;;
* ) echo -e '\nInstallation complete, QtVCP screens are now available.\n'
exit;;
esac
done
# install tools
#clear
echo -e '\ninstalling common tools'
sudo apt-get install -y qttools5-dev qttools5-dev-tools
echo -e '\ninstalling python3 tools'
sudo apt-get install -y libpython3-dev
# get the correct plugin file to copy
# for run in place LinuxCNC installation
if [ "$response" -eq 2 ]; then
if [ -f ~/linuxcnc-dev/lib/python/qtvcp/plugins/qtvcp_plugin.py ]; then
pifile=~/linuxcnc-dev/lib/python/qtvcp/plugins/qtvcp_plugin.py
else
while true; do
# clear
echo -e '\nrun in place installation not found'
echo 'Enter base directory name e.g. linuxcnc-2.9'
echo 'X to exit'
read -r -p $'\nDirectory? ' basedir
case $basedir in
[Xx]* ) clear
exit;;
* ) if [ -f ~/"$basedir"/lib/python/qtvcp/plugins/qtvcp_plugin.py ]; then
pifile=~/"$basedir"/lib/python/qtvcp/plugins/qtvcp_plugin.py
break
fi
;;
esac
done
fi
# for full LinuxCNC installation
elif [ "$response" -eq 3 ]; then
# check for valid plugin file
if [ -f /usr/lib/python3/dist-packages/qtvcp/plugins/qtvcp_plugin.py ]; then
pifile=/usr/lib/python3/dist-packages/qtvcp/plugins/qtvcp_plugin.py
else
# clear
echo -e '\n'"$pifile"' not found in /usr/lib/python3/dist-packages/qtvcp/plugins/'
echo -e '\ncannot continue designer installation\n'
exit
fi
fi
# create users plugin directory
mkdir -p ~/.designer/plugins/python
# link to plugins
if [ -f ~/.designer/plugins/python/qtvcp_plugin.py ]; then
sudo rm ~/.designer/plugins/python/qtvcp_plugin.py
fi
echo -e '\ncreate plugin link at:\n~/.designer/plugins/python/\n'
ln -s "$pifile" ~/.designer/plugins/python/
sopath=/usr/lib/x86_64-linux-gnu/qt5/plugins/designer/
p2sofile=libpyqt5_py2.so
p3sofile=libpyqt5.so
# ensure we have a python3 .so file
if [ ! -f $sopath/$p3sofile ]; then
# check if we have a python3 .so backup file from a python2 installation
if [ -f $sopath/$p3sofile.old ]; then
echo -e '\nrename libpyqt5.so.old file to libpyqt5.so'
sudo mv $sopath/$p3sofile.old $sopath/$p3sofile
# otherwise report an error
else
echo -e '\nlibpyqt5.so is missing from:'
echo -e '/usr/lib/x86_64-linux-gnu/qt5/plugins/designer/'
echo -e '\ndesigner installation aborted...\n'
exit
fi
fi
# remove python2 .so file if it exists
if [ -f $sopath/$p2sofile ]; then
echo -e '\nremove python2 .so file'
sudo rm $sopath/$p2sofile
fi
#clear
echo -e '\nInstallation complete, designer can be started with:'
echo -e '\ndesigner -qt=5\n'
if [ "$response" -eq 2 ]; then
echo -e 'After setting the rip-environment\n'
fi

60
scripts/shellcheck.sh Executable file
View File

@@ -0,0 +1,60 @@
#!/bin/bash -e
if ! command -v shellcheck > /dev/null; then
echo "E: Please install the program 'shellcheck' prior to executing this script."
exit 1
fi
# This script is supposed to be in the scripts subdirectory
TOPDIR=$(realpath --relative-to=. "$(dirname "$0")/..")
# Exclude tracking includes and don't complain about include paths
SHELLCHECK_OPTS="--exclude=SC1090,SC1091"
export SHELLCHECK_OPTS
# Check if files are specified on the command line. If so, only use those.
if [ $# -gt 0 ]; then
case "$1" in
-h|--help*)
cat <<EOF
Shellcheck script to check LinuxCNC shell scripts.
Usage: shellcheck.sh [-h|--help] [files...]
-h, --help This help message
Specify one or more files on the command line if you only want those files
to be checked. The script will search all of the LinuxCNC source tree for
shell scripts if no files are specified on the command line.
EOF
exit 1
;;
esac
FILES=( "$@" )
else
#
# With the file command we can find all scripts.
# Note: perform shellcheck with a distclean tree. Some scripts are
# generated by configure from .in files.
#
# Without the file command we just look for script file names.
#
if [ -n "$(command -v file)" ]; then
mapfile -t FILES < <(find "$TOPDIR" -type f \
-not -path "*/.git/*" \
-not -path "*/autom4te.cache/*" \
-not -name "*~" \
-not -path "*/tcl/*" \
-not -name "*.tcl" \
-not -name "configure" \
-not -name "config.guess" \
-not -name "config.status" \
-not -name "config.sub" \
-not -name "install-sh" \
-exec file {} + | awk -F: '/shell script/{print $1}')
else
mapfile -r FILES < <(find "$TOPDIR" -type f -name "*.sh")
fi
fi
if [ "${#FILES[@]}" -gt 0 ]; then
shellcheck "${FILES[@]}"
fi

581
scripts/sim_pin Executable file
View File

@@ -0,0 +1,581 @@
#!/usr/bin/wish
if [catch {package require Hal} msg] {
puts "\nProblem: $msg"
puts "Is linuxcnc installed?"
puts "If using Run-In-Place build, source scripts/rip-environment first"
exit 1
}
proc usage {} {
puts "
Usage:
$::SP(progname) \[Options\] name1 \[name2 ...\] &
Options:
--help (this text)
--title title_string (window title, default: $::SP(progname))
Note: LinuxCNC (or a standalone Hal application) must be running
A named item can specify a pin, param, or signal
The item must be writable, e.g.:
pin: IN or I/O (and not connected to a signal with a writer)
param: RW
signal: connected to a writable pin
Hal item types bit,s32,u32,float are supported
When a bit item is specified, a pushbutton is created
to manage the item in one of three manners specified
by radio buttons:
toggle: Toggle value when button pressed
pulse: Pulse item to 1 once when button pressed
hold: Set to 1 while button pressed
The bit pushbutton mode can be specified on the command
line by formatting the item name:
namei/mode=\[toggle | pulse | hold\]
If the bit item mode begins with an uppercase letter,
the radio buttons for selecting other modes are not shown
"
exit 1
} ;# usage
proc add_item_to_gui {id itemname} {
set l [split $itemname /]
set itemname [lindex $l 0]
set itemargs [lindex $l 1]
set ::SP($id,onemode) 0
if { [string first "mode=" "$itemargs"] == 0} {
set themode [lindex [split $itemargs =] 1]
set firstchar [string range "$themode" 0 0]
if {[string first "$firstchar" "PTH"] >= 0} {
set ::SP($id,onemode) 1
}
set ::SP($id,mode) [string tolower $themode]
} else {
set ::SP($id,mode) "default"
}
set ::SP($id,itemname) $itemname
if ![item_info $itemname $id] {
puts "$::SP(message)"
return 0
} else {
puts "$::SP(message)"
}
if { ![info exists ::SP(vframe)] \
|| ($::SP(vframe,ct) >= $::SP(vframe,vct)) } {
set ::SP(vframe,ct) 0
incr ::SP(vframe,column)
set ::SP(vframe) [frame .vf-$::SP(vframe,column)]
pack $::SP(vframe) -side left -fill both -expand 1
}
incr ::SP(vframe,ct)
set vf $::SP(vframe)
set f [frame ${vf}.f$id -borderwidth 3 -relief ridge]
pack [label $f.hdr -bg lightgray -fg blue \
-borderwidth 0 -relief raised \
-text "$::SP($id,itemname)"] \
-fill x -expand 1
switch $::SP($id,itemtype) {
bit {add_bit_item_to_gui $f $id}
s32 -
u32 {add_number_item_to_gui $f $id 1}
float {add_number_item_to_gui $f $id 0}
default {return -code error \
"add_item_to_gui: unexpected itemtype <$::SP($id,itemtype)>"
}
}
return 1
} ;# add_item_to_gui
proc add_bit_item_to_gui {f id} {
switch -nocase $::SP($id,mode) {
pulse -
hold -
toggle {}
default {
if {"$::SP($id,mode)" != "default"} {
puts "$::SP($id,itemname): unknown </mode=$::SP($id,mode)>,\
using /mode=$::SP(bit,mode,default)"
}
set ::SP($id,mode) $::SP(bit,mode,default)
}
}
set value [get_item $id]
set color lightgray
if $value {set color magenta}
pack [label $f.b \
-text "$::SP($::SP($id,mode),text)" \
-borderwidth 4 -relief raised ] \
-fill x -expand 1
set ::SP($id,button) $f.b
bind $::SP($id,button) <ButtonRelease-1> [list b_release $id]
bind $::SP($id,button) <ButtonPress-1> [list b_press $id]
set ::SP($id,ivalue) "$value"
pack [label $f.l -bg $color -fg black \
-text "$::SP(iprefix)$::SP($id,ivalue) $::SP(prefix)$value"] \
-fill x -expand 1
set ::SP($id,label) $f.l
if {!$::SP($id,onemode)} {
pack [radiobutton $f.p -text OnePulse \
-anchor w \
-value "pulse" \
-command [list bit_mode $id] \
-variable ::SP($id,mode)] \
-fill x -expand 0
pack [radiobutton $f.t -text ToggleValue \
-anchor w \
-value "toggle" \
-command [list bit_mode $id] \
-variable ::SP($id,mode)] \
-fill x -expand 0
pack [radiobutton $f.h -text "1 WhilePressed" \
-anchor w \
-value "hold" \
-command [list bit_mode $id] \
-variable ::SP($id,mode)] \
-fill x -expand 0
}
pack $f -side top -fill x -expand 0
} ;# add_bit_item_to_gui
proc add_number_item_to_gui {f id enable_plusminus} {
set value [get_item $id]
set color lightgray
pack [frame $f.one] -fill x -expand 1
pack [button $f.one.b -bg $color -fg black \
-text "Set " \
-relief raised -bd 3 \
-command [list b_press $id] ]\
-side left -fill x -expand 1
if $enable_plusminus {
pack [button $f.one.m -bg $color -fg black \
-text "-" \
-relief raised -bd 3 \
-command [list minus_number_item $id] ]\
-side left -fill x -expand 1
pack [button $f.one.p -bg $color -fg black \
-text "+" \
-relief raised -bd 3 \
-command [list plus_number_item $id] ]\
-side left -fill x -expand 1
}
pack [button $f.one.r -bg $color -fg black \
-text "Reset" \
-relief raised -bd 3 \
-command [list reset_number_item $id] ]\
-side left -fill x -expand 1
set e [entry $f.e \
-justify right \
-textvariable ::SP($id,entry)]
pack $e -fill x -expand 0
bind $e <Return> [list b_press $id]
set ::SP($id,ivalue) "$value"
pack [label $f.l -bg $color -fg black \
-anchor w \
-text "$::SP(iprefix)$::SP($id,ivalue) $::SP(prefix)$value"] \
-fill x -expand 1
set ::SP($id,label) $f.l
if {$::SP($id,itemtype) == "u32"} {
pack [label $f.hexl -bg $color -fg black \
-anchor w \
-text "$::SP(iprefix)[format %#X $::SP($id,ivalue)] \
$::SP(prefix)[format %#X $value]"] \
-fill x -expand 1
set ::SP($id,hexlabel) $f.hexl
}
pack $f -side top -fill x -expand 0
} ;# add_number_item_to_gui
proc exact_name {name line} {
set idx [string first $name $line]
if {$idx < 0} {return 0}
if {0 == [string compare $name [string range $line $idx end]]} {
return 1
}
return 0
} ;# exact_name
proc connected_to {name line} {
# check if an input pin is already connected to a signal
# since it does not necessarily have a writer
set idx [string first $name $line]
if {$idx < 0} {return ""}
# check if pin is an input
if {-1 != [string first "$name <==" [string range $line $idx end]]} {
set idx [string first "<==" $line]
set signame [string range $line [expr 4 + $idx] end]
return "$signame"
}
return ""
} ;# connected_to
proc item_info {itemname id} {
set fmt "sim_pin: %-30s %5s %3s %s"
set theitem "-----"
set dir "---"
set found 0
# try pin:
set answer [hal show pin "$itemname"]
set lines [split $answer \n]
set lines [lreplace $lines 0 1] ;# discard header lines
# look for exact match (hal show will present all matching leading part)
foreach line $lines {
if {"$line" == ""} continue
if [exact_name $itemname $line] {
set found 1
set theitem "PIN"
break
}
set signame [connected_to $itemname $line]
if {"" != "$signame"} {
puts "pin <$itemname> is already connected, trying signal:<$signame>"
set itemname $signame
set ::SP($id,itemname) $itemname
}
}
if !$found {
# try param:
set answer [hal show param "$itemname"]
set lines [split $answer \n]
set lines [lreplace $lines 0 1] ;# discard header lines
# look for exact match (hal show will present all matching leading part)
foreach line $lines {
if {"$line" == ""} continue
if [exact_name $itemname $line] {
set found 1
set theitem "PARAM"
break
}
}
}
if !$found {
# try signal:
set answer [hal show signal "$itemname"]
set lines [split $answer \n]
set lines [lreplace $lines 0 1] ;# discard header lines
# look for exact match (hal show will present all matching leading part)
foreach line $lines {
if {"$line" == ""} continue
if [exact_name $itemname $line] {
set found 1
scan $line "%s %s" sigtype other
switch $sigtype {
bit -
u32 -
s32 -
float {set theitem SIG}
default {
set ::SP(message) \
"Unknown type for signal item <$id $::SP($id,itemname) $sigtype>"
return 0
}
}
break
}
}
}
if !$found {
set ::SP(message) "Unknown item: $::SP($id,itemname)"
return 0
}
switch $theitem {
PIN -
PARAM {
scan $line "%d %s %s %s %s %s %s" owner type dir value name arrows signalname
if { ("$dir" == "IN") || ("$dir" == "I/O") || "$dir" == "RW"} {
if [info exists arrows] {
set ::SP(message) [format $fmt \
$itemname $theitem $dir "not writable (connected to signal)"]
return 0
} else {
#puts "OK <$dir> $line"
}
} else {
set ::SP(message) [format $fmt \
$itemname $theitem $dir "not writable"]
return 0
}
}
SIG {
set sig_header_ct 0
foreach line $lines {
if { ([string first "<==" $line] < 0) \
&& ([string first "==>" $line] < 0) \
} {
incr sig_header_ct
}
if {[string first "<==" $line] >= 0} {
set has_writer 1
}
}
if {$sig_header_ct > 4} {
# wild cards not supported:
set ::SP(message) "Unknown item: $::SP($id,itemname)"
return 0
}
if [info exists has_writer] {
set ::SP(message) [format $fmt \
$itemname $theitem $dir "signal has writer"]
return 0
} else {
set theitem "SIG"
set is_signal 1
}
}
}
if [info exists is_signal] {
set ::SP($id,itemtype) $sigtype
set ::SP($id,set_cmd) sets
set ::SP($id,get_cmd) gets
} else {
set ::SP($id,itemtype) [hal ptype $itemname]
set ::SP($id,set_cmd) setp
set ::SP($id,get_cmd) getp
}
set ::SP(message) [format $fmt $itemname $theitem $dir ""]
return 1 ;# ok
} ;# item_info
proc bit_mode {id} {
switch -nocase $::SP($id,mode) {
pulse {$::SP($id,button) config -text Pulse}
hold {$::SP($id,button) config -text "1 while pressed"}
toggle -
default {$::SP($id,button) config -text Toggle}
}
} ;# bit_mode
proc item_set {id {new_value 0}} {
if [catch {
switch $::SP($id,itemtype) {
bit {hal $::SP($id,set_cmd) $::SP($id,itemname) 1}
s32 -
u32 -
float {hal $::SP($id,set_cmd) $::SP($id,itemname) $new_value}
}
} msg ] {
popup $msg
return
}
item_show $id
} ;# item_set
proc item_unset {id} {
if [catch {hal $::SP($id,set_cmd) $::SP($id,itemname) 0} msg] {
popup $msg
}
set value [get_item $id]
set color lightgray
if $value {set color magenta}
$::SP($id,label) configure -bg $color -fg black \
-text "$::SP(iprefix)$::SP($id,ivalue) $::SP(prefix)$value"
} ;# item_unset
proc item_show {id} {
set value [get_item $id]
set color lightgray
if {$value != $::SP($id,ivalue)} {set color magenta}
switch $::SP($id,itemtype) {
bit {
$::SP($id,label) configure -bg $color \
-text "$::SP(iprefix)$::SP($id,ivalue) $::SP(prefix)$value"
}
s32 - \
u32 - \
float {$::SP($id,label) configure -bg $color -fg black \
-text "$::SP(iprefix)$::SP($id,ivalue) $::SP(prefix)$value"
}
}
if {$::SP($id,itemtype) == "u32"} {
$::SP($id,hexlabel) configure -bg $color -fg black \
-text "$::SP(iprefix)[format %#X $::SP($id,ivalue)] \
$::SP(prefix)[format %#X $value]"
}
} ;# item_show
proc b_press {id} {
set value [get_item $id]
switch $::SP($id,itemtype) {
bit {switch -nocase $::SP($id,mode) {
"hold" {item_set $id}
"toggle" { if $value {
item_unset $id
} else {
item_set $id
}
}
"pulse" {item_set $id; after $::SP(pulse,ms) [list b_release $id]}
}
}
s32 - \
u32 - \
float {
set e $::SP($id,entry)
if ![isnumber $e] {
if [catch {set e [expr $::SP($id,entry)]} msg] {
popup "Invalid Expression (<$e>)"
set ::SP($id,entry) ""
return
} else {
switch $e {
Inf - NaN {
popup "Bad expr result: <$e>"
set ::SP($id,entry) ""
return
}
}
}
}
# Note: halcmd rejects numbers formatted 'nEmm' for s32, u32
if { (($::SP($id,itemtype) == "s32") || ($::SP($id,itemtype) == "u32")) \
&& ![isinteger $e]} {
popup "Integer required for u32,s32 entry (not <$e>)"
return
}
if { ($::SP($id,itemtype) == "u32") \
&& [isnegative $e]} {
popup "Nonnegative Integer required for u32 entry (not <$e>)"
return
}
item_set $id $e
}
default {return -code error \
"b_press: unknown pin type <$::SP($id,itemtype)> for $::SP($id,itemname)"
}
}
} ;# b_press
proc b_release {id} {
switch -nocase $::SP($id,mode) {
"hold" {item_unset $id}
"toggle" {}
"pulse" {item_unset $id}
}
} ;# b_release
proc reset_number_item {id} {
item_set $id $::SP($id,ivalue)
} ;# reset_number_item
proc plus_number_item {id} {
item_set $id [expr 1 + [get_item $id]]
} ;# plus_number_item
proc minus_number_item {id} {
item_set $id [expr -1 + [get_item $id]]
} ;# minus_number_item
proc get_item {id} {
set value [hal $::SP($id,get_cmd) $::SP($id,itemname)]
switch $::SP($id,itemtype) {
bit {
switch $value {
FALSE {return 0}
TRUE {return 1}
}
}
s32 -
u32 -
float {return $value}
default {return -code error \
"get_item: unknown item type <$::SP($id,itemtype)> for $::SP($id,itemname)"
}
}
} ;# get_item
proc update_current_values {} {
for {set id 0} {$id < $::SP(id)} {incr id} {
item_show $id
}
after $::SP(update,ms) update_current_values
} ;# update_current_values
proc isinteger {v} {
if ![isnumber $v] {return 0}
if {[string first . $v] >=0} {return 0}
if {[string first e [string tolower $v]] >= 0} {return 0}
return 1
} ;# isinteger
proc isnumber {v} {
if [catch {format %f $v}] {
return 0
} else {
return 1
}
} ;# isnumber
proc isnegative {v} {
# Note:check with isnumber before this
if {[format %f $v] < 0} {return 1}
return 0
} ;# isnegative
proc popup msg {
tk_messageBox \
-type ok \
-title "$::SP(progname): Problem" \
-message $msg
} ;# popup
if [catch {
if {[info exists ::argv0] && [info script] == $::argv0} {
set ::SP(progname) [file tail $::argv0]
set ::SP(update,ms) 300
if {$::argv == ""} {usage}
set ::SP(bit,mode,default) toggle
# button text for bit item modes:
set ::SP(pulse,text) "Pulse"
set ::SP(hold,text) "1 while Pressed"
set ::SP(toggle,text) "Toggle"
set ::SP(id) 0
set ::SP(vframe,column) 0
set ::SP(vframe,vct) 4 ;# howmany items in a column
set ::SP(iprefix) "Initial=" ;# initial value prefix
set ::SP(prefix) "Current=" ;# current value prefix
set ::SP(pulse,ms) 200 ;# pulse duration
set ::SP(title) $::SP(progname)
set currentarg [lindex $::argv 0]
while {[string first "-" $currentarg] == 0} {
switch -- $currentarg {
--help {usage}
--title {set ::SP(title) [lindex $::argv 1]
set ::argv [lreplace $::argv 0 0]
}
}
set ::argv [lreplace $::argv 0 0]
set currentarg [lindex $::argv 0]
}
foreach itemname $::argv {
if [add_item_to_gui $::SP(id) $itemname] {
incr ::SP(id)
}
}
wm title . $::SP(title)
if {$::SP(id) < 1} usage
update_current_values
}
} msg] {
puts "\nError: $msg"
usage
}

51
scripts/simulate_probe Executable file
View File

@@ -0,0 +1,51 @@
#!/usr/bin/wish
# simulate a probe
# You must disconnect hal connections to motion.probe-input
package require Hal
set this [file tail $::argv0]
if [catch {hal setp motion.probe-input 0} msg] {
if {[string last connected $msg] >= 0} {
puts "\nThe pin motion.probe-input is currently connected to a signal"
puts "Remove the connection to use $this\n"
exit
} else {
puts "\nIs LinuxCNC running? <$msg>"
puts "Start LinuxCNC before starting $this\n"
}
exit
}
pack [label .l -width 20 -height 10 -bg lightgray -fg black \
-relief raised -bd 5 \
-text "Push to simulate\nProbe touch"] \
-fill both -expand 1
bind .l <ButtonPress-1> probetouch
bind .l <ButtonRelease-1> proberelease
set ::pulse 0
pack [checkbutton .c -text Pulse -variable ::pulse] \
-fill x -expand 0
proc probetouch {} {
.l configure -bg red -fg white -text ProbeON
if [catch {hal setp motion.probe-input 1} msg] {
puts stderr "Is LinuxCNC running? <$msg>"
return
}
if $::pulse {
after 100 proberelease
if [catch {hal setp motion.probe-input 0} msg] {
puts stderr "Is LinuxCNC running? <$msg>"
return
}
}
}
proc proberelease {} {
.l configure -bg lightgray -fg black -text ProbeOFF
if [catch {hal setp motion.probe-input 0} msg] {
puts stderr "Is LinuxCNC running? <$msg>"
}
}

100
scripts/swish Executable file
View File

@@ -0,0 +1,100 @@
#!/bin/bash
if ! type -path swish-e > /dev/null 2>&1; then
cat<<EOF
This program requires that the 'swish-e' full-text indexer be installed.
On Debian and Ubuntu systems, you may install it by running
sudo apt-get install swish-e
On Ubuntu systems, this requires that the "universe" repository be enabled.
EOF
exit 2
fi
case "$0" in
*/*) MYDIR="${0%/*}" ;;
*) MYDIR="$(type -path "$0")"; MYDIR="${MYDIR%/*}"
esac
SRCDIR=$(realpath "$MYDIR/../src")
[ -d "$SRCDIR" ] || { echo "E: Cannot find directory '$SRCDIR'."; exit 1; }
SWISHINDEX="$SRCDIR/.swishindex"
function makerelative () {
python -c '
import os, sys
here = os.getcwd()
parents = [here + "/"]
while 1:
next = os.path.dirname(here)
if next == here: break
parents.append(next + "/")
here = next
for file in sys.stdin.read().split("\0"):
for i in range(len(parents)):
p = parents[i]
if file.startswith(p):
file1 = os.path.join("../" * i, file[len(p):])
if len(file1) < len(file): file = file1
sys.stdout.write(file + "\0")
'
}
usage () {
b=${0##*/}
cat<<EOF
$b: Search emc2 source code and documentation
Usage:
$b [-R] [-F file-re] swish-pattern [grep-pattern]
-R: Rebuild the index. This is equivalent to running 'make swish'
in the source directory
-F: Specify a subset of files to search. This is an extended regular
expression, not a shell glob. To search all ".c" files, use
$b -F '\.c$' ...
swish-pattern: Specify the pattern to pass to 'swish -w'. See
"man SWISH-RUN" for more information
grep-pattern: Specify the pattern to pass to 'grep'. Each file listed
by swish is grepped for this pattern, and matching lines are
printed. By default, $b attempts to turn swish-pattern into
a grep-pattern, but this only works if swish-pattern is a simple
word. For more information about grep patterns, see 'man grep'
EOF
exit 1
}
FORCEREBUILD=0
while getopts "hRF:" opt
do
case "$opt" in
R) FORCEREBUILD=1 ;;
F) FILEFILTER="grep -E -z -e $OPTARG" ;;
h|?) usage ;;
esac
done
shift $((OPTIND - 1))
if [ "$FORCEREBUILD" -eq 1 ] || [ ! -f "$SWISHINDEX" ]
then
make -C "$SRCDIR" swish || exit $?
if [ $# -eq 0 ]; then exit 0; fi
fi
PAT="$1"; shift
if [ $# -eq 0 ]; then
REGEXP="${PAT%%\*}"
else
REGEXP="$1"
fi
if [ -n "$FILEFILTER" ]; then
swish-e -f "$SWISHINDEX" -H0 -x '<swishdocpath>\0' -w "$PAT" \
| $FILEFILTER | makerelative \
| xargs -0 grep -E -Hnis -e "$REGEXP"
else
swish-e -f "$SWISHINDEX" -H0 -x '<swishdocpath>\0' -w "$PAT" \
| makerelative \
| xargs -0 grep -E -Hnis -e "$REGEXP"
fi

189
scripts/torture.py Executable file
View File

@@ -0,0 +1,189 @@
#!/usr/bin/env python3
# Copyright (C) 2012 Jeff Epler <jepler@unpythonic.net>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
"""
This program creates "torture test" ngc files. These include arcs (helices)
in any plane, straight feeds, and traverses all in a random order with
differing feeds.
Run the resulting file in a stepper inifile with no headroom, and kick
things up a notch by using different max velocities and accelerations.
Turning feed override past 100% has also helped turn up bugs.
"""
from random import *
from math import sin, cos, pi
distances = [-10,1,-.1,0,0,.1,1,10]
radii = [.1, 1, 10]
arcangles = range(0, 360, 45)
aangles = [-361,-89,-1,1,91,359]
chance_angular = .5
chance_angular_helix = .5
chance_lineaxis = .2
def feed():
"Half the time, change the feed rate"
if random() < .5: return
print("F%d" % randrange(100, 1000, 10), end=" ")
def torture_arc(x, y, z, a, b, c, u, v, w):
"Generate a random arc (helix)"
plane = randrange(3)
print("G%d" % (plane+17), end=" ")
direction = randrange(2)
print("G%d" % (direction+2), end=" ")
feed()
theta = choice(arcangles)
theta1 = choice(arcangles)
r = choice(radii)
q = choice(distances)
print("(%d %d)" % (theta*180/pi, theta1*180/pi), end=" ")
if plane == 0: # XY plane
ox = -cos(theta)*r
oy = -sin(theta)*r
print("I%f J%f" % (ox, oy), end=" ")
z = z + q
x = x + ox + cos(theta1)*r
y = y + oy + sin(theta1)*r
elif plane == 1: # XZ plane
ox = -cos(theta)*r
oz = -sin(theta)*r
print("I%f K%f" % (ox, oz), end=" ")
x = x + ox + sin(theta1)*r
z = z + oz + cos(theta1)*r
y = y + q
else: # YZ plane
oy = -cos(theta)*r
oz = -sin(theta)*r
print("J%f K%f" % (oy, oz), end=" ")
x = x + q
y = y + oy + cos(theta1)*r
z = z + oz + sin(theta1)*r
if random() < chance_angular_helix:
a = a + choice(aangles)
print("A%f" % a, end=" ")
if random() < chance_angular_helix:
b = b + choice(aangles)
print("B%f" % b, end=" ")
if random() < chance_angular_helix:
c = c + choice(aangles)
print("C%f" % c, end=" ")
if random() < chance_angular_helix:
u = u + randrange(-500, 500)/100.
print("U%f" % u, end=" ")
if random() < chance_angular_helix:
v = v + randrange(-500, 500)/100.
print("V%f" % v, end=" ")
if random() < chance_angular_helix:
w = w + randrange(-500, 500)/100.
print("W%f" % w, end=" ")
print("X%f Y%f Z%f" % (x, y, z))
return x, y, z, a, b, c, u, v, w
def torture_line(x, y, z, a, b, c, u, v, w):
"Generate a random traverse or straight feed"
kind = randrange(2)
p = ""
p += "G%d " % kind
if random() < chance_lineaxis:
x = x + randrange(-10, 11)/2.
p += "X%f " % x
if random() < chance_lineaxis:
y = y + randrange(-10, 11)/2.
p += "Y%f " % y
if random() < chance_lineaxis:
z = z + randrange(-10, 11)/2.
p += "Z%f " % z
if random() < chance_lineaxis:
a = a + randrange(-180, 80)/2.
p += "A%f " % a
if random() < chance_lineaxis:
b = b + randrange(-180, 80)/2.
p += "B%f " % b
if random() < chance_lineaxis:
c = c + randrange(-180, 80)/2.
p += "C%f " % c
if random() < chance_lineaxis:
u = u + randrange(-500, 500)/100.
p += "U%f " % u
if random() < chance_lineaxis:
v = v + randrange(-500, 500)/100.
p += "V%f " % v
if random() < chance_lineaxis:
w = w + randrange(-500, 500)/100.
p += "W%f " % w
if len(p) > 4:
if kind == 1: feed()
print(p)
return x, y, z, a, b, c, u, v, w
def torture_main(runs):
"""Generate random chains of movement several times, restarting near the
center each time motion gets too far away."""
funcs = [torture_line, torture_arc]
#funcs = [torture_arc]
def R(x,l=-50, k=50): return x < l or x > k
print("g21")
for i in range(runs):
x = y = 0
z = 20
a = b = c = u = v = w = 0
if chance_angular or chance_angular_helix:
print("G0 X0 Y0 A0 B0 C0 U0 V0 W0 Z20")
else:
print("G0 X0 Y0 Z20")
print("F100")
while 1:
x, y, z, a, b, c, u, v, w = choice(funcs)(x, y, z, a, b, c, u, v, w)
if (R(x) or R(y) or R(z,-10,30) or
R(a,-30000,30000) or R(b, -30000,30000) or R(c, -30000,30000) or
R(u) or R(v) or R(w)):
break
# Do ten runs then print m2
torture_main(20)
print("m2")

8
scripts/travis-build-test.sh Executable file
View File

@@ -0,0 +1,8 @@
#!/bin/sh -ex
cd src
./autogen.sh
./configure --disable-check-runtime-deps --enable-build-documentation
make -O -j"$(getconf _NPROCESSORS_ONLN)" manpages V=1
make -O -j"$(getconf _NPROCESSORS_ONLN)" translateddocs V=1
make -O -j"$(getconf _NPROCESSORS_ONLN)" default pycheck V=1
../scripts/rip-environment runtests

View File

@@ -0,0 +1,14 @@
#!/bin/sh -ex
sudo apt-get update -qq
sudo apt-get install -y devscripts equivs build-essential --no-install-recommends
debian/configure
# This gives an error that is hidden from us
#mk-build-deps -i -r -s sudo -t 'apt-get -o Debug::pkgProblemResolver=yes --no-install-recommends --no-install-suggests'
if ! sudo apt-get -y build-dep . ; then
echo "E: could not install dependencies, maybe inspect d/control for hints:"
cat debian/control
echo
echo "E: apt-get -y build-dep . failed"
exit 1
fi
sudo apt install -y lintian

34
scripts/version-is-release Executable file
View File

@@ -0,0 +1,34 @@
#!/bin/bash
#
# This script checks if the current version (HEAD) is an official release.
# If so, the program returns with an exit code of 0 (True). If it is not a
# release, the program returns an exit code other than 0 (False).
#
# HEAD is an official release if there exists a tag that points to it and
# that is signed by the release manager's key.
#
if [ -n "$EMC2_HOME" ]; then
source "$EMC2_HOME"/scripts/githelper.sh
else
source "$(git rev-parse --show-toplevel)"/scripts/githelper.sh
fi
githelper "$1"
if [ -z "$GIT_TAG" ]; then
# no signed tags found
echo "no"
exit 1
fi
TAGGED_REV=$(git rev-parse "$GIT_TAG^{commit}")
HEAD_REV=$(git rev-parse HEAD)
if [ "$TAGGED_REV" = "$HEAD_REV" ]; then
echo "yes"
exit 0
fi
echo "no"
exit 1

16
scripts/xhc-hb04-accels Executable file
View File

@@ -0,0 +1,16 @@
#!/bin/bash
cat << EOF
#------------------------------------------------------------
This obsolete script:
$(basename "$0")
should be removed from LinuxCNC ini files (effective 2.8~pre)
xhc-hb04.tcl now controls reduced wheel jogging accels using
the hal pins:
axis.L.jog-accel-fraction
joint.N.jog-accel-fraction
#------------------------------------------------------------
EOF
exit 0