wharfie: ./trunk/wharfie/examples/toolchain_amd64_jessie/Wharfile
Bash
bash
(Bash)
#
# Build a basic root filesystem including Qt5 and export the toolchain
#
FROM debian_amd64_jessie
RUN apt-get install -y qtbase5-dev libqt5svg5
RUN ldconfig
ENV ADDITIONAL_TOOLCHAIN_PACKAGES libstdc++-4.9-dev qtbase5-dev-tools qt5-default perl libqt5svg5 libqt5svg5-dev
TOOLCHAIN toolchain_debian_jessie_amd64.tar
Last updated: 2017-11-13
wharfie: ./trunk/wharfie/examples/toolchain_amd64_stretch/Wharfile
Bash
bash
(Bash)
#
# Build a basic root filesystem including Qt5 and export the toolchain
#
FROM debian_amd64_stretch
RUN apt-get install -y qtbase5-dev libqt5svg5
RUN ldconfig
ENV ADDITIONAL_TOOLCHAIN_PACKAGES libstdc++-6-dev qtbase5-dev-tools qt5-default perl libqt5svg5 libqt5svg5-dev
TOOLCHAIN toolchain_debian_stretch_amd64.tar
Last updated: 2017-11-13
wharfie: ./trunk/wharfie/qemu/files/wharfie.service
Bash
bash
(Bash)
[Unit]
Description=wharfie
After=network.target
[Service]
Type=idle
ExecStart=/usr/local/wharfie/qemu_autorun.sh
StandardInput=tty
#TTYPath=/dev/tty2
#TTYReset=yes
#TTYHangup=yes
[Install]
WantedBy=multi-user.target
Last updated: 2018-03-09
wharfie: ./trunk/wharfie/wharfie.py
Bash
bash
(Bash)
#!/usr/bin/python
#
# Copyright 2017 Ingo Hornberger <ingo_@gmx.net>
#
# This software is licensed under the MIT License
#
# Permission is hereby granted, free of charge, to any person obtaining a copy of this
# software and associated documentation files (the "Software"), to deal in the Software
# without restriction, including without limitation the rights to use, copy, modify,
# merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to the following
# conditions:
#
# The above copyright notice and this permission notice shall be included in all copies
# or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
# PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
# OR OTHER DEALINGS IN THE SOFTWARE.
#
################################################################################
#
# The file format and the principle are lent from Docker.
# It's just all a bit simpler, as we only create images
# and have no infrastructure to provide prebuilt images.
#
# What is more complex for embedded systems is the legal
# part. When s.o. is using docker to build a web service,
# he doesn't include the used open source software into
# a product, which he is commercially using, but he uses
# it himself to provide a web service to his customers.
#
# With most embedded systems, which Wharfie is focusing
# on, this is different. People are usually building
# products, including the open source software. So they
# need to respect the licenses of the software, which
# they are including.
#
# For this reason Wharfie only supports debian based
# systems, as the debian projects takes much care about
# respecting open source licenses, and makes it easy
# to get a list of all licenses used in a system.
#
# It makes it also easy to get the source packets, fitting
# to the installed binary version. Therefore, one can
# easily provide all sources of the open source software,
# included in his image to his customers.
#
# With this background, the supported commands of Wharfie
# are differing slightly from its big brother Docker.
#
# Commands:
# - FROM: Supports only a fix number of build rules.
# e.g.: debian_armhf_sid, debian_etch, ...
#
# - RUN: Execute a command inside the image
#
# - RUN HOST: Execute a command on the host system,
# But inside of the image root.
#
# - ADD: Add an archive or file to the root filesystem.
#
# - TO: name of the final archive
#
# - SOURCE: name of an archive, where the currently installed
# source packages are written to.
#
# - LICENSE: Extract a list of all licenses, of all currently
# installed packages.
#
# - TOOLCHAIN: Build a cross-toolchain for the currently
# installed root filesystem.
#
# Note: Technically everything can be done, using RUN
# and RUN HOST. Other commands, like ADD or TO
# are only supported for more convenience.
# So for example if you want to combine the
# resulting image with a special bootloader or
# something, it can be flexibly done with RUN HOST.
import os
import re
import sys
import glob
import binascii
import argparse
from shutil import copyfile
from lib import makefile as make
from lib import actions
from lib import files
regexCommand = '^[ \t]*(RUN HOST|RUN|FROM|TOOLCHAIN|TO|ADD|COPY|WORKDIR|ENTRYPOINT|SOURCE|LICENSE|ENV|ARG|EXPOSE|[a-zA-Z0-9]+)([^\n]*)';
g_depend = '';
#
# Read a Wharfile
#
def read_wharfile(filename):
content = open(filename, 'r').read()
content = content.replace('\\\n', '')
bld = ''
g_allDepends = '';
g_depend = '';
for cmd in re.findall(regexCommand, content, flags=re.MULTILINE):
# calculate current build target and dependency names
dep = list();
if bld != '':
dep.append(bld);
if g_depend != '':
dep.append(g_depend);
if cmd[0] not in ('FROM', 'ENV', 'ARG', 'EXPOSE', 'WORKDIR'):
g_allDepends += str(cmd);
if g_allDepends != '':
bld = format(0xFFFFFFFF & binascii.crc32(g_allDepends), '02X') + ".piling.tar";
# EXPOSE
if cmd[0] == 'EXPOSE':
pass
# WORKDIR
elif cmd[0] == 'WORKDIR':
actions.workingdir(bld, dep, cmd)
# FROM
elif cmd[0] == 'FROM':
# special handling for debootstrap
if cmd[1].lstrip().startswith("debian_"):
g_depend = cmd[1].lstrip() + ".tar";
else:
g_depend = files.input_file(cmd[1]);
# ENV / ARG
elif cmd[0] == 'ENV' or cmd[0] == 'ARG':
arg = cmd[1].lstrip()
if (not " " in arg) and ("=" in arg):
arg = arg.replace("=", " ")
make.environment.append(arg);
# RUN
elif cmd[0] == 'RUN':
makeTarget=actions.run(bld, dep, cmd)
make.makeTargets.append(makeTarget);
g_depend = '';
# RUN HOST
elif cmd[0] == 'RUN HOST':
makeTarget=actions.run_host(bld, dep, cmd)
make.makeTargets.append(makeTarget);
g_depend = '';
# ENTRYPOINT
elif cmd[0] == 'ENTRYPOINT':
makeTarget=actions.entrypoint(bld, dep, cmd)
make.makeTargets.append(makeTarget);
g_depend = '';
# COPY (same as add, but w/o magic)
elif cmd[0] == 'COPY':
makeTarget=actions.copy(bld, dep, cmd)
make.makeTargets.append(makeTarget);
g_depend = '';
# ADD
elif cmd[0] == 'ADD':
makeTarget=actions.add(bld, dep, cmd)
make.makeTargets.append(makeTarget);
g_depend = '';
# TO
elif cmd[0] == 'TO':
makeTarget=actions.to(bld, dep, cmd)
bld = makeTarget["name"]
make.makeTargets.append(makeTarget);
g_depend = '';
# SOURCE
elif cmd[0] == 'SOURCE':
makeTarget=actions.source(bld, dep, cmd)
make.makeTargets.append(makeTarget);
g_depend = '';
# LICENSE
elif cmd[0] == 'LICENSE':
makeTarget=actions.license(bld, dep, cmd)
make.makeTargets.append(makeTarget);
g_depend = '';
# TOOLCHAIN
elif cmd[0] == 'TOOLCHAIN':
makeTarget=actions.toolchain(bld, dep, cmd)
make.makeTargets.append(makeTarget);
g_depend = '';
else:
print ('error: parse error in Wharfile');
print ('error: unknown command: "%s %s"' % (cmd[0], cmd[1]));
exit(-1);
make.finalTarget.append(bld);
#
# Main
#
def main():
parser = argparse.ArgumentParser()
parser.add_argument('--clean', action='store_true', help="Clear intermediate files" )
parser.add_argument('--info', action='store_true', help="Print info about already generated image" )
parser.add_argument('--incremental', action='store_true', help="An experimental feature, which uses incremental backup mechanisms of tar." )
parser.add_argument('--gen-only', action='store_true', help="Generate makefile only, but don't build it" )
parser.add_argument('--dry-run', action='store_true', help="Generate makefile with disabled run actions and don't build it" )
parser.add_argument('--qemu', action='store_true', help="Enable qemu build (default if qemu support is available)" )
parser.add_argument('--no-qemu', action='store_true', help="Even if qemu support for wharfie is available, run without" )
parser.add_argument('--no-proc', action='store_true', help="suppress mounting of the proc filesystem." )
parser.add_argument('--verbose', action='store_true', help="Print verbose make output" )
parser.add_argument('wharfile', default='Wharfile', nargs='?', help="Filename of a 'Wharfile'. By default ./Wharfile is used." )
args = parser.parse_args()
# check if qemu should be used
if os.path.isfile(os.path.abspath(os.path.dirname(sys.argv[0])) + "/qemu/qwharfie.qcow"):
args.qemu = True
else:
if args.qemu:
print("error: qemu build forced, but no qemu support available.")
exit(1)
if args.no_qemu:
args.qemu = False
# If qemu is used, we generate only the make file. The build is then done in a second stage.
if args.qemu:
args.gen_only=True
# generate makefile
if os.path.isfile(args.wharfile):
read_wharfile(args.wharfile);
else:
print("error: Wharfile '%s' not found." % args.wharfile)
exit(1)
if os.path.isfile(os.path.abspath(os.path.dirname(sys.argv[0])) + "/wharfie.mk"):
make.write_makefile('Makefile', args.dry_run, os.path.dirname(sys.argv[0]), args.incremental, not args.no_proc);
else:
make.write_makefile('Makefile', args.dry_run, os.path.abspath(os.path.dirname(sys.argv[0])) + "/../share/wharfie", args.incremental, not args.no_proc);
# call make
flags=""
if args.verbose:
flags+=" VERBOSE=y"
if args.clean:
os.system("make %s clean" % flags)
elif args.info:
os.system("make %s info" % flags)
elif not args.gen_only and not args.dry_run:
os.system("make %s" % flags);
# call qemu if enabled
if args.qemu and not args.clean:
qemu = "qemu-system-x86_64"
disk = os.path.dirname(sys.argv[0]) + "/qemu/qwharfie.qcow"
cache = "qwharfie.cache.qcow"
input = "qwharfie.input.raw"
output = "qwharfie.output.raw"
kernel = glob.glob(os.path.dirname(sys.argv[0]) + "/qemu/boot/vmlinuz*")[0]
initrd = glob.glob(os.path.dirname(sys.argv[0]) + "/qemu/boot/initrd*")[0]
uuid = open(os.path.dirname(sys.argv[0]) + "/qemu/qwharfie.qcow.uuid", "r").readline().rstrip()
# copy cache and output
if not os.path.isfile(cache):
copyfile(os.path.dirname(sys.argv[0]) + "/qemu/" + cache, cache)
copyfile(os.path.dirname(sys.argv[0]) + "/qemu/" + output, output)
copyfile(os.path.dirname(sys.argv[0]) + "/wharfie.mk", "wharfie.mk")
input_command='tar --exclude="%s" --exclude="%s" --exclude="%s" -cf %s .' % (input, output, cache, input)
qemu_command='%s -machine accel=kvm -m 512 -drive file=%s,index=0,media=disk,snapshot=on -drive file=%s,index=1,media=disk,snapshot=off -drive file=%s,index=2,media=disk,snapshot=off -drive file=%s,index=3,media=disk,snapshot=off -net nic,model=virtio -net user -kernel %s -initrd %s -append "root=UUID=%s ro single console=ttyS0 fsck.mode=skip systemd.unit=multi-user.target" -nographic' % (qemu, disk, cache, input, output, kernel, initrd, uuid)
output_command='tar -xf %s' % (output)
print("cmd: %s\n" % input_command)
os.system(input_command);
print("cmd: %s\n" % qemu_command)
os.system(qemu_command);
print("cmd: %s\n" % output_command)
os.system(output_command);
if __name__ == "__main__":
main()
Last updated: 2018-09-01
wharfie: ./trunk/wharfie/wharfie.mk
Bash
bash
(Bash)
#
# Copyright 2017 Ingo Hornberger <ingo_@gmx.net>
#
# This software is licensed under the MIT License
#
# Permission is hereby granted, free of charge, to any person obtaining a copy of this
# software and associated documentation files (the "Software"), to deal in the Software
# without restriction, including without limitation the rights to use, copy, modify,
# merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to the following
# conditions:
#
# The above copyright notice and this permission notice shall be included in all copies
# or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
# PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
# OR OTHER DEALINGS IN THE SOFTWARE.
################################################################################
SHELL=/bin/bash
PATH := $(PATH):/sbin:/usr/sbin
SUDO=$(shell PATH=${PATH}:/sbin:/usr/sbin which sudo)
FAKEROOT=$(shell PATH=${PATH}:/sbin:/usr/sbin which fakeroot)
FAKECHROOT=$(shell PATH=${PATH}:/sbin:/usr/sbin which fakechroot)
DBOOTSTRAP=$(shell PATH=${PATH}:/sbin:/usr/sbin which qemu-debootstrap)
DBOOTSTRAP2=$(shell PATH=${PATH}:/sbin:/usr/sbin which debootstrap)
KPARTX=$(shell PATH=${PATH}:/sbin:/usr/sbin which kpartx)
PACKAGES=locales systemd dbus
MAKEFILE_PATH := $(abspath $(lastword $(MAKEFILE_LIST)))
CWD := $(dir $(MAKEFILE_PATH))
# extend path to sbin binaries
export PATH := /usr/sbin:${PATH}
ifeq (${SUDO},)
$(error sudo not found. On debian, you may install the package sudo first)
endif
#ifeq (${FAKEROOT},)
#$(error fakeroot not found. On debian, you may install the package fakeroot first)
#endif
#ifeq (${FAKECHROOT},)
#$(error fakechroot not found. On debian, you may install the package fakechroot first)
#endif
ifeq (${DBOOTSTRAP},)
$(error qemu-debootstrap not found. On debian, you may install the package qemu-user-static first)
endif
ifeq (${DBOOTSTRAP2},)
$(error debootstrap not found. On debian, you may install the package debootstrap first)
endif
ifeq (${KPARTX},)
$(error kpartx not found. On debian, you may install the package kpartx first)
endif
.PHONY: test
test:
echo ${FAKEROOT}
#
# Set version specific system variables for the toolchain
#
debian_armhf_%.tar: gcc_postfix=-arm-none-eabi
debian_armhf_%.tar: libc_postfix=-armhf-cross
debian_armel_%.tar: gcc_postfix=-arm-none-eabi
debian_armel_%.tar: libc_postfix=-armhf-cross
debian_arm64_%.tar: gcc_postfix=-aarch64-linux-gnu
debian_arm64_%.tar: libc_postfix=-arm64-cross
# stretch changed doesn't support ARM non-eabi anymore, but has more specific ones
debian_armhf_stretch.tar: gcc_postfix=-arm-linux-gnueabihf
debian_armel_stretch.tar: gcc_postfix=-arm-linux-gnueabi
# since stretch, ARM supports g++
debian_armhf_stretch.tar: additional_arch_packages+=g++-arm-linux-gnueabihf
debian_armel_stretch.tar: additional_arch_packages+=g++-arm-linux-gnueabi
debian_amd64_stretch.tar: additional_arch_packages+=g++
#
# Debian / ARM Hardfloat
#
.PHONY: debian_armhf_%.tar
debian_armhf_%.tar:
-mkdir ./$$(basename $@ .tar)
${SUDO} ${DBOOTSTRAP} --arch=armhf --variant=minbase --include="${PACKAGES}" $* ./$$(basename $@ .tar) http://ftp.debian.org/debian/
(echo "deb http://ftp.debian.org/debian $* main"; \
echo "deb-src http://ftp.debian.org/debian $* main") | ${SUDO} tee ./$$(basename $@ .tar)/etc/apt/sources.list
cd $$(basename $@ .tar); ${SUDO} tar -cf "../$@" .
${SUDO} rm -R ./$$(basename $@ .tar)
echo DEBIAN_VERSION=$* > debian_version.mk
echo GCC_POSTFIX=${gcc_postfix} >> debian_version.mk
echo LIBC_POSTFIX=${libc_postfix} >> debian_version.mk
echo ADDITIONAL_ARCH_PACKAGES=${additional_arch_packages} >> debian_version.mk
#
# Debian / ARM EABI
#
.PHONY: debian_armel_%.tar
debian_armel_%.tar:
-mkdir ./$$(basename $@ .tar)
${SUDO} ${DBOOTSTRAP} --arch=armel --variant=minbase --include="${PACKAGES}" $* ./$$(basename $@ .tar) http://ftp.debian.org/debian/
(echo "deb http://ftp.debian.org/debian $* main"; \
echo "deb-src http://ftp.debian.org/debian $* main") | ${SUDO} tee ./$$(basename $@ .tar)/etc/apt/sources.list
cd $$(basename $@ .tar); ${SUDO} tar -cf "../$@" .
${SUDO} rm -R ./$$(basename $@ .tar)
echo DEBIAN_VERSION=$* > debian_version.mk
echo GCC_POSTFIX=${gcc_postfix} >> debian_version.mk
echo LIBC_POSTFIX=${libc_postfix} >> debian_version.mk
echo ADDITIONAL_ARCH_PACKAGES=${additional_arch_packages} >> debian_version.mk
#
# Debian / ARM64
#
.PHONY: debian_arm64_%.tar
debian_arm64_%.tar:
-mkdir ./$$(basename $@ .tar)
${SUDO} ${DBOOTSTRAP} --arch=arm64 --variant=minbase --include="${PACKAGES}" $* ./$$(basename $@ .tar) http://ftp.debian.org/debian/
(echo "deb http://ftp.debian.org/debian $* main"; \
echo "deb-src http://ftp.debian.org/debian $* main") | ${SUDO} tee ./$$(basename $@ .tar)/etc/apt/sources.list
cd $$(basename $@ .tar); ${SUDO} tar -cf "../$@" .
${SUDO} rm -R ./$$(basename $@ .tar)
echo DEBIAN_VERSION=$* > debian_version.mk
echo GCC_POSTFIX=${gcc_postfix} >> debian_version.mk
echo LIBC_POSTFIX=${libc_postfix} >> debian_version.mk
echo ADDITIONAL_ARCH_PACKAGES=${additional_arch_packages} >> debian_version.mk
#
# Debian x86/amd64...
# extended for debian_{amd64,i386}_{deb_version}.tar
# or old format "debian_{deb_version}.tar"
.PHONY: debian_%.tar
debian_%.tar:
$(eval ARCH:=$(if $(word 3,$(subst _, ,$@)),$(word 2,$(subst _, ,$@)),"i386"))
$(eval VERSION:=$(if $(word 3,$(subst _, ,$@)),$(basename $(word 3,$(subst _, ,$@))),$(basename $(word 2,$(subst _, ,$@)))))
-mkdir ./$$(basename $@ .tar)
${SUDO} ${DBOOTSTRAP} --arch=${ARCH} --variant=minbase --include="${PACKAGES}" ${VERSION} ./$$(basename $@ .tar) http://ftp.debian.org/debian/
(echo "deb http://ftp.debian.org/debian ${VERSION} main"; \
echo "deb-src http://ftp.debian.org/debian ${VERSION} main") | ${SUDO} tee ./$$(basename $@ .tar)/etc/apt/sources.list
cd $$(basename $@ .tar); ${SUDO} tar -cf "../$@" .
${SUDO} rm -R ./$$(basename $@ .tar)
echo DEBIAN_VERSION=${VERSION} > debian_version.mk
echo GCC_POSTFIX=${gcc_postfix} >> debian_version.mk
echo LIBC_POSTFIX=${libc_postfix} >> debian_version.mk
echo ADDITIONAL_ARCH_PACKAGES=${additional_arch_packages} >> debian_version.mk
-include debian_version.mk
#
# Debian Cross-Toolchain
#
debian_toolchain.tar:
${MAKE} make_toolchain
# note, this is only a sub-taget, so that we build in a subprocess, and debian_version.mk is included
.PHONY: make_toolchain
make_toolchain:
-mkdir ./debian_toolchain
${SUDO} ${DBOOTSTRAP} --arch=amd64 --variant=minbase --include="${ADDITIONAL_TOOLCHAIN_PACKAGES} gcc${GCC_POSTFIX} libc6-dev${LIBC_POSTFIX} ${ADDITIONAL_ARCH_PACKAGES}" ${DEBIAN_VERSION} ./debian_toolchain http://ftp.debian.org/debian/
${SUDO} chroot ./debian_toolchain ldconfig
cd debian_toolchain && ${SUDO} tar -cf "../debian_toolchain.tar" .
${SUDO} rm -R ./debian_toolchain
#
# Export environment shell script for the toolchain
#
define ENV_SH
TC_PATH=$$(dirname $$_)
SYSROOT=$${TC_PATH}/target
INCLUDEDIR=$${TC_PATH}/host/usr/arm-linux-gnueabihf/include/
LIBRARYDIR=$${TC_PATH}/target/usr/lib/arm-linux-gnueabihf/
COMPILE_PREFIX=$${TC_PATH}/host/usr/bin/arm-none-eabi-
PLUGINDIR=$$(dirname $$(readlink -f $${TC_PATH}/host/usr/lib/gcc/arm-none-eabi/*/liblto_plugin.so))
HOSTLIBDIR=$${TC_PATH}/host/usr/lib/i386-linux-gnu/:$${TC_PATH}/host/usr/lib/:$${TC_PATH}/host/lib
LD_LIBRARY_PATH=$${HOSTLIBDIR}
CC=$${COMPILE_PREFIX}gcc
CXX=$${COMPILE_PREFIX}g++
LD=$${COMPILE_PREFIX}ld
AS=$${COMPILE_PREFIX}as
AR=$${COMPILE_PREFIX}ar
NM=$${COMPILE_PREFIX}nm
GCOV=$${COMPILE_PREFIX}gcov
OBJDUMP=$${COMPILE_PREFIX}objdump
CFLAGS=
LDFLAGS=
# common C/LD Flags
CFLAGS="$${CFLAGS} --sysroot=$${SYSROOT}"
CFLAGS="$${CFLAGS} -B$${PLUGINDIR} -mfloat-abi=hard"
LDFLAGS="$${CFLAGS}"
# CFLAGS
CFLAGS="$${CFLAGS} --sysroot=$${SYSROOT}"
CFLAGS="-I$${INCLUDEDIR}"
# LDFLAGS
LDFALGS="-L$${LIBRARYDIR}"
LDFLAGS="$${LDFLAGS} -L$${LIBRARYDIR}"
LDFLAGS="$${LDFLAGS} -B$${LIBRARYDIR}"
LDFLAGS="$${LDFLAGS} -lc"
export CC
export CXX
export LD
export AS
export AR
export NM
export GCOV
export OBJDUMP
export CFLAGS
export LDFLAGS
export LD_LIBRARY_PATH
endef
export ENV_SH
debian_toolchain_env.sh:
@echo "$${ENV_SH}" > $@
# output some info about the locally generated filesystem
info:
@[ -f ${OUTPUT_FILE} ] || (echo "The image ${OUTPUT_FILE} is not build, yet."; false)
@targethostname="$$(tar -O -xf ${OUTPUT_FILE} ./etc/hostname)"; \
printf "Hostname: %s\n" $${targethostname}; \
printf "IP: %s (if target is running)\n" $$(ping -c 1 "$${targethostname}" 2>&1 | sed -n '/bytes from/ s,.*(\([^)]*\)).*,\1, p');
clean:
rm -f debian_version.mk
rm -f debian_*.tar
rm -f *.piling.tar *.piling.tar.snar
for i in ???????.piling/; do sudo rm -Rf "./$$i"; done
for i in ????????.piling/; do sudo rm -Rf "./$$i"; done
Last updated: 2019-04-08
wharfie: ./trunk/wharfie/qemu/qwharfie.sh
Bash
bash
(Bash)
PREFIX=$(dirname $(readlink -f $0))
DISK=${PREFIX}/qwharfie.qcow
INPUT=qwharfie.input.raw
OUTPUT=qwharfie.output.raw
CACHE=qwharfie.cache.qcow
UUID=$(cat ${DISK}.uuid)
INITRD=$(ls -1 ${PREFIX}/boot/initrd* | sort | head -n 1)
KERNEL=$(ls -1 ${PREFIX}/boot/vmlinuz* | sort | head -n 1)
QEMU=$(which qemu-system-x86_64)
QWHARFIE_OUTPUT=1G
QWHARFIE_CACHE=10G
function error()
{
msg=${1}
echo "error: ${msg}";
exit 1;
}
# Check, that all prerequisits are there
[ -z ${DISK} ] && error "No hard drive image defined";
[ -z ${UUID} ] && error "No UUID for HD image found. Has to be a file, named '<img-name>.uuid'.";
[ -z ${INITRD} ] && error "No initial ramdisk found (searched in /boot for initrd*)";
[ -z ${KERNEL} ] && error "No kernel found (searched in /boot for vmlinuz*)";
[ -z ${QEMU} ] && error "No QEMU system emulation found. Install Qemu system emulation for x86_64";
# Generate Makefile
#${PREFIX}/../wharfie.py --gen-only
#cp ${PREFIX}/../wharfie.mk .
# Create input, output and cache images
tar --exclude="${INPUT}" --exclude="${OUTPUT}" --exclude="${CACHE}" -cf ${INPUT} .
if [ ! -f ${OUTPUT} ]; then
qemu-img create -f raw ${OUTPUT} ${QWHARFIE_OUTPUT}
fi
if [ ! -f ${CACHE} ]; then
if [ -f ${PREFIX}/${CACHE} ]; then
cp ${PREFIX}/${CACHE} .
else
qemu-img create -f raw ${CACHE}.raw ${QWHARFIE_CACHE}
# create partition table
(
echo o # Create a new empty DOS partition table
echo n # Add a new partition
echo p # Primary partition
echo 1 # Partition number
echo # First sector (Accept default: 1)
echo # Last sector (Accept default: varies)
echo w # Write changes
) | sudo fdisk ${CACHE}.raw &&
l=$(sudo /sbin/kpartx -l ${CACHE}.raw | sed -n '/loop/ s,.*/dev/\(loop[0-9]\+\).*,\1, p;q;') &&
sudo /sbin/kpartx -as ${CACHE}.raw &&
# Create filesystem
sudo mkfs.ext3 /dev/mapper/${l}p1 &&
sudo /sbin/kpartx -ds ${CACHE}.raw &&
qemu-img convert -O qcow2 ${CACHE}.raw ${CACHE}
rm -f ${CACHE}.raw
fi
fi
# Run Qemu
${QEMU} -machine accel=kvm -m 512 -hda ${DISK} -hdb ${CACHE} -hdc ${INPUT} -hdd ${OUTPUT} -net nic,model=virtio -net user -kernel ${KERNEL} -initrd ${INITRD} -append "root=UUID=${UUID} ro single console=ttyS0 fsck.mode=skip systemd.unit=multi-user.target" -nographic
tar -xf ${OUTPUT}
Last updated: 2018-03-09
wharfie: ./trunk/wharfie/LICENSE
Bash
bash
(Bash)
Copyright 2017 Ingo Hornberger <ingo_@gmx.net>
This software is licensed under the MIT License
Permission is hereby granted, free of charge, to any person obtaining a copy of this
software and associated documentation files (the "Software"), to deal in the Software
without restriction, including without limitation the rights to use, copy, modify,
merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be included in all copies
or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
OR OTHER DEALINGS IN THE SOFTWARE.
Last updated: 2017-11-13
wharfie: ./trunk/wharfie/qemu/qcow.sh
Bash
bash
(Bash)
#!/bin/bash
OUT=${1}
d=$(basename $(mktemp -d));
# create blank image
qemu-img create -f raw ${OUT}.raw 2G &&
# create partition table
(
echo o # Create a new empty DOS partition table
echo n # Add a new partition
echo p # Primary partition
echo 1 # Partition number
echo # First sector (Accept default: 1)
echo # Last sector (Accept default: varies)
echo w # Write changes
) | sudo /sbin/fdisk ${OUT}.raw &&
l=$(/sbin/kpartx -l ${OUT}.raw | sed -n '/loop/ s,.*/dev/\(loop[0-9]\+\).*,\1, p;q;') &&
/sbin/kpartx -as ${OUT}.raw &&
# Create filesystem
echo mount /dev/mapper/${l}p1 /tmp/${d}
uuid=$(/sbin/mkfs.ext3 /dev/mapper/${l}p1 | sed -n '/^Filesystem UUID/ s,.*: \(.*\),\1, p') &&
mount /dev/mapper/${l}p1 /tmp/${d} &&
# Copy files
((tar -cf - .) | (cd /tmp/${d}; tar -xf -)) &&
# Generate fstab with root partition
mkdir /tmp/${d}/build &&
(
echo "UUID=${uuid} / ext3 errors=remount-ro 0 0";
) > /tmp/${d}/etc/fstab
umount /tmp/${d} &&
/sbin/kpartx -ds ${OUT}.raw &&
qemu-img convert -O qcow2 ${OUT}.raw ${OUT}
echo ${uuid} > ${OUT}.uuid
rm -f ${OUT}.raw
rmdir /tmp/${d}
Last updated: 2018-06-27
wharfie: ./trunk/wharfie/Makefile
Bash
bash
(Bash)
#
# Wharfie Makefile
# Used just for test and install, as wharfie is a pure python script,
# which can be used in place.
#
ifeq (${TEST},)
TESTS=$(addsuffix result.tar, $(wildcard tests/*/))
else
TESTS=tests/${TEST}/result.tar
endif
help:
@echo "make <target>:"
@echo "- test: Execute all tests from the subfolder tests/"
@echo "- test TEST=<name>: Execute only the test in the folder specified by the TEST variable"
test: ${TESTS}
%/result.tar:
( \
echo "Entering $*..."; \
cd $*; \
echo "Running test..."; \
../../wharfie.py; \
echo "Checking result."; \
[ ! -f $@ ] && exit 1; \
)
Last updated: 2020-08-04
wharfie: ./trunk/wharfie/tests/Toolchain/Wharfile
Bash
bash
(Bash)
#
# Build a basic root filesystem including Qt5 and export the toolchain
#
FROM debian_amd64_stretch
RUN apt-get install -y qtbase5-dev libqt5svg5
RUN ldconfig
ENV ADDITIONAL_TOOLCHAIN_PACKAGES libstdc++-6-dev qtbase5-dev-tools qt5-default perl libqt5svg5 libqt5svg5-dev
TOOLCHAIN result.tar
Last updated: 2018-03-09
wharfie: ./trunk/wharfie/tests/Toolchain2/Wharfile
Bash
bash
(Bash)
# Testcase: Check if command of TOOLCHAIN action is executed in the host sysroot
FROM debian_amd64_stretch
TOOLCHAIN toolchain.tar echo "test"; pwd; touch /tmp/file-in-host-sysroot
RUN HOST tar -tf ../toolchain.tar | grep ./host/tmp/file-in-host-sysroot; \
[ $? == 0 ] && touch ../result.tar
Last updated: 2020-08-04
wharfie: ./trunk/wharfie/wharfie.sh
Bash
bash
(Bash)
#!/bin/bash
PREFIX=/usr/local/
exec ${PREFIX}/share/wharfie/wharfie.py ${1+"$@"}
Last updated: 2017-11-20
sm3raspistepper: ./tags/Release_1.0/devdesc/SM3_Drive_PosControl.devdesc.xml
Bash
bash
(Bash)
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE DeviceDescription [
<!ENTITY standardparameters SYSTEM "StandardParameters.xml">
]>
<!-- edited with XMLSPY v2004 rel. 4 U (http://www.xmlspy.com) by Hilmar Panzer (3S - Smart Software Solutions GmbH) -->
<DeviceDescription xmlns="http://www.3s-software.com/schemas/DeviceDescription-1.0.xsd" xmlns:ts="http://www.3s-software.com/schemas/TargetSettings-0.1.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.3s-software.com/schemas/DeviceDescription-1.0.xsd D:\CoDeSys\Documentation\DOCUME~2.)\PRODUC~1\CODESY~1.0\DEVELO~1\FEATUR~1\DeviceDescription-1.0.xsd">
<Types namespace="localTypes"></Types>
<Strings namespace="localStrings">
</Strings>
<Files namespace="local">
<Language lang="en">
<File fileref="local" identifier="DriveIcon">
<LocalFile>SoftMotion_PosControl_Small_Stepper.ico</LocalFile>
</File>
</Language>
</Files>
<Device>
<DeviceIdentification>
<Type>1025</Type>
<Id>FFFF 0001</Id>
<Version>0.0.0.1</Version>
</DeviceIdentification>
<CompatibleVersions>
</CompatibleVersions>
<DeviceInfo>
<Name name="localStrings:TypeName">SM_Drive_RaspiStepper</Name>
<Description name="localStrings:TypeDescription">SoftMotion position controlled drive PTt</Description>
<Vendor name="localStrings:_3S">3S - Smart Software Solutions GmbH</Vendor>
<OrderNumber>1805</OrderNumber>
<Icon name="local:DriveIcon">SoftMotion_PosControl_Small_Stepper.ico</Icon>
</DeviceInfo>
<Connector connectorId="1" moduleType="1248" interface="Common.SoftMotion.Logical" role="parent" explicit="false" hostpath="0" initialStatusFlag="241">
<InterfaceName name="local:InterfaceName">SM_Drive_Logical:</InterfaceName>
<Var />
</Connector>
<Connector role="child" connectorId="0" interface="Common.SoftMotion.General" moduleType="1025" explicit="false">
<InterfaceName name="localStrings:InterfaceName">SM_Drive_PosControl:</InterfaceName>
<Slot count="1" allowEmpty="false" />
<Var />
<HostParameterSet>
&standardparameters;
<ParameterSection>
<Name name="local:AXIS_REF">AXIS_REF: Scalings</Name>
<Parameter ParameterId="1051" type="std:DWORD">
<Attributes download="true" functional="false" />
<Default>1</Default>
<Name name="localStrings:AXIS_REF.dwRatioTechUnitsDenom">dwRatioTechUnitsDenom</Name>
<Description name="localStrings:AXIS_REF.dwRatioTechUnitsDenom.Desc">conversion inc./tech.units denominator</Description>
<Custom />
</Parameter>
<Parameter ParameterId="1052" type="std:DINT">
<Attributes download="true" functional="false" />
<Default>1</Default>
<Name name="localStrings:AXIS_REF.iRatioTechUnitsNum">iRatioTechUnitsNum</Name>
<Description name="localStrings:AXIS_REF.iRatioTechUnitsNum.Desc">conversion inc./tech.units numerator</Description>
<Custom />
</Parameter>
<Parameter ParameterId="5000" type="std:DINT">
<Default>1</Default>
<Name name="localStrings:ScalingIncs">ScalingIncs</Name>
<download>false</download>
</Parameter>
<Parameter ParameterId="5001" type="std:DINT">
<Default>1</Default>
<Name name="localStrings:ScalingMotorTurns1">ScalingMotorTurns1</Name>
<download>false</download>
</Parameter>
<Parameter ParameterId="5002" type="std:DINT">
<Default>1</Default>
<Name name="localStrings:ScalingMotorTurns2">ScalingMotorTurns2</Name>
<download>false</download>
</Parameter>
<Parameter ParameterId="5003" type="std:DINT">
<Default>1</Default>
<Name name="localStrings:ScalingGearOutput1">ScalingGearOutput1</Name>
<download>false</download>
</Parameter>
<Parameter ParameterId="5004" type="std:DINT">
<Default>1</Default>
<Name name="localStrings:ScalingGearOutput2">ScalingGearOutput2</Name>
<download>false</download>
</Parameter>
<Parameter ParameterId="5005" type="std:DINT">
<Default>1</Default>
<Name name="localStrings:ScalingUnits">ScalingUnits</Name>
<download>false</download>
</Parameter>
<Parameter ParameterId="5006" type="std:BOOL">
<Default>FALSE</Default>
<Name name="localStrings:InvertDirection">InvertDirection</Name>
<download>false</download>
</Parameter>
<Parameter ParameterId="5007" type="std:BOOL">
<Default>FALSE</Default>
<Name name="localStrings:HalfStep">HalfStep</Name>
<Description name="Motor takes half steps instead of full steps">Motor takes half steps instead of full steps</Description>
<download>false</download>
</Parameter>
</ParameterSection>
<ParameterSection>
<Name name="local:AXIS_REF">AXIS_REF_PosControl: Input</Name>
<Parameter ParameterId="10000" type="std:USINT">
<Default>32</Default>
<Name name="localStrings:EncoderBitWidth">EncoderBitWidth</Name>
</Parameter>
</ParameterSection>
<ParameterSection>
<Name name="local:AXIS_REF">AXIS_REF_PosControl: Output</Name>
<Parameter ParameterId="10100" type="std:DINT">
<Default>16#7FFF</Default>
<Name name="localStrings:MaximumOutputVelocityInt">MaximumOutputVelocityInt</Name>
</Parameter>
<Parameter ParameterId="10101" type="std:LREAL">
<Default>50.0</Default>
<Name name="localStrings:MaximumOutputVelocityTU">MaximumOutputVelocityTU</Name>
</Parameter>
<Parameter ParameterId="10102" type="std:DINT">
<Default>-16#8000</Default>
<Name name="localStrings:MinimumOutputVelocityInt">MinimumOutputVelocityInt</Name>
</Parameter>
<Parameter ParameterId="10103" type="std:LREAL">
<Default>-50.0</Default>
<Name name="localStrings:MinimumOutputVelocityTU">MinimumOutputVelocityTU</Name>
</Parameter>
<Parameter ParameterId="10104" type="std:DINT">
<Default>0</Default>
<Name name="localStrings:ZeroVelocityInt">ZeroVelocityInt</Name>
</Parameter>
<Parameter ParameterId="10105" type="std:BOOL">
<Default>FALSE</Default>
<Name name="localStrings:DirectionInverted">DirectionInverted</Name>
</Parameter>
</ParameterSection>
<ParameterSection>
<Name name="local:AXIS_REF">AXIS_REF_PosControl: Controller</Name>
<Parameter ParameterId="10200" type="std:LREAL">
<Default>0.05</Default>
<Name name="localStrings:Kp">Kp</Name>
<Description name="localStrings:Kp_Desc">Proportional gain for lag error</Description>
</Parameter>
<Parameter ParameterId="10202" type="std:LREAL">
<Default>0.0</Default>
<Name name="localStrings:MaxFollowingError">MaxFollowingError</Name>
<Description name="localStrings:MaxFollowingErrorDesc">Maximum position lag</Description>
</Parameter>
<Parameter ParameterId="10203" type="std:BOOL">
<Default>FALSE</Default>
<Name name="localStrings:EnableFollowingErrorCheck">EnableFollowingErrorCheck</Name>
<Description name="localStrings:EnableFollowingErrorCheckDesc">Switch on checking of maximum position lag</Description>
</Parameter>
<Parameter ParameterId="10204" type="std:LREAL">
<Default>0.0</Default>
<Name name="localStrings:PartVelPilotControl">PartVelocityPilotControl</Name>
<Description name="localStrings:PartVelPilotControlDesc">Factor for velocity pilot control with fSetVelocity (0 means no velocity pilot control, 1 means direct output of fSetVelocity)</Description>
</Parameter>
<Parameter ParameterId="10205" type="std:LREAL">
<Default>1.0</Default>
<Name name="localStrings:DeadTime">DeadTime</Name>
<Description name="localStrings:DeadTimeDesc">Time lag in cycles between fSetPosition and fActPosition</Description>
</Parameter>
</ParameterSection>
<ParameterSection>
<Name name="local:AXIS_REF">AXIS_REF: Motorsettings</Name>
<Parameter ParameterId="10300" type="std:DWORD">
<Default>6</Default>
<Name name="localStrings:A">A</Name>
<Description name="localStrings:ADesc">MotorPin A</Description>
</Parameter>
<Parameter ParameterId="10301" type="std:DWORD">
<Default>13</Default>
<Name name="localStrings:A-">A-</Name>
<Description name="localStrings:A-Desc">MotorPin A-</Description>
</Parameter>
<Parameter ParameterId="10302" type="std:DWORD">
<Default>19</Default>
<Name name="localStrings:B">B</Name>
<Description name="localStrings:BDesc">MotorPin B</Description>
</Parameter>
<Parameter ParameterId="10303" type="std:DWORD">
<Default>26</Default>
<Name name="localStrings:B-">B-</Name>
<Description name="localStrings:B-Desc">MotorPin B-</Description>
</Parameter>
<Parameter ParameterId="10304" type="std:LREAL">
<Default>0.5</Default>
<Name name="localStrings:B-">MotorPowerTime</Name>
<Description name="localStrings:MotorPowerTimeDesc">fraction of the cycle, where outputs are set High (determines the power of the motor)</Description>
</Parameter>
</ParameterSection>
</HostParameterSet>
<DriverInfo needsBusCycle="false">
<RequiredLib libname="SM3_Drive_RaspiStepper" vendor="3S - Smart Software Solutions GmbH" version="0.0.0.1" identifier="deviceLib">
<FBInstance basename="$(DeviceName)" fbname="AXIS_REF_RASPISTEPPER" fbnamediag="AXIS_REF_RASPISTEPPER">
<Initialize methodName="Initialize" />
<CyclicCall methodname="BeforeReadInputs" task="#buscycletask" whentocall="beforeReadInputs" />
<CyclicCall methodname="AfterReadInputs" task="#buscycletask" whentocall="afterReadInputs" />
<CyclicCall methodname="BeforeWriteOutputs" task="#buscycletask" whentocall="beforeWriteOutputs" />
<CyclicCall methodname="AfterWriteOutputs" task="#buscycletask" whentocall="afterWriteOutputs" />
</FBInstance>
</RequiredLib>
</DriverInfo>
</Connector>
</Device>
</DeviceDescription>
Last updated: 2020-07-15
mcp7941x: ./trunk/License/License.html
Bash
bash
(Bash)
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=iso-8859-1"/>
<title></title>
<meta name="generator" content="LibreOffice 5.4.0.3 (Linux)"/>
<meta name="created" content="00:00:00"/>
<meta name="changed" content="00:00:00"/>
<style type="text/css">
@page { size: 8.5in 11in; margin-left: 1.25in; margin-right: 1.25in; margin-top: 1in; margin-bottom: 1in }
p { margin-bottom: 0.1in; line-height: 120% }
a:link { so-language: zxx }
</style>
</head>
<body lang="en-US" dir="ltr">
<p align="center" style="margin-top: 0.07in; margin-bottom: 0.07in; line-height: 100%; orphans: 2; widows: 2">
<font face="Calibri, serif"><font size="6" style="font-size: 24pt"><span lang="en-US"><b>I2C MCP7941x SRAM Library </b></span></font></font>
</p>
<p align="center" style="margin-top: 0.07in; margin-bottom: 0.07in; line-height: 100%; orphans: 2; widows: 2">
<font face="Calibri, serif"><font size="6" style="font-size: 24pt"><span lang="en-US"><b>for
CODESYS by Aliazzz</b></span></font></font></p>
<p align="center" style="margin-bottom: 0in; font-weight: normal; line-height: 100%; orphans: 2; widows: 2">
<br/>
</p>
<p align="center" style="margin-bottom: 0in; line-height: 100%; orphans: 2; widows: 2">
<font face="Calibri, serif"><font size="3" style="font-size: 12pt"><span lang="en-US"><span style="font-weight: normal">This
is free and unencumbered software released into the public domain.</span></span></font></font></p>
<p align="center" style="margin-bottom: 0in; font-weight: normal; line-height: 100%; orphans: 2; widows: 2">
<br/>
</p>
<p align="center" style="margin-bottom: 0in; line-height: 100%; orphans: 2; widows: 2">
<font face="Calibri, serif"><font size="3" style="font-size: 12pt"><span lang="en-US"><span style="font-weight: normal">Anyone
is free to copy, modify, publish, use, compile, sell, or</span></span></font></font></p>
<p align="center" style="margin-bottom: 0in; line-height: 100%; orphans: 2; widows: 2">
<font face="Calibri, serif"><font size="3" style="font-size: 12pt"><span lang="en-US"><span style="font-weight: normal">distribute
this software, either in source code form or as a compiled</span></span></font></font></p>
<p align="center" style="margin-bottom: 0in; line-height: 100%; orphans: 2; widows: 2">
<font face="Calibri, serif"><font size="3" style="font-size: 12pt"><span lang="en-US"><span style="font-weight: normal">binary,
for any purpose, commercial or non-commercial, and by any</span></span></font></font></p>
<p align="center" style="margin-bottom: 0in; line-height: 100%; orphans: 2; widows: 2">
<font face="Calibri, serif"><font size="3" style="font-size: 12pt"><span lang="en-US"><span style="font-weight: normal">means.</span></span></font></font></p>
<p align="center" style="margin-bottom: 0in; font-weight: normal; line-height: 100%; orphans: 2; widows: 2">
<br/>
</p>
<p align="center" style="margin-bottom: 0in; line-height: 100%; orphans: 2; widows: 2">
<font face="Calibri, serif"><font size="3" style="font-size: 12pt"><span lang="en-US"><span style="font-weight: normal">In
jurisdictions that recognize copyright laws, the author or authors</span></span></font></font></p>
<p align="center" style="margin-bottom: 0in; line-height: 100%; orphans: 2; widows: 2">
<font face="Calibri, serif"><font size="3" style="font-size: 12pt"><span lang="en-US"><span style="font-weight: normal">of
this software dedicate any and all copyright interest in the</span></span></font></font></p>
<p align="center" style="margin-bottom: 0in; line-height: 100%; orphans: 2; widows: 2">
<font face="Calibri, serif"><font size="3" style="font-size: 12pt"><span lang="en-US"><span style="font-weight: normal">software
to the public domain. We make this dedication for the benefit</span></span></font></font></p>
<p align="center" style="margin-bottom: 0in; line-height: 100%; orphans: 2; widows: 2">
<font face="Calibri, serif"><font size="3" style="font-size: 12pt"><span lang="en-US"><span style="font-weight: normal">of
the public at large and to the detriment of our heirs and</span></span></font></font></p>
<p align="center" style="margin-bottom: 0in; line-height: 100%; orphans: 2; widows: 2">
<font face="Calibri, serif"><font size="3" style="font-size: 12pt"><span lang="en-US"><span style="font-weight: normal">successors.
We intend this dedication to be an overt act of</span></span></font></font></p>
<p align="center" style="margin-bottom: 0in; line-height: 100%; orphans: 2; widows: 2">
<font face="Calibri, serif"><font size="3" style="font-size: 12pt"><span lang="en-US"><span style="font-weight: normal">relinquishment
in perpetuity of all present and future rights to this</span></span></font></font></p>
<p align="center" style="margin-bottom: 0in; line-height: 100%; orphans: 2; widows: 2">
<font face="Calibri, serif"><font size="3" style="font-size: 12pt"><span lang="en-US"><span style="font-weight: normal">software
under copyright law.</span></span></font></font></p>
<p align="center" style="margin-bottom: 0in; font-weight: normal; line-height: 100%; orphans: 2; widows: 2">
<br/>
</p>
<p align="center" style="margin-bottom: 0in; line-height: 100%; orphans: 2; widows: 2">
<font face="Calibri, serif"><font size="3" style="font-size: 12pt"><span lang="en-US"><span style="font-weight: normal">THE
SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,</span></span></font></font></p>
<p align="center" style="margin-bottom: 0in; line-height: 100%; orphans: 2; widows: 2">
<font face="Calibri, serif"><font size="3" style="font-size: 12pt"><span lang="en-US"><span style="font-weight: normal">EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF</span></span></font></font></p>
<p align="center" style="margin-bottom: 0in; line-height: 100%; orphans: 2; widows: 2">
<font face="Calibri, serif"><font size="3" style="font-size: 12pt"><span lang="en-US"><span style="font-weight: normal">MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.</span></span></font></font></p>
<p align="center" style="margin-bottom: 0in; line-height: 100%; orphans: 2; widows: 2">
<font face="Calibri, serif"><font size="3" style="font-size: 12pt"><span lang="en-US"><span style="font-weight: normal">IN
NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR</span></span></font></font></p>
<p align="center" style="margin-bottom: 0in; line-height: 100%; orphans: 2; widows: 2">
<font face="Calibri, serif"><font size="3" style="font-size: 12pt"><span lang="en-US"><span style="font-weight: normal">OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,</span></span></font></font></p>
<p align="center" style="margin-bottom: 0in; line-height: 100%; orphans: 2; widows: 2">
<font face="Calibri, serif"><font size="3" style="font-size: 12pt"><span lang="en-US"><span style="font-weight: normal">ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR</span></span></font></font></p>
<p align="center" style="margin-bottom: 0in; line-height: 100%; orphans: 2; widows: 2">
<font face="Calibri, serif"><font size="3" style="font-size: 12pt"><span lang="en-US"><span style="font-weight: normal">OTHER
DEALINGS IN THE SOFTWARE.</span></span></font></font></p>
<p align="center" style="margin-bottom: 0in; font-weight: normal; line-height: 100%; orphans: 2; widows: 2">
<br/>
</p>
<p align="center" style="margin-bottom: 0in; line-height: 100%; orphans: 2; widows: 2">
<font face="Calibri, serif"><font size="3" style="font-size: 12pt"><span lang="en-US"><span style="font-weight: normal">For
more information, please refer to <a href="http://unlicense.org/">http://unlicense.org</a></span></span></font></font></p>
<p align="center" style="margin-bottom: 0in; line-height: 100%; orphans: 2; widows: 2">
<br/>
</p>
</body>
</html>
Last updated: 2019-04-06
sm3raspistepper: ./tags/Release_1.0/devdesc/StandardParameters.xml
Bash
bash
(Bash)
<?xml version="1.0" encoding="utf-8"?>
<ParameterSection>
<Name name="local:AXIS_REF">AXIS_REF: Standard</Name>
<Parameter ParameterId="1021" type="std:WORD">
<Attributes download="true" functional="false" />
<Default>1</Default>
<Name name="x">wDriveID</Name>
<Description name="x">Unique ID of the drive</Description>
<Custom />
</Parameter>
<Parameter ParameterId="1040" type="std:BOOL">
<Attributes download="true" functional="false" />
<Default>FALSE</Default>
<Name name="x">bVirtual</Name>
<Description name="x">Whether the drive is simulated</Description>
<Custom />
</Parameter>
<Parameter ParameterId="1060" type="std:INT">
<Attributes download="true" functional="false" />
<Default>1</Default>
<Name name="x">iMovementType</Name>
<Description name="x">Movement type: 0: modulo, 1: finite</Description>
<Custom />
</Parameter>
<Parameter ParameterId="1061" type="std:LREAL">
<Attributes download="true" functional="false" />
<Default>360.0</Default>
<Name name="x">fPositionPeriod</Name>
<Description name="x">Modulo period value for modulo drives [u]</Description>
<Custom />
</Parameter>
<Parameter ParameterId="1205" type="std:BOOL">
<Attributes download="true" functional="false" />
<Default>FALSE</Default>
<Name name="x">bSWLimitEnable</Name>
<Description name="x">Activate/deactivate software limits</Description>
<Custom />
</Parameter>
<Parameter ParameterId="1201" type="std:LREAL">
<Attributes download="true" functional="false" />
<Default>0.0</Default>
<Name name="x">fSWLimitNegative</Name>
<Description name="x">Software limit in negative direction</Description>
<Custom />
</Parameter>
<Parameter ParameterId="1200" type="std:LREAL">
<Attributes download="true" functional="false" />
<Default>1000.0</Default>
<Name name="x">fSWLimitPositive</Name>
<Description name="x">Software limit in positive direction</Description>
<Custom />
</Parameter>
<Parameter ParameterId="1062" type="std:INT">
<Attributes download="true" functional="false" />
<Default>0</Default>
<Name name="x">eRampType</Name>
<Description name="x">The velocity ramp used for trajectories.</Description>
<Custom />
</Parameter>
<Parameter ParameterId="1144" type="std:LREAL">
<Attributes download="true" functional="false" />
<Default>0</Default>
<Name name="x">fRampJerk</Name>
<Description name="x">Jerk used for bringing acceleration to 0 when sin² ramp is interrupted.</Description>
<Custom />
</Parameter>
<Parameter ParameterId="1113" type="std:LREAL">
<Attributes download="true" functional="false" />
<Default>30</Default>
<Name name="x">fSWMaxVelocity</Name>
<Description name="x">Maximum velocity magnitude (software limit)</Description>
<Custom />
</Parameter>
<Parameter ParameterId="1123" type="std:LREAL">
<Attributes download="true" functional="false" />
<Default>1000</Default>
<Name name="x">fSWMaxAcceleration</Name>
<Description name="x">Maximum acceleration magnitude (software limit)</Description>
<Custom />
</Parameter>
<Parameter ParameterId="1133" type="std:LREAL">
<Attributes download="true" functional="false" />
<Default>1000</Default>
<Name name="x">fSWMaxDeceleration</Name>
<Description name="x">Maximum deceleration magnitude (software limit)</Description>
<Custom />
</Parameter>
<Parameter ParameterId="1143" type="std:LREAL">
<Attributes download="true" functional="false" />
<Default>10000</Default>
<Name name="x">fSWMaxJerk</Name>
<Description name="x">Maximum jerk magnitude (software limit)</Description>
<Custom />
</Parameter>
<Parameter ParameterId="1207" type="std:INT">
<Attributes download="true" functional="false" />
<Default>0</Default>
<Name name="x">eCheckPositionLag</Name>
<Description name="x">Position lag mode</Description>
<Custom />
</Parameter>
<Parameter ParameterId="1208" type="std:LREAL">
<Attributes download="true" functional="false" />
<Default>1.0</Default>
<Name name="x">fMaxPositionLag</Name>
<Description name="x">Maximum position lag (magnitude) [u]</Description>
<Custom />
</Parameter>
<Parameter ParameterId="1203" type="std:LREAL">
<Attributes download="true" functional="false" />
<Default>0</Default>
<Name name="x">fSWLimitDeceleration</Name>
<Description name="x">Deceleration for stop on software error [u/s²]</Description>
<Custom />
</Parameter>
<Parameter ParameterId="1250" type="std:LREAL">
<Attributes download="true" functional="false" />
<Default>0</Default>
<Name name="x">fSWErrorMaxDistance</Name>
<Description name="x">Maximum distance that may be travelled for ramping down after a software error has been detected [u]</Description>
<Custom />
</Parameter>
</ParameterSection>
Last updated: 2020-07-15
mcp7941x: ./trunk/devdesc/i2c_mcp7941x.devdesc.xml
Bash
bash
(Bash)
<?xml version="1.0" encoding="utf-8"?>
<DeviceDescription xmlns="http://www.3s-software.com/schemas/DeviceDescription-1.0.xsd" xmlns:ts="http://www.3s-software.com/schemas/TargetSettings-0.1.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Types namespace="local"></Types>
<Strings namespace="local">
<Language lang="en"></Language>
</Strings>
<Files namespace="local">
<Language lang="en">
<File fileref="local" identifier="ImageFile">
<LocalFile>MCP7941xRTC.png</LocalFile>
</File>
<File fileref="local" identifier="Icon">
<LocalFile>icon.ico</LocalFile>
</File>
</Language>
</Files>
<Device hideInCatalogue="false">
<DeviceIdentification>
<Type>500</Type>
<Id>0003 0009</Id>
<Version>0.0.2.1</Version>
</DeviceIdentification>
<DeviceInfo>
<Name name="local:ModelName">MCP7941x</Name>
<Description name="local:DeviceDescription">Library containing support for storing 64 bytes of retain data in MCP7941x RTC chip</Description>
<Vendor name="local:VendorName">Open Source</Vendor>
<OrderNumber>-</OrderNumber>
<Image name="local:ImageFile">MCP7941xRTC.png</Image>
<Icon name="local:Icon">icon.ico</Icon>
</DeviceInfo>
<Connector moduleType="500" interface="Raspberry.I2C" role="child" explicit="false" connectorId="1" hostpath="-1">
<InterfaceName name="local:I2C">I2C devices</InterfaceName>
<Slot count="1" allowEmpty="false"></Slot>
<DriverInfo needsBusCycle="false">
<RequiredLib libname="MCP7941x" vendor="Open Source" version="0.0.2.1" identifier="MCP7941x">
<FBInstance basename="$(DeviceName)" fbname="MCP7941x">
<Initialize methodName="Initialize" />
<CyclicCall methodname="AfterReadInputs" task="#buscycletask" whentocall="afterReadInputs" />
<CyclicCall methodname="BeforeWriteOutputs" task="#buscycletask" whentocall="beforeWriteOutputs" />
</FBInstance>
</RequiredLib>
</DriverInfo>
<HostParameterSet>
<Parameter ParameterId="1000" type="std:USINT">
<Attributes channel="none" download="true" functional="false" onlineaccess="read" />
<Default>16#6F</Default>
<Name name="local:I2CAddress">I2C Address</Name>
<Description name="local:I2CAddressDesc">Address of the device</Description>
</Parameter>
</HostParameterSet>
</Connector>
</Device>
</DeviceDescription>
Last updated: 2019-04-06
mcp7941x: ./trunk/license.txt
Bash
bash
(Bash)
I2C MCP7941x SRAM Library
for CODESYS by Aliazzz
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to http://unlicense.org
Last updated: 2019-04-06
mcp7941x: ./trunk/package.manifest
Bash
bash
(Bash)
<?xml version="1.0" encoding="ISO-8859-1"?>
<Package>
<Strings>
<String Id="GeneralName">
<Neutral>I2C MCP7941x SRAM Library v0.0.2.1</Neutral>
</String>
<String Id="GeneralVendor">
<Neutral>Open Source Software</Neutral>
</String>
<String Id="GeneralCopyright">
<Neutral>all rights reserved</Neutral>
</String>
<String Id="GeneralDescription">
<Neutral>Library containing support for battery backed SRAM support provided by realtime clock MCP7941x.</Neutral>
</String>
<String Id="license">
<Neutral>LICENSE.TXT</Neutral>
</String>
<String Id="TargetDir">
<Neutral>Target directory</Neutral>
</String>
<String Id="TargetDirDescription">
<Neutral>The target directory where the projects are saved.</Neutral>
</String>
</Strings>
<General>
<Id>{86c13b19-5871-11e9-a52d-7fefdc8c4c90}</Id>
<Version>1.0.0.0</Version>
<Name>$GeneralName</Name>
<Vendor>$GeneralVendor</Vendor>
<Copyright>$GeneralCopyright</Copyright>
<Description>$GeneralDescription</Description>
<LicenseAgreement>$license</LicenseAgreement>
<RequiredInstallerVersion>3.5.10.0</RequiredInstallerVersion>
</General>
<TargetDirectoryDefinitions>
<TargetDirectoryDefinition>
<Id>1</Id>
<Name>$TargetDir</Name>
<Description>$TargetDirDescription</Description>
<PromptUser>True</PromptUser>
<DefaultValue>%USERPROFILE%\CODESYS Projects\</DefaultValue>
</TargetDirectoryDefinition>
</TargetDirectoryDefinitions>
<Components>
<Component>
<General>
<Id>1</Id>
<Name>Default</Name>
<Description>Default Package</Description>
<Selectable>false</Selectable>
<SelectedByDefault>true</SelectedByDefault>
</General>
<Dependencies>
</Dependencies>
<Items>
</Items>
</Component>
</Components>
</Package>
Last updated: 2019-04-06
wharfie: ./.drone.yml
Bash
bash
(Bash)
kind: pipeline
name: default
workspace:
base: /working/repo
steps:
- name: compile
image: codesys-ide:local
commands:
- echo "building debian package"
- ./trunk/wharfie/deb.sh
- mkdir -p .drone-artifacts
- cp trunk/wharfie/wharfie.deb .drone-artifacts/
Last updated: 2020-02-07
wharfie: ./trunk/wharfie/install.sh
Bash
bash
(Bash)
#!/bin/sh
INSTALL_PREFIX=${PREFIX:-/usr/local/}
SRCDIR=$(dirname $(realpath $0))
mkdir -p ${INSTALL_PREFIX}/bin/
cp ${SRCDIR}/wharfie.sh ${INSTALL_PREFIX}/bin/wharfie
mkdir -p ${INSTALL_PREFIX}/share/wharfie/
cp ${SRCDIR}/wharfie.py ${INSTALL_PREFIX}/share/wharfie/wharfie.py
cp ${SRCDIR}/wharfie.mk ${INSTALL_PREFIX}/share/wharfie/wharfie.mk
cp -R ${SRCDIR}/lib ${INSTALL_PREFIX}/share/wharfie/
Last updated: 2020-02-06
wharfie: ./trunk/wharfie/deb.sh
Bash
bash
(Bash)
#!/bin/bash
SRCDIR=$(dirname $(realpath $0))
export PREFIX=/tmp/wharfie
rm -Rf /tmp/wharfie
cp -R ${SRCDIR}/package /tmp/wharfie
${SRCDIR}/install.sh
chmod -R 755 ${PREFIX}
dpkg-deb --build ${PREFIX}
cp $(dirname ${PREFIX})/$(basename ${PREFIX}).deb ${SRCDIR}/
Last updated: 2020-02-07
tubetutor: ./trunk/tubetutor/tubetutor.py
Bash
bash
(Bash)
import os
import time
import tkinter
import ffmpeg
import atexit
import platform
if platform.system() == "Linux":
if os.geteuid() == 0:
import keyboard
keyboard_loaded=True
else:
print("Warning: Hotkeys only work as root")
keyboard_loaded=False
else:
import keyboard
keyboard_loaded=True
import shutil
import glob
path="videos"
#resolution="3840:2160"
resolution="1920:1080"
def id(filename):
id = os.path.basename(filename).split(".")[0]
return id
def text_changed(event):
global path
filename=str(event.widget).split(".")[-1] + ".txt"
content=event.widget.get("1.0", tkinter.END)
f = open(os.path.join(path, "working", filename), "w")
if f:
f.write(content)
f.close()
class TubeTutorWin:
files=[]
frames=[]
buttons={}
labels={}
images={}
canvases={}
textfields={}
lasttextfield=""
master=None
frame=None
lastid=0
keyboardid=None
hotkey_stop = False
def __init__(self, master, path):
global keyboard_loaded
self.master = master
self.path = path
master.title("TubeTutor - Create easy video tutorials")
self.refresh_files()
self.refresh_display()
self.textfields[self.lasttextfield].focus_set()
self.master.after(1000, self.poll)
if keyboard_loaded:
keyboard.add_hotkey('ctrl+alt+r', self.hotkey)
def poll(self):
# change color of record button, if ffmpeg is running
try:
if not self.orig_buttoncolor:
self.orig_buttoncolor = self.buttons["Record"].cget("background")
except:
pass
if ffmpeg.is_recording():
try:
self.buttons["Record"].configure(bg="red")
except:
pass
else:
try:
self.buttons["Record"].configure(bg=self.orig_buttoncolor)
except:
pass
# process record hotkey
if self.hotkey_stop:
print("hotkey polled")
self.hotkey_stop = False
self.refresh_files()
self.refresh_display()
self.textfields[self.lasttextfield].focus_force()
self.master.after(1000, self.poll)
def hotkey(self):
global resolution
print("Hotkey pressed")
if ffmpeg.is_recording():
time.sleep(3)
ffmpeg.stop()
self.hotkey_stop = True
else:
ffmpeg.record(self.get_filename())
def get_filename(self, extension="mkv"):
global path
self.lastid += 10
basename = "%04d.%s" % (self.lastid, extension)
return os.path.join(path, "working", basename)
def set_filename(self, filename):
base = filename.split(".")[0]
self.lastid = int(base)
def get_title(self):
title=self.textfields["title"].get("1.0", tkinter.END)
return title
def refresh_files(self):
self.files = []
for root, dirs, files in os.walk(os.path.join(self.path, "working")):
for file in files:
if file.endswith(".mkv") or file.endswith(".txt"):
self.files.append(os.path.join(root, file))
self.files.sort()
# Output the main window
def refresh_display(self):
# remove all widgets
for w in self.buttons:
self.buttons[w].pack_forget()
self.buttons = {}
for w in self.frames:
w.pack_forget()
self.frames = []
for w in self.labels:
self.labels[w].pack_forget()
self.labels = {}
for w in self.textfields:
self.textfields[w].pack_forget()
self.textfields = {}
for w in self.canvases:
self.canvases[w].pack_forget()
self.images = {}
self.canvases = {}
self.lastid=0
# re-add all widgets
if keyboard_loaded:
self.add_label("description", text="Press <Ctrl+Alt+R> to start or end recording.")
else:
self.add_label("description", text="Hotkey <Ctrl+Alt+R> only works when started as root!!!")
# Title of the video
self.start_frame()
self.add_label("title", "Title:", side=tkinter.LEFT)
self.add_text("title", "Video Tutorial", width=30, height=1, autosave=False, side=tkinter.LEFT)
self.add_button("Render", self.render, side=tkinter.LEFT)
self.add_button("Stash", self.stash, side=tkinter.LEFT)
self.end_frame()
# Recording control panel
self.start_frame()
self.add_button("Record", self.record, side=tkinter.LEFT)
self.add_button("Stop", self.stop, side=tkinter.LEFT)
self.end_frame()
file = self.get_filename("txt")
content=""
try:
f = open(file, "r")
if f:
content = f.read()
f.close()
except:
pass
self.add_text(id(file), content);
for file in self.files:
if os.path.basename(file).startswith("tmp_"):
continue
if file.endswith(".mkv"):
self.start_frame()
moviefile=file
self.add_label(id(file) + "_image", image=os.path.join("media", "movie.png"), side=tkinter.LEFT)
self.add_label(id(file), text=file, side=tkinter.LEFT)
self.add_button("del", lambda: self.delete(moviefile), side=tkinter.LEFT)
self.end_frame()
self.set_filename(os.path.basename(file))
file = self.get_filename("txt")
content=""
try:
f = open(file, "r")
if f:
content = f.read()
f.close()
except:
pass
self.add_text(id(file), content);
def add_image(self, name, filename, width=32, height=32, side=tkinter.TOP, fill=tkinter.NONE, expand=False):
self.images[name] = tkinter.PhotoImage(master=self.root(), file=filename)
self.canvases[name] = tkinter.Canvas(self.root(), width=width, height=height)
self.canvases[name].create_image(0, 0, image=self.images[name])
self.canvases[name].pack(side=side, fill=fill, expand=expand)
def add_label(self, name, text="", image="", width=120, side=tkinter.TOP, fill=tkinter.NONE, expand=False):
if image != "":
self.images[name] = tkinter.PhotoImage(master=self.root(), file=image)
self.labels[name] = tkinter.Label(self.root(), width=width, text=text, image=self.images[name])
else:
self.labels[name] = tkinter.Label(self.root(), text=text)
self.labels[name].pack(side=side, fill=fill, expand=expand)
def add_text(self, name, content="", autosave=True, width=50, height=2, side=tkinter.TOP, fill=tkinter.NONE, expand=False):
self.textfields[name] = tkinter.Text(self.root(), name=name, width=width, height=height)
if content != "":
self.textfields[name].insert(tkinter.INSERT, content)
self.textfields[name].pack(side=side, fill=fill, expand=expand)
if autosave:
self.textfields[name].bind("<KeyRelease>", text_changed)
self.lasttextfield = name
def add_button(self, name, fn, side=tkinter.TOP, fill=tkinter.NONE, expand=False):
self.buttons[name] = tkinter.Button(self.root(), text=name, command=fn)
self.buttons[name].pack(side=side, fill=fill, expand=expand)
def start_frame(self, side=tkinter.TOP, fill=tkinter.NONE, expand=False):
self.frame = tkinter.Frame(self.master)
self.frame.pack(side=side, fill=fill, expand=expand)
self.frames.append(self.frame)
def end_frame(self):
self.frame = None
def root(self):
if self.frame:
return self.frame
else:
return self.master
def record(self):
ffmpeg.record(self.get_filename())
def stop(self):
time.sleep(3)
ffmpeg.stop()
self.refresh_files()
self.refresh_display()
def render(self):
global resolution
self.refresh_files()
videos=[]
description=""
if os.path.exists(os.path.join("media", "intro.mkv")):
# intro
r = ffmpeg.renderer()
r.resolution(resolution)
r.filename(os.path.join("media", "intro.mkv"))
params = " -scale %s!" % resolution.replace(":", "x")
params += " -font %s -weight 500 -pointsize 100" % ffmpeg.font
params += " -draw \"gravity northwest fill white text 100,775 '%s'\"" % self.get_title()
params += " -scale %s!" % resolution.replace(":", "x")
ffmpeg.convert_image(os.path.join("media", "intro.jpg"), os.path.join(path, "tmp", "intro.jpg"), params)
r.add_image(os.path.join(path, "tmp", "intro.jpg"), fadein=False)
r.process(os.path.join(path, "tmp", "intro-scale.mkv"), "-vf scale=" + resolution)
r.concat(os.path.join(path, "tmp", "intro-concat.mkv"))
# mux intro
r = ffmpeg.renderer()
r.resolution(resolution)
r.filename(os.path.join(path, "tmp", "intro-concat.mkv"))
r.mux(os.path.join(path, "tmp", "intro.mkv"), os.path.join("media", "intro.mp3"))
videos.append(r.lastfilename)
r = ffmpeg.renderer()
r.resolution(resolution)
else:
params = " -scale %s!" % resolution.replace(":", "x")
params += " -font %s -weight 500 -pointsize 100" % ffmpeg.font
params += " -draw \"gravity northwest fill white text 100,775 '%s'\"" % self.get_title()
params += " -scale %s!" % resolution.replace(":", "x")
ffmpeg.convert_image(os.path.join("media", "intro.jpg"), os.path.join(path, "tmp", "intro.jpg"), params)
r = ffmpeg.renderer()
r.resolution(resolution)
r.add_image(os.path.join("media", "intro.png"))
# main
#r = ffmpeg.renderer()
#r.resolution(resolution)
for file in self.files:
if os.path.basename(file).startswith("tmp_"):
continue
if file.endswith(".txt"):
description += "- %s" % r.add_text(file)
if file.endswith(".mkv"):
r.add_video(file)
r.concat(os.path.join(path, "tmp", "concat.mkv"))
r.speedup(os.path.join(path, "tmp", "speedup.mkv"), 0.7)
r.mux(os.path.join(path, "tmp", "main.mkv"), os.path.join("media", "back.mp3"))
videos.append(r.lastfilename)
# outro
if os.path.exists(os.path.join("media", "outro.mkv")):
r = ffmpeg.renderer()
r.resolution(resolution)
r.filename(os.path.join("media", "outro.mkv"))
r.process(os.path.join(path, "tmp", "outro.mkv"), "-vf scale=" + resolution)
videos.append(r.lastfilename)
# mastering
outputfilename=self.get_title().replace(" ", "-").replace(".", "_").replace("\n", "") + ".mkv"
r = ffmpeg.renderer()
r.resolution(resolution)
for file in videos:
r.add_video(file)
r.concat(os.path.join(path, outputfilename), audio=True)
# write description
outputfilename=self.get_title().replace(" ", "-").replace(".", "_").replace("\n", "") + ".txt"
f = open(os.path.join("media", "legal.txt"), "r")
if f:
description += f.read()
f.close()
f = open(os.path.join(path, outputfilename), "w")
if f:
f.write(description)
f.close()
# copy thumbnail
outputfilename=self.get_title().replace(" ", "-").replace(".", "_").replace("\n", "") + ".jpg"
shutil.copy(os.path.join(path, "tmp", "intro.jpg"), os.path.join(path, outputfilename))
def delete(self, filename):
os.remove(filename)
self.refresh_files()
self.refresh_display()
def stash(self):
dirname=self.get_title().replace(" ", "-").replace(".", "_").replace("\n", "")
if not os.path.exists(os.path.join(path, dirname)):
os.makedirs(os.path.join(path, dirname))
for filename in glob.glob(os.path.join(path, "working", "*")):
shutil.move(filename, os.path.join(path, dirname))
self.refresh_files()
self.refresh_display()
def quit(self, event):
ffmpeg.stop()
root = tkinter.Tk()
win = TubeTutorWin(root, path)
root.bind("<Destroy>", win.quit)
root.mainloop()
Last updated: 2019-09-21
sm3raspistepper: ./trunk/devdesc/SM3_Drive_PosControl.devdesc.xml
Bash
bash
(Bash)
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE DeviceDescription [
<!ENTITY standardparameters SYSTEM "StandardParameters.xml">
]>
<!-- edited with XMLSPY v2004 rel. 4 U (http://www.xmlspy.com) by Hilmar Panzer (3S - Smart Software Solutions GmbH) -->
<DeviceDescription xmlns="http://www.3s-software.com/schemas/DeviceDescription-1.0.xsd" xmlns:ts="http://www.3s-software.com/schemas/TargetSettings-0.1.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.3s-software.com/schemas/DeviceDescription-1.0.xsd D:\CoDeSys\Documentation\DOCUME~2.)\PRODUC~1\CODESY~1.0\DEVELO~1\FEATUR~1\DeviceDescription-1.0.xsd">
<Types namespace="localTypes"></Types>
<Strings namespace="localStrings">
</Strings>
<Files namespace="local">
<Language lang="en">
<File fileref="local" identifier="DriveIcon">
<LocalFile>SoftMotion_PosControl_Small_Stepper.ico</LocalFile>
</File>
</Language>
</Files>
<Device>
<DeviceIdentification>
<Type>1025</Type>
<Id>FFFF 0001</Id>
<Version>0.0.0.1</Version>
</DeviceIdentification>
<CompatibleVersions>
</CompatibleVersions>
<DeviceInfo>
<Name name="localStrings:TypeName">SM_Drive_RaspiStepper</Name>
<Description name="localStrings:TypeDescription">SoftMotion position controlled drive PTt</Description>
<Vendor name="localStrings:_3S">3S - Smart Software Solutions GmbH</Vendor>
<OrderNumber>1805</OrderNumber>
<Icon name="local:DriveIcon">SoftMotion_PosControl_Small_Stepper.ico</Icon>
</DeviceInfo>
<Connector connectorId="1" moduleType="1248" interface="Common.SoftMotion.Logical" role="parent" explicit="false" hostpath="0" initialStatusFlag="241">
<InterfaceName name="local:InterfaceName">SM_Drive_Logical:</InterfaceName>
<Var />
</Connector>
<Connector role="child" connectorId="0" interface="Common.SoftMotion.General" moduleType="1025" explicit="false">
<InterfaceName name="localStrings:InterfaceName">SM_Drive_PosControl:</InterfaceName>
<Slot count="1" allowEmpty="false" />
<Var />
<HostParameterSet>
&standardparameters;
<ParameterSection>
<Name name="local:AXIS_REF">AXIS_REF: Scalings</Name>
<Parameter ParameterId="1051" type="std:DWORD">
<Attributes download="true" functional="false" />
<Default>1</Default>
<Name name="localStrings:AXIS_REF.dwRatioTechUnitsDenom">dwRatioTechUnitsDenom</Name>
<Description name="localStrings:AXIS_REF.dwRatioTechUnitsDenom.Desc">conversion inc./tech.units denominator</Description>
<Custom />
</Parameter>
<Parameter ParameterId="1052" type="std:DINT">
<Attributes download="true" functional="false" />
<Default>1</Default>
<Name name="localStrings:AXIS_REF.iRatioTechUnitsNum">iRatioTechUnitsNum</Name>
<Description name="localStrings:AXIS_REF.iRatioTechUnitsNum.Desc">conversion inc./tech.units numerator</Description>
<Custom />
</Parameter>
<Parameter ParameterId="5000" type="std:DINT">
<Default>1</Default>
<Name name="localStrings:ScalingIncs">ScalingIncs</Name>
<download>false</download>
</Parameter>
<Parameter ParameterId="5001" type="std:DINT">
<Default>1</Default>
<Name name="localStrings:ScalingMotorTurns1">ScalingMotorTurns1</Name>
<download>false</download>
</Parameter>
<Parameter ParameterId="5002" type="std:DINT">
<Default>1</Default>
<Name name="localStrings:ScalingMotorTurns2">ScalingMotorTurns2</Name>
<download>false</download>
</Parameter>
<Parameter ParameterId="5003" type="std:DINT">
<Default>1</Default>
<Name name="localStrings:ScalingGearOutput1">ScalingGearOutput1</Name>
<download>false</download>
</Parameter>
<Parameter ParameterId="5004" type="std:DINT">
<Default>1</Default>
<Name name="localStrings:ScalingGearOutput2">ScalingGearOutput2</Name>
<download>false</download>
</Parameter>
<Parameter ParameterId="5005" type="std:DINT">
<Default>1</Default>
<Name name="localStrings:ScalingUnits">ScalingUnits</Name>
<download>false</download>
</Parameter>
<Parameter ParameterId="5006" type="std:BOOL">
<Default>FALSE</Default>
<Name name="localStrings:InvertDirection">InvertDirection</Name>
<download>false</download>
</Parameter>
<Parameter ParameterId="5007" type="std:BOOL">
<Default>FALSE</Default>
<Name name="localStrings:HalfStep">HalfStep</Name>
<Description name="Motor takes half steps instead of full steps">Motor takes half steps instead of full steps</Description>
<download>false</download>
</Parameter>
</ParameterSection>
<ParameterSection>
<Name name="local:AXIS_REF">AXIS_REF_PosControl: Input</Name>
<Parameter ParameterId="10000" type="std:USINT">
<Default>32</Default>
<Name name="localStrings:EncoderBitWidth">EncoderBitWidth</Name>
</Parameter>
</ParameterSection>
<ParameterSection>
<Name name="local:AXIS_REF">AXIS_REF_PosControl: Output</Name>
<Parameter ParameterId="10100" type="std:DINT">
<Default>16#7FFF</Default>
<Name name="localStrings:MaximumOutputVelocityInt">MaximumOutputVelocityInt</Name>
</Parameter>
<Parameter ParameterId="10101" type="std:LREAL">
<Default>50.0</Default>
<Name name="localStrings:MaximumOutputVelocityTU">MaximumOutputVelocityTU</Name>
</Parameter>
<Parameter ParameterId="10102" type="std:DINT">
<Default>-16#8000</Default>
<Name name="localStrings:MinimumOutputVelocityInt">MinimumOutputVelocityInt</Name>
</Parameter>
<Parameter ParameterId="10103" type="std:LREAL">
<Default>-50.0</Default>
<Name name="localStrings:MinimumOutputVelocityTU">MinimumOutputVelocityTU</Name>
</Parameter>
<Parameter ParameterId="10104" type="std:DINT">
<Default>0</Default>
<Name name="localStrings:ZeroVelocityInt">ZeroVelocityInt</Name>
</Parameter>
<Parameter ParameterId="10105" type="std:BOOL">
<Default>FALSE</Default>
<Name name="localStrings:DirectionInverted">DirectionInverted</Name>
</Parameter>
</ParameterSection>
<ParameterSection>
<Name name="local:AXIS_REF">AXIS_REF_PosControl: Controller</Name>
<Parameter ParameterId="10200" type="std:LREAL">
<Default>0.05</Default>
<Name name="localStrings:Kp">Kp</Name>
<Description name="localStrings:Kp_Desc">Proportional gain for lag error</Description>
</Parameter>
<Parameter ParameterId="10202" type="std:LREAL">
<Default>0.0</Default>
<Name name="localStrings:MaxFollowingError">MaxFollowingError</Name>
<Description name="localStrings:MaxFollowingErrorDesc">Maximum position lag</Description>
</Parameter>
<Parameter ParameterId="10203" type="std:BOOL">
<Default>FALSE</Default>
<Name name="localStrings:EnableFollowingErrorCheck">EnableFollowingErrorCheck</Name>
<Description name="localStrings:EnableFollowingErrorCheckDesc">Switch on checking of maximum position lag</Description>
</Parameter>
<Parameter ParameterId="10204" type="std:LREAL">
<Default>0.0</Default>
<Name name="localStrings:PartVelPilotControl">PartVelocityPilotControl</Name>
<Description name="localStrings:PartVelPilotControlDesc">Factor for velocity pilot control with fSetVelocity (0 means no velocity pilot control, 1 means direct output of fSetVelocity)</Description>
</Parameter>
<Parameter ParameterId="10205" type="std:LREAL">
<Default>1.0</Default>
<Name name="localStrings:DeadTime">DeadTime</Name>
<Description name="localStrings:DeadTimeDesc">Time lag in cycles between fSetPosition and fActPosition</Description>
</Parameter>
</ParameterSection>
<ParameterSection>
<Name name="local:AXIS_REF">AXIS_REF: Motorsettings</Name>
<Parameter ParameterId="10300" type="std:DWORD">
<Default>6</Default>
<Name name="localStrings:A">A</Name>
<Description name="localStrings:ADesc">MotorPin A</Description>
</Parameter>
<Parameter ParameterId="10301" type="std:DWORD">
<Default>13</Default>
<Name name="localStrings:A-">A-</Name>
<Description name="localStrings:A-Desc">MotorPin A-</Description>
</Parameter>
<Parameter ParameterId="10302" type="std:DWORD">
<Default>19</Default>
<Name name="localStrings:B">B</Name>
<Description name="localStrings:BDesc">MotorPin B</Description>
</Parameter>
<Parameter ParameterId="10303" type="std:DWORD">
<Default>26</Default>
<Name name="localStrings:B-">B-</Name>
<Description name="localStrings:B-Desc">MotorPin B-</Description>
</Parameter>
<Parameter ParameterId="10304" type="std:LREAL">
<Default>0.5</Default>
<Name name="localStrings:B-">MotorPowerTime</Name>
<Description name="localStrings:MotorPowerTimeDesc">fraction of the cycle, where outputs are set High (determines the power of the motor)</Description>
</Parameter>
</ParameterSection>
</HostParameterSet>
<DriverInfo needsBusCycle="false">
<RequiredLib libname="SM3_Drive_RaspiStepper" vendor="3S - Smart Software Solutions GmbH" version="0.0.0.1" identifier="deviceLib">
<FBInstance basename="$(DeviceName)" fbname="AXIS_REF_RASPISTEPPER" fbnamediag="AXIS_REF_RASPISTEPPER">
<Initialize methodName="Initialize" />
<CyclicCall methodname="BeforeReadInputs" task="#buscycletask" whentocall="beforeReadInputs" />
<CyclicCall methodname="AfterReadInputs" task="#buscycletask" whentocall="afterReadInputs" />
<CyclicCall methodname="BeforeWriteOutputs" task="#buscycletask" whentocall="beforeWriteOutputs" />
<CyclicCall methodname="AfterWriteOutputs" task="#buscycletask" whentocall="afterWriteOutputs" />
</FBInstance>
</RequiredLib>
</DriverInfo>
</Connector>
</Device>
</DeviceDescription>
Last updated: 2020-07-15
tubetutor: ./trunk/tubetutor/ffmpeg.py
Bash
bash
(Bash)
#
# Wrapper for the ffmpeg utilities
#
# Default search path is relatively to the main script that is called:
# <script path>/ffmpeg
#
# Otherwise, the windows search path is used
#
import subprocess
import platform
import ctypes
import signal
import sys
import os
import re
#font="Arial"
font=os.path.join("media", "Roboto-Regular.ttf")
proc_ffmpeg=None
if platform.system() == "Linux":
grabber="-video_size 1920x1080 -f x11grab -framerate 20 -i :0.0"
else:
grabber="-f gdigrab -framerate 20 -i desktop"
#
# The following helper functions will retrieve the pathnames for the ffmpeg tools
#
def get_cmd_ffmpeg():
subdir=os.path.join(os.path.dirname(sys.argv[0]), "ffmpeg")
if os.path.exists(subdir):
return os.path.join(subdir, "ffmpeg.exe")
return "ffmpeg"
def get_cmd_ffprobe():
subdir=os.path.join(os.path.dirname(sys.argv[0]), "ffmpeg")
if os.path.exists(subdir):
return os.path.join(subdir, "ffprobe.exe")
return "ffprobe"
def get_cmd_convert():
subdir=os.path.join(os.path.dirname(sys.argv[0]), "imagemagick")
if os.path.exists(subdir):
return os.path.join(subdir, "convert.exe")
return "convert"
def is_recording():
return proc_ffmpeg != None
def convert_image(input, output, params):
global font
cmd = get_cmd_convert()
cmd += " " + input
cmd += " " + params
cmd += " " + output
print ("====================================================")
print ("cmd: %s" % cmd)
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True)
(out, error) = p.communicate()
print("output:\n%s" % out)
print("error:\n%s" % error)
def start(filename, params):
global proc_ffmpeg
# Kill remaining ffmpeg instances
stop()
# execute ffmpeg cmd
cmd = get_cmd_ffmpeg()
cmd += " " + params
cmd += " " + filename
print ("====================================================")
print ("cmd: %s" % cmd)
if platform.system() == "Linux":
proc_ffmpeg = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True, preexec_fn=os.setsid)
else:
proc_ffmpeg = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True)
def record(filename, resolution=""):
if resolution != "":
start(filename, " %s -y -vf scale=%s -v:q 1" % (grabber, resolution))
else:
start(filename, " %s -y -v:q 1" % (grabber))
def stop():
global proc_ffmpeg
# Kill remaining ffmpeg instances
if proc_ffmpeg:
try:
ctypes.windll.kernel32.TerminateProcess(int(proc_ffmpeg._handle), -1)
except:
os.killpg(os.getpgid(proc_ffmpeg.pid), signal.SIGTERM)
proc_ffmpeg.kill()
proc_ffmpeg=None
def video_duration(filename):
cmd = get_cmd_ffprobe()
cmd += " -i %s" % filename
cmd += " -show_entries format=duration"
print ("====================================================")
print ("cmd: %s" % cmd)
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True)
(output, error) = p.communicate()
print("duration output: %s - %s" % (output, error))
durations = re.findall(r'duration=[0-9.]*', str(output))
for d in durations:
duration = d.split("=")[1]
print("duration string: %s" % duration)
print("duration: %f" % float(duration))
return float(duration)
def wait():
global proc_ffmpeg
if proc_ffmpeg:
(out, error) = proc_ffmpeg.communicate()
print("output:\n%s" % out)
print("error:\n%s" % error)
proc_ffmpeg = None
class renderer:
pconcat=""
nconcat=0
vidfilter=[]
vidresolution=""
lastfilename=""
tmppath=""
framerate=20
qfilter=" -v:q 1 "
def __init__(self):
self.tmppath = os.path.join("videos", "tmp")
self.framerate = 20
def add_text(self, filename, duration=5):
f = open(filename, "r")
if f:
content = f.read()
f.close()
if content.replace("\n", "") == "":
return
params = " -scale %s!" % self.vidresolution.replace(":", "x")
params += " -font %s -weight 500 -pointsize 75" % font
params += " -draw \"gravity northwest fill white text 100,650 '%s'\"" % content
params += " -scale %s!" % self.vidresolution.replace(":", "x")
image = os.path.splitext(filename)[0] + ".png"
convert_image(os.path.join("media", "back.png"), image, params)
self.add_image(image, duration)
return content
def add_image(self, filename, duration=5, fadein=True):
print(self.tmppath)
tmpimage = os.path.join(self.tmppath, os.path.basename(filename) + ".png")
tmpfilename = os.path.join(self.tmppath, os.path.basename(filename) + ".mkv")
params = " -scale %s!" % self.vidresolution.replace(":", "x")
convert_image(filename, tmpimage, params)
params = " -y -loop 1 -t %d -i %s" % (duration, tmpimage)
if fadein:
params += " -filter_complex \"fade=in:0:d=1[v];[v]fade=out:st=%f:d=2\"" % ((duration-2))
else:
params += " -filter_complex \"fade=out:st=%f:d=1\"" % ((duration-3))
params += self.qfilter
start(tmpfilename, params)
wait()
self.pconcat += " -i %s" % (tmpfilename)
self.nconcat+=1
def add_video(self, filename):
# fade video temporarily
tmpfilename = os.path.join(self.tmppath, "add_" + os.path.basename(filename))
(w,h) = self.vidresolution.split(":")
params = " -y -i %s -filter_complex \"scale=-1:%s[v];[v]crop=%s:0:0[v];[v]fade=in:0:10\"" % (filename, h, self.vidresolution)
params += self.qfilter
start(tmpfilename, params)
wait()
self.pconcat += " -i %s" % (tmpfilename)
self.nconcat+=1
def speedup(self, filename, pts=0.7):
params = " -i %s -y -filter:v \"setpts=%f*PTS\"" % (self.lastfilename, pts)
params += self.qfilter
self.lastfilename = filename
start(filename, params)
wait()
def mux(self, filename, audiofile):
duration = video_duration(self.lastfilename)
params = " -i %s -i %s -y" % (self.lastfilename, audiofile)
params += " -t %d" % (int(duration)+1)
params += " -af \"afade=out:st=%f:d=3\"" % (duration-3.0)
params += " -map 0:v:0 -map 1:a:0"
params += self.qfilter
self.lastfilename = filename
start(filename, params)
wait()
def filter(self, filter):
self.vidfilter = filter
def resolution(self, resolution):
self.vidresolution = resolution
def concat(self, filename, audio=False):
if self.lastfilename != "":
self.nconcat += 1
self.pconcat = "-i %s %s " % (self.lastfilename, self.pconcat)
if audio:
a=1
else:
a=0
if len(self.vidfilter) > 0:
params = self.pconcat + " -y -filter_complex \"concat=n=%d:v=1:a=%d,%s\"" % (a, self.nconcat, ",".join(self.vidfilter))
else:
params = self.pconcat + " -y -filter_complex concat=n=%d:v=1:a=%d" % (self.nconcat, a)
self.lastfilename = filename
params += self.qfilter
start(filename, params)
wait()
def filename(self, filename):
self.lastfilename = filename
def framerate(self, framerate):
self.framerate = framerate
def tmppath(self, path):
self.tmppath = path
def process(self, filename, param):
print("process %s -> %s" % (self.lastfilename, filename))
params = " -y -i %s %s " % (self.lastfilename, param)
self.lastfilename = filename
params += self.qfilter
start(filename, params)
wait()
Last updated: 2019-03-26
icons: ./forge/Drives.svg
Bash
bash
(Bash)
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="88px" height="88px" viewBox="0 0 88 88" enable-background="new 0 0 88 88" xml:space="preserve"> <image id="image0" width="88" height="88" x="0" y="0"
xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFgAAABYCAAAAABU/m/oAAAABGdBTUEAALGPC/xhBQAAACBjSFJN
AAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAAmJLR0QA/4ePzL8AAAAJcEhZ
cwAADdcAAA3XAUIom3gAAAAHdElNRQfiBQ8XLjbaD6ZPAAAFqUlEQVRYw+2YXUwUVxTH/3f37i7s
srBfsMiXXUEQyqcViloEI21t2molNWmtxTRp4lNfm/hifGpr0tcm7UM1UdKWxhqtsbZCkIJaxQUs
KF8FDSqwjuwu7Cf7NbcPi7ubNN0dnHnkPM2c3PubM2fuPec/lzAAYCw002+dW9KYG5or5DJIYIQB
YMHxn274ow5a316lItKAmbvrzFzcZfrwvUzxZMLAPJe+X0706ds+0IsGUyA08MMyAG2uThVamvMA
zsvGd9PFg5ntRxsgszQ1Fmv9D2/2TQG2P0prxCaDIjgwCpCyw69pAG11RcWpe8DkQEmGSLAMK90R
wLyvSRN9UGN7NrAyOi86Fcw5BqByp3rVoaxtOg9MnN4gAqrbYZFTtuADNCXmmFfbcIGHs0fMLlEP
HcuhsAPIMMrj72DUuIFIRAR4+dZ4tgwEAEvwEvAimFELyyCDHoDHHkeHHX7R4FfLCSUFyiB8D7hY
kt1WHshqLHhxrLxom4FQZFUNAoM3969+rcj0bwAq2jeLCJiAgJK0PUMM9k7DdgUBEJo+6QVoZZHY
2ik/ITeOcIBjlNcj6Hd1fTkPkKoDhWLTTBiCfV8vAkBuqc5z/ykAZB9pU0oAhuvXjsVEn2H/IUnK
JjLfll18FIolp2Bvm3hutDXBa+2+bwsCgCy3omWXWhwzAQzGjU7OuwMKzYbSOjHl5z9gAEGnK6DQ
mBWSYBPBEpskGmIdvA4WZnQtgyMBuUJoJGsBe0emsjaZ9MJk3VrA3SeDUG9trhe07deypT8eB4CM
7QeqBAS9lo8XxXl6vhsQIBDkJ4SD1bPLDADjuKKclAEJALsnn6jSCYCiwmKT1w3gWbg8UzzYfqHz
li1fB0BWUFm+0TcHMK64KNVXT7kq+Ac/c5gtyVMAIDTPZMQtwNW/NTfFvJS58o1xgMcZXr1VVr9j
BjDoSLWaKPhn3VySAf4RAGnGmM5QvlxzFXCeykvyk5LdmiOjzHFsOpkWZmEAeQmrIGcTAHZdnmSO
/NpXJsrujqbeI9r0eHxKrTwChMPJxo+M7I4K71QWCMWv+aAAsc9ASW31FJ8s5kgEWLDzsVw4HgOA
Ikk8RFZaSyjRf9HNJQGHpoeB5eFa0yopMjMCIL0liQoj5lYDoZCZP0r2UuH+CT/Qs6U1mmZ+rnsa
QM3RVIo/5Tqmlq0AFs92cREGtjLe+TuAtNcMKeelGoDc1gk78PCb+3VG6nvcNxwGSE19ysIpoB4v
dFzyAYA6R+V6ygPAS0d3qSQAs5mzf3oSHYWH3shKOUtIB+EfXrwal/x0S1uTAGEuqDXxnLXXGg1a
nreruUwjYI7AnrfydHbqgTOUkVNaUqgX1M4EN9OIzxtiVJkh9IRrXXivg9fBSWxNij6ZBThvRKXV
x/SHNGD25O7MvDeclplfVrnaHCUBu3v7xqINWZVX+XqtGpCmVth+uboQOwRUWtpas6QBcx2XEw/M
ifmTvRopUuG/cmUZAArK1EuTHMBsp3MaFRKA/+5yAij4tF5FeGfPeQ6wdZaaxYNdt6cAlB+3UAC6
wxu/nQUGhvYoZQATZbOjPGD4vJgCAEnbfTAT4K+tMAreeeexiMPi2SkA+2InlnSHtRew+rSUOY4P
iE2Hok4bu8633AjBZTfL2LhoLnTaeMmRGzMAOJgMEfmLE//XGCgpb7znE7FLWARwLcWjC9l9AIyE
EtNnN5ZEgOfvuBEYqtY9v5+bDgAGAyjkJSViXnrUOQxcq29YPWrw9I8BqNMQ0YW+qFoBzJ0ZjK5Y
V89lO6DcnSZ+52U23J4AhvzNjUXpy//8df0RgG3VEtQKUvnm4iLY2JNeHQ3YbV4A+e9nEwmKkOat
xUsuwOV67jC1v0IlqcfMce6cM35rOdKiIdJ0EOYf6ri7+guc0XJws4JAKhnLh0b6rc+8hsL6nRYa
XWj/AvmiPJXm3QhiAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE4LTA1LTE1VDIzOjQ2OjU0KzAyOjAw
94qregAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxOC0wNS0xNVQyMzo0Njo1NCswMjowMIbXE8YAAAAZ
dEVYdFNvZnR3YXJlAHd3dy5pbmtzY2FwZS5vcmeb7jwaAAAAAElFTkSuQmCC" />
</svg>
Last updated: 2018-05-15