Import LinuxCNC source tree
This commit is contained in:
13
scripts/.gitignore
vendored
Normal file
13
scripts/.gitignore
vendored
Normal 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
118
scripts/cppcheck.sh
Executable 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
47
scripts/decode_dmesg
Executable 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
84
scripts/docs-anchor-check.py
Executable 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())
|
||||
12
scripts/get-deb-component-from-git
Executable file
12
scripts/get-deb-component-from-git
Executable 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
29
scripts/get-version-from-git
Executable 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
102
scripts/githelper.sh
Normal 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
82
scripts/gladevcp_demo
Executable 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
1019
scripts/hal-histogram
Executable file
File diff suppressed because it is too large
Load Diff
410
scripts/hal_demo
Executable file
410
scripts/hal_demo
Executable 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
|
||||
|
||||
5
scripts/halcmd_twopass.in
Normal file
5
scripts/halcmd_twopass.in
Normal 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
981
scripts/halreport
Executable 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
170
scripts/halrun.in
Normal 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
43
scripts/haltcl.in
Normal 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
108
scripts/htmlcheck.sh
Executable 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
1051
scripts/latency-histogram
Executable file
File diff suppressed because it is too large
Load Diff
536
scripts/latency-plot
Executable file
536
scripts/latency-plot
Executable 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
199
scripts/latency-test
Executable 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
1016
scripts/linuxcnc.in
Normal file
File diff suppressed because it is too large
Load Diff
179
scripts/linuxcnc_info.in
Executable file
179
scripts/linuxcnc_info.in
Executable 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
104
scripts/linuxcnc_var.in
Normal 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)
|
||||
35
scripts/linuxcncmkdesktop.in
Normal file
35
scripts/linuxcncmkdesktop.in
Normal 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
41
scripts/make-docs-pdf-index
Executable 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
36
scripts/man2adoc-migrate
Executable 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
156
scripts/manpage-name-check.py
Executable 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
151
scripts/monitor-xhc-hb04
Executable 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
920
scripts/moveoff_gui
Executable 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
123
scripts/platform-is-supported
Executable 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
129
scripts/pyvcp_demo
Executable 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
317
scripts/realtime.in
Normal 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
139
scripts/rip-environment.in
Normal 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
54
scripts/rtapi.conf.in
Normal 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
155
scripts/run-auto-pid-tuner
Executable 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
394
scripts/runtests.in
Executable 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
115
scripts/setup_designer.in
Executable 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
60
scripts/shellcheck.sh
Executable 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
581
scripts/sim_pin
Executable 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
51
scripts/simulate_probe
Executable 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
100
scripts/swish
Executable 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
189
scripts/torture.py
Executable 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
8
scripts/travis-build-test.sh
Executable 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
|
||||
14
scripts/travis-install-build-deps.sh
Executable file
14
scripts/travis-install-build-deps.sh
Executable 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
34
scripts/version-is-release
Executable 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
16
scripts/xhc-hb04-accels
Executable 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
|
||||
Reference in New Issue
Block a user