mirror of
https://github.com/fhem/fhem-mirror.git
synced 2024-06-14 05:38:43 +00:00
Compare commits
No commits in common. "4.7" and "travis" have entirely different histories.
11
.github/dependabot.yml
vendored
Normal file
11
.github/dependabot.yml
vendored
Normal file
|
@ -0,0 +1,11 @@
|
|||
# To get started with Dependabot version updates, you'll need to specify which
|
||||
# package ecosystems to update and where the package manifests are located.
|
||||
# Please see the documentation for all configuration options:
|
||||
# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
|
||||
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "github-actions" # See documentation for possible values
|
||||
directory: "/" # Location of package manifests
|
||||
schedule:
|
||||
interval: "daily"
|
104
.github/workflows/cache.yml
vendored
Normal file
104
.github/workflows/cache.yml
vendored
Normal file
|
@ -0,0 +1,104 @@
|
|||
name: Update cache from SVN
|
||||
|
||||
on:
|
||||
# Allows you to run this workflow manually from the Actions tab
|
||||
workflow_dispatch:
|
||||
|
||||
#schedule:
|
||||
#- cron: '59 */3 * * *' # every third hour to keep cache up to date
|
||||
|
||||
jobs:
|
||||
# This workflow contains a single job called "build"
|
||||
mirror:
|
||||
# The type of runner that the job will run on
|
||||
runs-on: ubuntu-latest
|
||||
continue-on-error: true
|
||||
|
||||
steps:
|
||||
- name: install git-svn package
|
||||
run: |
|
||||
sudo apt-get remove git git-man
|
||||
sudo apt-get update
|
||||
sudo apt-get install git-svn --no-install-recommends
|
||||
|
||||
- name: checkout authors
|
||||
uses: actions/checkout@v4.1.7
|
||||
with:
|
||||
ref: travis
|
||||
path: ./authors
|
||||
|
||||
- name: Get current date
|
||||
id: get-date
|
||||
run: |
|
||||
echo "::set-output name=timestamp::$(/bin/date -u "+%Y%m%d%H" )"
|
||||
shell: bash
|
||||
|
||||
- name: Cache runners svn-2-git-fhem mirror directory
|
||||
# Some room for improvement because we create a new cache on every run where a new ref is fetched, this isn't very nice, normaly we need only the last one and it takes 7 days until they are deleted
|
||||
id: cache-fhem
|
||||
uses: actions/cache@v4.0.2
|
||||
with:
|
||||
path: |
|
||||
./src/fhem-mirror/.git
|
||||
key: ${{ runner.os }}-fhemsvndir-${{ steps.get-date.outputs.timestamp }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-fhemsvndir-
|
||||
|
||||
#- name: remove gitconfig
|
||||
# run: |
|
||||
# rm ./src/fhem-mirror/.git/config
|
||||
|
||||
- name: checkout main branch
|
||||
uses: actions/checkout@v4.1.7
|
||||
with:
|
||||
path: ./src/fhem-mirror
|
||||
clean: false
|
||||
|
||||
- name: generate merged authors file
|
||||
run: |
|
||||
cd /tmp
|
||||
svn log https://svn.fhem.de/fhem --xml --quiet | grep author | sort -u | perl -pe 's/.*>(.*?)<.*/$1 = $1 <>/' > ${GITHUB_WORKSPACE}/authors/authors_svn.txt;
|
||||
cat ${GITHUB_WORKSPACE}/authors/authors.txt ${GITHUB_WORKSPACE}/authors/authors_svn.txt | sort -u -k1,1 > ${GITHUB_WORKSPACE}/authors/authors_merged.txt;
|
||||
ls -la ${GITHUB_WORKSPACE}/authors/authors_merged.txt;
|
||||
|
||||
- name: fetch from svn
|
||||
id: fetchsvn
|
||||
timeout-minutes: 120
|
||||
working-directory: ./src/fhem-mirror
|
||||
run: |
|
||||
echo "::group::git svn init"
|
||||
git svn init --trunk=trunk --tags=tags --prefix=svn/ https://svn.fhem.de/fhem;
|
||||
git config --replace-all svn.authorsfile "${GITHUB_WORKSPACE}/authors/authors_merged.txt"
|
||||
git config --replace-all svn-remote.svn.preserve-empty-dirs "true" ;
|
||||
git config --replace-all svn-remote.svn.placeholder-filename ".gitkeep" ;
|
||||
echo "Current .git/config file content:";
|
||||
cat ${GITHUB_WORKSPACE}/src/fhem-mirror/.git/config;
|
||||
echo "::endgroup::"
|
||||
|
||||
echo "::set-output name=SVN_FETCH_STATUS::incomplete"
|
||||
# Run fetches after init, go pick up some base refs for the cache on first run only!
|
||||
RET=124
|
||||
c=1
|
||||
while [ $RET -eq 124 ]; do
|
||||
echo "::group::Fetch ${c}/5"
|
||||
timeout 1200 git svn --log-window-size=200 -q fetch && RET=$? || true
|
||||
if [ "$RET" -ne 0 ] && [ "$RET" -ne 124 ]; then
|
||||
echo "::set-output name=SVN_FETCH_STATUS::error"
|
||||
fi
|
||||
((c++)) && ((c==6)) && break
|
||||
echo "::endgroup::"
|
||||
done
|
||||
if [ "$RET" -eq 0 ]; then
|
||||
echo "::set-output name=SVN_FETCH_STATUS::complete"
|
||||
fi
|
||||
|
||||
# - name: Copy Workflow Files to target
|
||||
# if: ${{ steps.fetchsvn.outputs.SVN_FETCH_STATUS == 'complete' }}
|
||||
# run: |
|
||||
# cp -R ${GITHUB_WORKSPACE}/main/.github ./src/fhem-mirror
|
||||
|
||||
- name: Verify no fetch error state
|
||||
if: ${{ steps.fetchsvn.outputs.SVN_FETCH_STATUS == 'error' }}
|
||||
run: |
|
||||
echo "A permanent error occured"
|
||||
exit 1
|
143
.github/workflows/mirror.yml
vendored
Normal file
143
.github/workflows/mirror.yml
vendored
Normal file
|
@ -0,0 +1,143 @@
|
|||
name: Mirror from SVN
|
||||
|
||||
on:
|
||||
push:
|
||||
|
||||
# Allows you to run this workflow manually from the Actions tab
|
||||
workflow_dispatch:
|
||||
|
||||
schedule:
|
||||
- cron: '10 */10 * * *' # every hour to keep cache up to date
|
||||
|
||||
jobs:
|
||||
# This workflow contains a single job called "build"
|
||||
mirror:
|
||||
# The type of runner that the job will run on
|
||||
runs-on: ubuntu-latest
|
||||
continue-on-error: true
|
||||
|
||||
steps:
|
||||
|
||||
- name: install git-svn package
|
||||
run: |
|
||||
sudo apt-get remove git git-man
|
||||
sudo apt-get update
|
||||
sudo apt-get install git-svn --no-install-recommends
|
||||
|
||||
- name: checkout mirror config branch
|
||||
uses: actions/checkout@v4.1.7
|
||||
|
||||
- name: Get current date as seconds
|
||||
id: get-date
|
||||
run: |
|
||||
echo "timestamp=$(/bin/date -u "+%Y%m%d%H" )" >> $GITHUB_OUTPUT
|
||||
|
||||
shell: bash
|
||||
|
||||
- name: generate merged authors file
|
||||
run: |
|
||||
ls -RLa ${GITHUB_WORKSPACE}
|
||||
cd /tmp
|
||||
mkdir -p ${GITHUB_WORKSPACE}/authors
|
||||
svn log https://svn.fhem.de/fhem --xml --quiet | grep author | sort -u | perl -pe 's/.*>(.*?)<.*/$1 = $1 <>/' > ${GITHUB_WORKSPACE}/authors_svn.txt;
|
||||
cat ${GITHUB_WORKSPACE}/authors.txt ${GITHUB_WORKSPACE}/authors_svn.txt | sort -u -k1,1 > ${GITHUB_WORKSPACE}/authors/authors_merged.txt;
|
||||
ls -la ${GITHUB_WORKSPACE}/authors/authors_merged.txt;
|
||||
|
||||
- name: create tmpfs for svn repo
|
||||
run: |
|
||||
mkdir -p ./src/fhem-mirror
|
||||
sudo mount -t tmpfs -o size=3G tmpfs ./src/fhem-mirror
|
||||
|
||||
- name: Cache runners svn-2-git-fhem mirror directory
|
||||
# Some room for improvement because we create a new cache on every run where a new ref is fetched, this isn't very nice, normaly weneed only the last one and it takes 7 days until they are deleted
|
||||
id: cache-fhem
|
||||
uses: actions/cache@v4.0.2
|
||||
with:
|
||||
path: ./src/fhem-mirror/.git
|
||||
key: ${{ runner.os }}-fhemsvndir-${{ steps.get-date.outputs.timestamp }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-fhemsvndir-
|
||||
|
||||
- name: list filesystem
|
||||
run: |
|
||||
df -h ./src/fhem-mirror
|
||||
|
||||
- name: clean cache
|
||||
env:
|
||||
Clean_Cache: ${{ secrets.CLEANCACHE }}
|
||||
if: "${{ env.Clean_Cache == 'true' }}"
|
||||
run: |
|
||||
rm -r ./src/fhem-mirror/.git
|
||||
|
||||
#- name: 'Tar files'
|
||||
# run: tar -cvf ${GITHUB_WORKSPACE}/svnMirror.tar ./src/fhem-mirror/
|
||||
|
||||
#- uses: actions/upload-artifact@v2
|
||||
# with:
|
||||
# name: mirror-artifact
|
||||
# path: ./svnMirror.tar
|
||||
|
||||
- name: init mirror repository if it is not already a mirror
|
||||
timeout-minutes: 1800
|
||||
run: |
|
||||
if [[ ! -d "${GITHUB_WORKSPACE}/src/fhem-mirror/.git" ]]; then
|
||||
git init "${GITHUB_WORKSPACE}/src/fhem-mirror" ;
|
||||
cd "${GITHUB_WORKSPACE}/src/fhem-mirror";
|
||||
git svn init --trunk=trunk --tags=tags --prefix=svn/ https://svn.fhem.de/fhem;
|
||||
git config --replace-all svn-remote.svn.preserve-empty-dirs "true" ;
|
||||
git config --replace-all svn-remote.svn.placeholder-filename ".gitkeep" ;
|
||||
git config --replace-all svn.authorsfile "${GITHUB_WORKSPACE}/authors/authors_merged.txt" ;
|
||||
# Run extra fetches after init, go pick up some base refs for the cache on first run only!
|
||||
timeout 900 git svn -q fetch || timeout 900 git svn -q fetch || timeout 900 git svn -q fetch || true
|
||||
else
|
||||
echo "Current .git/config file content:";
|
||||
cat ${GITHUB_WORKSPACE}/src/fhem-mirror/.git/config;
|
||||
fi
|
||||
|
||||
- name: fetch svn to git master branch
|
||||
id: fetchsvn
|
||||
timeout-minutes: 1800
|
||||
run: |
|
||||
echo "SVN_FETCH_STATUS=incomplete" >> $GITHUB_OUTPUT
|
||||
cd "${GITHUB_WORKSPACE}/src/fhem-mirror";
|
||||
RET=0
|
||||
timeout 1800 git svn -q --log-window-size=5000 fetch || timeout 1500 git svn -q --log-window-size=5000 fetch || RET=$?;
|
||||
if [[ $RET == 0 ]]; then
|
||||
git switch master
|
||||
git config --global user.email "actions@gitbhub.com"
|
||||
git config --global user.name "Github Actions"
|
||||
git reset --hard "remotes/svn/trunk"
|
||||
echo "SVN_FETCH_STATUS=complete" >> $GITHUB_OUTPUT
|
||||
elif [[ $RET != 124 ]]; then
|
||||
echo "SVN_FETCH_STATUS=error" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Verify no fetch error state
|
||||
if: ${{ steps.fetchsvn.outputs.SVN_FETCH_STATUS == 'error' }}
|
||||
run: |
|
||||
echo "A permanent error occured"
|
||||
exit 1
|
||||
|
||||
- name: Recreate tags from svn
|
||||
if: ${{ steps.fetchsvn.outputs.SVN_FETCH_STATUS == 'complete' }}
|
||||
working-directory: ./src/fhem-mirror
|
||||
run: |
|
||||
git for-each-ref --format="%(refname:lstrip=-1) %(objectname)" refs/remotes/svn/tags/FHEM_*_? \
|
||||
| while read BRANCH REF
|
||||
do
|
||||
TAG_NAME=${BRANCH#FHEM_}
|
||||
TAG_NAME=$(echo $TAG_NAME | sed 's/_/./g')
|
||||
BODY="$(git log -1 --format=format:%B $REF)"
|
||||
echo "branch=$BRANCH ref=$REF parent=$(git rev-parse $REF^) tagname=$TAG_NAME body=$BODY" >&2
|
||||
git tag -a -f -m "$BODY" $TAG_NAME $REF^
|
||||
# git branch -r -d origin/tags/$BRANCH
|
||||
done
|
||||
|
||||
- name: push tags and commits into master branch (force)
|
||||
if: ${{ steps.fetchsvn.outputs.SVN_FETCH_STATUS == 'complete' }}
|
||||
working-directory: ./src/fhem-mirror
|
||||
run: |
|
||||
git remote set-url origin https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }} || git remote add origin https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}
|
||||
git fetch --unshallow || true
|
||||
git push origin master --force --tags
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
# The "checkoutlist" file is used to support additional version controlled
|
||||
# administrative files in $CVSROOT/CVSROOT, such as template files.
|
||||
#
|
||||
# The first entry on a line is a filename which will be checked out from
|
||||
# the corresponding RCS file in the $CVSROOT/CVSROOT directory.
|
||||
# The remainder of the line is an error message to use if the file cannot
|
||||
# be checked out.
|
||||
#
|
||||
# File format:
|
||||
#
|
||||
# [<whitespace>]<filename><whitespace><error message><end-of-line>
|
||||
#
|
||||
# comment lines begin with '#'
|
|
@ -1,15 +0,0 @@
|
|||
# The "commitinfo" file is used to control pre-commit checks.
|
||||
# The filter on the right is invoked with the repository and a list
|
||||
# of files to check. A non-zero exit of the filter program will
|
||||
# cause the commit to be aborted.
|
||||
#
|
||||
# The first entry on a line is a regular expression which is tested
|
||||
# against the directory that the change is being committed to, relative
|
||||
# to the $CVSROOT. For the first match that is found, then the remainder
|
||||
# of the line is the name of the filter to run.
|
||||
#
|
||||
# If the repository name does not match any of the regular expressions in this
|
||||
# file, the "DEFAULT" line is used, if it is specified.
|
||||
#
|
||||
# If the name "ALL" appears as a regular expression it is always used
|
||||
# in addition to the first matching regex or "DEFAULT".
|
|
@ -1,21 +0,0 @@
|
|||
# Set this to "no" if pserver shouldn't check system users/passwords
|
||||
#SystemAuth=no
|
||||
|
||||
# Put CVS lock files in this directory rather than directly in the repository.
|
||||
#LockDir=/var/lock/cvs
|
||||
|
||||
# Set `TopLevelAdmin' to `yes' to create a CVS directory at the top
|
||||
# level of the new working directory when using the `cvs checkout'
|
||||
# command.
|
||||
#TopLevelAdmin=no
|
||||
|
||||
# Set `LogHistory' to `all' or `TOFEWGCMAR' to log all transactions to the
|
||||
# history file, or a subset as needed (ie `TMAR' logs all write operations)
|
||||
#LogHistory=TOFEWGCMAR
|
||||
|
||||
# Set `RereadLogAfterVerify' to `always' (the default) to allow the verifymsg
|
||||
# script to change the log message. Set it to `stat' to force CVS to verify# that the file has changed before reading it (this can take up to an extra
|
||||
# second per directory being committed, so it is not recommended for large
|
||||
# repositories. Set it to `never' (the previous CVS behavior) to prevent
|
||||
# verifymsg scripts from changing the log message.
|
||||
#RereadLogAfterVerify=always
|
|
@ -1,19 +0,0 @@
|
|||
# This file affects handling of files based on their names.
|
||||
#
|
||||
# The -m option specifies whether CVS attempts to merge files.
|
||||
#
|
||||
# The -k option specifies keyword expansion (e.g. -kb for binary).
|
||||
#
|
||||
# Format of wrapper file ($CVSROOT/CVSROOT/cvswrappers or .cvswrappers)
|
||||
#
|
||||
# wildcard [option value][option value]...
|
||||
#
|
||||
# where option is one of
|
||||
# -f from cvs filter value: path to filter
|
||||
# -t to cvs filter value: path to filter
|
||||
# -m update methodology value: MERGE or COPY
|
||||
# -k expansion mode value: b, o, kkv, &c
|
||||
#
|
||||
# and value is a single-quote delimited value.
|
||||
# For example:
|
||||
#*.gif -k 'b'
|
|
@ -1,21 +0,0 @@
|
|||
# The "editinfo" file is used to allow verification of logging
|
||||
# information. It works best when a template (as specified in the
|
||||
# rcsinfo file) is provided for the logging procedure. Given a
|
||||
# template with locations for, a bug-id number, a list of people who
|
||||
# reviewed the code before it can be checked in, and an external
|
||||
# process to catalog the differences that were code reviewed, the
|
||||
# following test can be applied to the code:
|
||||
#
|
||||
# Making sure that the entered bug-id number is correct.
|
||||
# Validating that the code that was reviewed is indeed the code being
|
||||
# checked in (using the bug-id number or a seperate review
|
||||
# number to identify this particular code set.).
|
||||
#
|
||||
# If any of the above test failed, then the commit would be aborted.
|
||||
#
|
||||
# Actions such as mailing a copy of the report to each reviewer are
|
||||
# better handled by an entry in the loginfo file.
|
||||
#
|
||||
# One thing that should be noted is the the ALL keyword is not
|
||||
# supported. There can be only one entry that matches a given
|
||||
# repository.
|
|
@ -1,26 +0,0 @@
|
|||
# The "loginfo" file controls where "cvs commit" log information
|
||||
# is sent. The first entry on a line is a regular expression which must match
|
||||
# the directory that the change is being made to, relative to the
|
||||
# $CVSROOT. If a match is found, then the remainder of the line is a filter
|
||||
# program that should expect log information on its standard input.
|
||||
#
|
||||
# If the repository name does not match any of the regular expressions in this
|
||||
# file, the "DEFAULT" line is used, if it is specified.
|
||||
#
|
||||
# If the name ALL appears as a regular expression it is always used
|
||||
# in addition to the first matching regex or DEFAULT.
|
||||
#
|
||||
# You may specify a format string as part of the
|
||||
# filter. The string is composed of a `%' followed
|
||||
# by a single format character, or followed by a set of format
|
||||
# characters surrounded by `{' and `}' as separators. The format
|
||||
# characters are:
|
||||
#
|
||||
# s = file name
|
||||
# V = old version number (pre-checkin)
|
||||
# v = new version number (post-checkin)
|
||||
#
|
||||
# For example:
|
||||
#DEFAULT (echo ""; id; echo %s; date; cat) >> $CVSROOT/CVSROOT/commitlog
|
||||
# or
|
||||
#DEFAULT (echo ""; id; echo %{sVv}; date; cat) >> $CVSROOT/CVSROOT/commitlog
|
|
@ -1,26 +0,0 @@
|
|||
# Three different line formats are valid:
|
||||
# key -a aliases...
|
||||
# key [options] directory
|
||||
# key [options] directory files...
|
||||
#
|
||||
# Where "options" are composed of:
|
||||
# -i prog Run "prog" on "cvs commit" from top-level of module.
|
||||
# -o prog Run "prog" on "cvs checkout" of module.
|
||||
# -e prog Run "prog" on "cvs export" of module.
|
||||
# -t prog Run "prog" on "cvs rtag" of module.
|
||||
# -u prog Run "prog" on "cvs update" of module.
|
||||
# -d dir Place module in directory "dir" instead of module name.
|
||||
# -l Top-level directory only -- do not recurse.
|
||||
#
|
||||
# NOTE: If you change any of the "Run" options above, you'll have to
|
||||
# release and re-checkout any working directories of these modules.
|
||||
#
|
||||
# And "directory" is a path to a directory relative to $CVSROOT.
|
||||
#
|
||||
# The "-a" option specifies an alias. An alias is interpreted as if
|
||||
# everything on the right of the "-a" had been typed on the command line.
|
||||
#
|
||||
# You can encode a module within a module by using the special '&'
|
||||
# character to interpose another module into the current module. This
|
||||
# can be useful for creating a module that consists of many directories
|
||||
# spread out over the entire source repository.
|
|
@ -1,12 +0,0 @@
|
|||
# The "notify" file controls where notifications from watches set by
|
||||
# "cvs watch add" or "cvs edit" are sent. The first entry on a line is
|
||||
# a regular expression which is tested against the directory that the
|
||||
# change is being made to, relative to the $CVSROOT. If it matches,
|
||||
# then the remainder of the line is a filter program that should contain
|
||||
# one occurrence of %s for the user to notify, and information on its
|
||||
# standard input.
|
||||
#
|
||||
# "ALL" or "DEFAULT" can be used in place of the regular expression.
|
||||
#
|
||||
# For example:
|
||||
#ALL mail -s "CVS notification" %s
|
|
@ -1,13 +0,0 @@
|
|||
# The "rcsinfo" file is used to control templates with which the editor
|
||||
# is invoked on commit and import.
|
||||
#
|
||||
# The first entry on a line is a regular expression which is tested
|
||||
# against the directory that the change is being made to, relative to the
|
||||
# $CVSROOT. For the first match that is found, then the remainder of the
|
||||
# line is the name of the file that contains the template.
|
||||
#
|
||||
# If the repository name does not match any of the regular expressions in this
|
||||
# file, the "DEFAULT" line is used, if it is specified.
|
||||
#
|
||||
# If the name "ALL" appears as a regular expression it is always used
|
||||
# in addition to the first matching regex or "DEFAULT".
|
|
@ -1,20 +0,0 @@
|
|||
# The "taginfo" file is used to control pre-tag checks.
|
||||
# The filter on the right is invoked with the following arguments:
|
||||
#
|
||||
# $1 -- tagname
|
||||
# $2 -- operation "add" for tag, "mov" for tag -F, and "del" for tag -d
|
||||
# $3 -- repository
|
||||
# $4-> file revision [file revision ...]
|
||||
#
|
||||
# A non-zero exit of the filter program will cause the tag to be aborted.
|
||||
#
|
||||
# The first entry on a line is a regular expression which is tested
|
||||
# against the directory that the change is being committed to, relative
|
||||
# to the $CVSROOT. For the first match that is found, then the remainder
|
||||
# of the line is the name of the filter to run.
|
||||
#
|
||||
# If the repository name does not match any of the regular expressions in this
|
||||
# file, the "DEFAULT" line is used, if it is specified.
|
||||
#
|
||||
# If the name "ALL" appears as a regular expression it is always used
|
||||
# in addition to the first matching regex or "DEFAULT".
|
|
@ -1,21 +0,0 @@
|
|||
# The "verifymsg" file is used to allow verification of logging
|
||||
# information. It works best when a template (as specified in the
|
||||
# rcsinfo file) is provided for the logging procedure. Given a
|
||||
# template with locations for, a bug-id number, a list of people who
|
||||
# reviewed the code before it can be checked in, and an external
|
||||
# process to catalog the differences that were code reviewed, the
|
||||
# following test can be applied to the code:
|
||||
#
|
||||
# Making sure that the entered bug-id number is correct.
|
||||
# Validating that the code that was reviewed is indeed the code being
|
||||
# checked in (using the bug-id number or a seperate review
|
||||
# number to identify this particular code set.).
|
||||
#
|
||||
# If any of the above test failed, then the commit would be aborted.
|
||||
#
|
||||
# Actions such as mailing a copy of the report to each reviewer are
|
||||
# better handled by an entry in the loginfo file.
|
||||
#
|
||||
# One thing that should be noted is the the ALL keyword is not
|
||||
# supported. There can be only one entry that matches a given
|
||||
# repository.
|
339
LICENSE
Normal file
339
LICENSE
Normal file
|
@ -0,0 +1,339 @@
|
|||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 2, June 1991
|
||||
|
||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
License is intended to guarantee your freedom to share and change free
|
||||
software--to make sure the software is free for all its users. This
|
||||
General Public License applies to most of the Free Software
|
||||
Foundation's software and to any other program whose authors commit to
|
||||
using it. (Some other Free Software Foundation software is covered by
|
||||
the GNU Lesser General Public License instead.) You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
this service if you wish), that you receive source code or can get it
|
||||
if you want it, that you can change the software or use pieces of it
|
||||
in new free programs; and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
anyone to deny you these rights or to ask you to surrender the rights.
|
||||
These restrictions translate to certain responsibilities for you if you
|
||||
distribute copies of the software, or if you modify it.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must give the recipients all the rights that
|
||||
you have. You must make sure that they, too, receive or can get the
|
||||
source code. And you must show them these terms so they know their
|
||||
rights.
|
||||
|
||||
We protect your rights with two steps: (1) copyright the software, and
|
||||
(2) offer you this license which gives you legal permission to copy,
|
||||
distribute and/or modify the software.
|
||||
|
||||
Also, for each author's protection and ours, we want to make certain
|
||||
that everyone understands that there is no warranty for this free
|
||||
software. If the software is modified by someone else and passed on, we
|
||||
want its recipients to know that what they have is not the original, so
|
||||
that any problems introduced by others will not reflect on the original
|
||||
authors' reputations.
|
||||
|
||||
Finally, any free program is threatened constantly by software
|
||||
patents. We wish to avoid the danger that redistributors of a free
|
||||
program will individually obtain patent licenses, in effect making the
|
||||
program proprietary. To prevent this, we have made it clear that any
|
||||
patent must be licensed for everyone's free use or not licensed at all.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License applies to any program or other work which contains
|
||||
a notice placed by the copyright holder saying it may be distributed
|
||||
under the terms of this General Public License. The "Program", below,
|
||||
refers to any such program or work, and a "work based on the Program"
|
||||
means either the Program or any derivative work under copyright law:
|
||||
that is to say, a work containing the Program or a portion of it,
|
||||
either verbatim or with modifications and/or translated into another
|
||||
language. (Hereinafter, translation is included without limitation in
|
||||
the term "modification".) Each licensee is addressed as "you".
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running the Program is not restricted, and the output from the Program
|
||||
is covered only if its contents constitute a work based on the
|
||||
Program (independent of having been made by running the Program).
|
||||
Whether that is true depends on what the Program does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Program's
|
||||
source code as you receive it, in any medium, provided that you
|
||||
conspicuously and appropriately publish on each copy an appropriate
|
||||
copyright notice and disclaimer of warranty; keep intact all the
|
||||
notices that refer to this License and to the absence of any warranty;
|
||||
and give any other recipients of the Program a copy of this License
|
||||
along with the Program.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy, and
|
||||
you may at your option offer warranty protection in exchange for a fee.
|
||||
|
||||
2. You may modify your copy or copies of the Program or any portion
|
||||
of it, thus forming a work based on the Program, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) You must cause the modified files to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
b) You must cause any work that you distribute or publish, that in
|
||||
whole or in part contains or is derived from the Program or any
|
||||
part thereof, to be licensed as a whole at no charge to all third
|
||||
parties under the terms of this License.
|
||||
|
||||
c) If the modified program normally reads commands interactively
|
||||
when run, you must cause it, when started running for such
|
||||
interactive use in the most ordinary way, to print or display an
|
||||
announcement including an appropriate copyright notice and a
|
||||
notice that there is no warranty (or else, saying that you provide
|
||||
a warranty) and that users may redistribute the program under
|
||||
these conditions, and telling the user how to view a copy of this
|
||||
License. (Exception: if the Program itself is interactive but
|
||||
does not normally print such an announcement, your work based on
|
||||
the Program is not required to print an announcement.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Program,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Program, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Program.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Program
|
||||
with the Program (or with a work based on the Program) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may copy and distribute the Program (or a work based on it,
|
||||
under Section 2) in object code or executable form under the terms of
|
||||
Sections 1 and 2 above provided that you also do one of the following:
|
||||
|
||||
a) Accompany it with the complete corresponding machine-readable
|
||||
source code, which must be distributed under the terms of Sections
|
||||
1 and 2 above on a medium customarily used for software interchange; or,
|
||||
|
||||
b) Accompany it with a written offer, valid for at least three
|
||||
years, to give any third party, for a charge no more than your
|
||||
cost of physically performing source distribution, a complete
|
||||
machine-readable copy of the corresponding source code, to be
|
||||
distributed under the terms of Sections 1 and 2 above on a medium
|
||||
customarily used for software interchange; or,
|
||||
|
||||
c) Accompany it with the information you received as to the offer
|
||||
to distribute corresponding source code. (This alternative is
|
||||
allowed only for noncommercial distribution and only if you
|
||||
received the program in object code or executable form with such
|
||||
an offer, in accord with Subsection b above.)
|
||||
|
||||
The source code for a work means the preferred form of the work for
|
||||
making modifications to it. For an executable work, complete source
|
||||
code means all the source code for all modules it contains, plus any
|
||||
associated interface definition files, plus the scripts used to
|
||||
control compilation and installation of the executable. However, as a
|
||||
special exception, the source code distributed need not include
|
||||
anything that is normally distributed (in either source or binary
|
||||
form) with the major components (compiler, kernel, and so on) of the
|
||||
operating system on which the executable runs, unless that component
|
||||
itself accompanies the executable.
|
||||
|
||||
If distribution of executable or object code is made by offering
|
||||
access to copy from a designated place, then offering equivalent
|
||||
access to copy the source code from the same place counts as
|
||||
distribution of the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
4. You may not copy, modify, sublicense, or distribute the Program
|
||||
except as expressly provided under this License. Any attempt
|
||||
otherwise to copy, modify, sublicense or distribute the Program is
|
||||
void, and will automatically terminate your rights under this License.
|
||||
However, parties who have received copies, or rights, from you under
|
||||
this License will not have their licenses terminated so long as such
|
||||
parties remain in full compliance.
|
||||
|
||||
5. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Program or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Program (or any work based on the
|
||||
Program), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Program or works based on it.
|
||||
|
||||
6. Each time you redistribute the Program (or any work based on the
|
||||
Program), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute or modify the Program subject to
|
||||
these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties to
|
||||
this License.
|
||||
|
||||
7. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Program at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Program by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Program.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under
|
||||
any particular circumstance, the balance of the section is intended to
|
||||
apply and the section as a whole is intended to apply in other
|
||||
circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system, which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
8. If the distribution and/or use of the Program is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Program under this License
|
||||
may add an explicit geographical distribution limitation excluding
|
||||
those countries, so that distribution is permitted only in or among
|
||||
countries not thus excluded. In such case, this License incorporates
|
||||
the limitation as if written in the body of this License.
|
||||
|
||||
9. The Free Software Foundation may publish revised and/or new versions
|
||||
of the General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Program
|
||||
specifies a version number of this License which applies to it and "any
|
||||
later version", you have the option of following the terms and conditions
|
||||
either of that version or of any later version published by the Free
|
||||
Software Foundation. If the Program does not specify a version number of
|
||||
this License, you may choose any version ever published by the Free Software
|
||||
Foundation.
|
||||
|
||||
10. If you wish to incorporate parts of the Program into other free
|
||||
programs whose distribution conditions are different, write to the author
|
||||
to ask for permission. For software which is copyrighted by the Free
|
||||
Software Foundation, write to the Free Software Foundation; we sometimes
|
||||
make exceptions for this. Our decision will be guided by the two goals
|
||||
of preserving the free status of all derivatives of our free software and
|
||||
of promoting the sharing and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
||||
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
||||
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
||||
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
||||
REPAIR OR CORRECTION.
|
||||
|
||||
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
||||
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
||||
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
||||
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
convey the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
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.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program is interactive, make it output a short notice like this
|
||||
when it starts in an interactive mode:
|
||||
|
||||
Gnomovision version 69, Copyright (C) year name of author
|
||||
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, the commands you use may
|
||||
be called something other than `show w' and `show c'; they could even be
|
||||
mouse-clicks or menu items--whatever suits your program.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your
|
||||
school, if any, to sign a "copyright disclaimer" for the program, if
|
||||
necessary. Here is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
||||
`Gnomovision' (which makes passes at compilers) written by James Hacker.
|
||||
|
||||
<signature of Ty Coon>, 1 April 1989
|
||||
Ty Coon, President of Vice
|
||||
|
||||
This General Public License does not permit incorporating your program into
|
||||
proprietary programs. If your program is a subroutine library, you may
|
||||
consider it more useful to permit linking proprietary applications with the
|
||||
library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License.
|
23
README.md
Normal file
23
README.md
Normal file
|
@ -0,0 +1,23 @@
|
|||
# fhem-mirror
|
||||
READ-ONLY mirror of the [main Subversion repository](http://svn.fhem.de/fhem/trunk), updated multiple times every day.
|
||||
|
||||
## Branches
|
||||
1. The [`master`](https://github.com/fhem/fhem-mirror/tree/master) branch hosts the current source code from [FHEM SVN Trunk](http://svn.fhem.de/fhem/trunk).
|
||||
2. The [`travis`](https://github.com/fhem/fhem-mirror/tree/travis) branch is controlling the mirroring process, running on [Github Actions](https://github.com/fhem/fhem-mirror/actions/workflows/mirror.yml).
|
||||
3. Under [`tags`](https://github.com/fhem/fhem-mirror/tags) FHEM Releases are mirrored also.
|
||||
|
||||
## Pull requests
|
||||
Pull requests to any other branch besides [`travis`](https://github.com/fhem/fhem-mirror/tree/travis) will be rejected.
|
||||
Instead, a module may have its own repository here on [Github.com/fhem](https://www.github.com/fhem) and will accept your patch using a [pull request](https://help.github.com/en/articles/about-pull-requests).
|
||||
|
||||
If you can't find a repository for the module you would like to contribute, visit the official [FHEM support forum](https://forum.fhem.de/) to post your patch. However, it might not be very welcome as it easily mixes up with user support requests and makes version control extremely difficult to handle. For that particular reason, please consider contacting the maintainer using the forum direct message function and send a link to where s/he can find your changed version or patch file.
|
||||
|
||||
## Author matching
|
||||
Authors from the Subversion repository will be referred here without any email relation.
|
||||
|
||||
Those that also have an Github.com account might have a different username here.
|
||||
|
||||
The [`authors.txt`](https://github.com/fhem/fhem-mirror/blob/travis/authors.txt) file will ensure to re-write authors from the Subversion repository to their Github.com username and email address.
|
||||
Should an author want to be re-matched for any _future_ commits, s/he may modify [`authors.txt`](https://github.com/fhem/fhem-mirror/blob/travis/authors.txt) file and send a [Git pull request](https://help.github.com/articles/creating-a-pull-request-from-a-fork/).
|
||||
|
||||
For that purpose, remember to fork the correct branch [`travis`](https://github.com/fhem/fhem-mirror/tree/travis), _not_ the [`master`](https://github.com/fhem/fhem-mirror/tree/master) branch.
|
17
authors.txt
Normal file
17
authors.txt
Normal file
|
@ -0,0 +1,17 @@
|
|||
(no author) = unknown <>
|
||||
root = unknown <>
|
||||
fhemupdate = svc8083 <svc8083@users.noreply.github.com>
|
||||
loredo = jpawlowski <jpawlowski@users.noreply.github.com>
|
||||
justme1968 = justme-1968 <justme-1968@users.noreply.github.com>
|
||||
CoolTux = LeonGaultier <LeonGaultier@users.noreply.github.com>
|
||||
neubert = borisneubert <borisneubert@users.noreply.github.com>
|
||||
Sidey = sidey79 <sidey79@users.noreply.github.com>
|
||||
yoda_gh = gernot-h <gernot-h@users.noreply.github.com>
|
||||
DeeSPe = deespe <DeeSPe@users.noreply.github.com>
|
||||
igami = igami <igami@users.noreply.github.com>
|
||||
KernSani = KernSani <KernSani@users.noreply.github.com>
|
||||
hexenmeister = hexenmeister <hexenmeister@users.noreply.github.com>
|
||||
eisler = eisler <eisler@users.noreply.github.com>
|
||||
marvin78 = marvin78 <marvin78@users.noreply.github.com>
|
||||
dominik = dominikkarall <dominikkarall@users.noreply.github.com>
|
||||
DS_Starter = nasseeder1 <nasseeder1@users.noreply.github.com>
|
536
fhem/CHANGED
536
fhem/CHANGED
|
@ -1,536 +0,0 @@
|
|||
- 2005-10-27 (1.3)
|
||||
- Bugfix: multiple at commands at the same time.
|
||||
|
||||
- 2005-11-10 (1.4)
|
||||
- Reformatting the package and the documentation
|
||||
- New links
|
||||
|
||||
- 2005-12-26 (1.5)
|
||||
- "modularized" in preparation for the FHT80B -> each device has a type
|
||||
- added relative "at" commands (with +HH:MM:SS)
|
||||
- multiple commands on one line separated with ;
|
||||
- sleeping 0.22 seconds after an ST command
|
||||
- some commands/syntax changed:
|
||||
- switch => set
|
||||
- device => fhzdevice
|
||||
- define <name> ... => define <name> <type> ...
|
||||
- the state of the devices and the at commands are saved
|
||||
- at start always sending a "set 0001 00 01" to enable the FHZ receiever.
|
||||
This is a workaround.
|
||||
- doc rewrite, examples directory
|
||||
|
||||
- 2006-01-03 (1.6)
|
||||
- signal handling (to save the state on shutdown)
|
||||
- module FHZ addded (for the FHZ1000PC device itself)
|
||||
- added the get function (to make the initialization prettier)
|
||||
- the module ST was renamed to FS20
|
||||
- FS20 timer commands added
|
||||
- modules command removed (we are loading everything from the modpath
|
||||
directory)
|
||||
- FHT80b module added (yes, it is already useful, you can set
|
||||
and view a lot of values)
|
||||
- documentation adapted
|
||||
- Added a TODO file
|
||||
|
||||
- 2006-01-04 (1.7)
|
||||
- the at command can be used to execute something repeatedly with *
|
||||
- ntfy can filter on device or on device+event with a regexp
|
||||
- checking the delete and notify regexps if they make sense
|
||||
- the FHT init string is now a set command (refreshvalues)
|
||||
- shutdown saves the detailed device information too
|
||||
|
||||
- 2006-01-05 (1.8)
|
||||
- Bugfix: detailed FS20 status was not set from external event
|
||||
- Bugfix: setstate for FS20 returned the last state set
|
||||
- Bugfix: undefined FS20 devices (can) crash the server
|
||||
- HMS module added by Martin Mueller
|
||||
(currently supporting the HMS100T & HMS100TF)
|
||||
- Log modules added, the first one being a simple FileLog
|
||||
(inspired by Martin Mueller)
|
||||
- A little gnuplot script to display temperature and actuator changes
|
||||
|
||||
- 2006-02-10 (1.9)
|
||||
(aka as the Juergen release)
|
||||
- The FHZ1300 is reported to work
|
||||
- Bugfix: spaces before comment in the config file should be ignored
|
||||
- added FS20STR codes to 10_FS20.pm
|
||||
- names restricted to A-Za-z0-9.:- (especially _ is not allowed)
|
||||
- delete calles now an UndefFn in the module
|
||||
- implementation of FS20 function group/local master/global master
|
||||
- the list command tells you the definition of the device too
|
||||
|
||||
- 2006-02-12 (1.9a)
|
||||
- Bugfix: wrong rights for HMS and wrong place for readonly
|
||||
(thanks to Juergen)
|
||||
|
||||
- 2006-02-12 (1.9b)
|
||||
- Bugfix: Fixing the same bug again (thanks to Martin)
|
||||
|
||||
- 2006-04-02 (2.0)
|
||||
- XmlList and webfrontend/pgm1 programs from Raoul Matthiessen
|
||||
- list tries to display the state and not the last command
|
||||
- Both log facilities (FileLog and Log) take wildcards
|
||||
(week, year, month, etc) to make logfile rotating easier
|
||||
- webfrontend/pgm2
|
||||
|
||||
- 2006-04-15 (2.1)
|
||||
- webfrontend/pgm2 changes:
|
||||
- make it work on Asus dsl-routers (no "use warnings")
|
||||
- css/readonly configurable
|
||||
- Formatting for HMS data
|
||||
- comments can be added to each device (setstate <dev> comment:xxx)
|
||||
- testbed to dry-test functionality (test directory)
|
||||
- added an empty hull for the KS300 weather module
|
||||
- added undocumented "exec" function to call arbitrary program parts
|
||||
for debugging. Example: exec FhzDecode("81xx04xx0101a0011234030011");
|
||||
- webfrontend/pgm3, contributed by Martin Haas
|
||||
- fixed pgm1: changing values should work now
|
||||
|
||||
- 2006-05-20 (2.2)
|
||||
- FHZ1300 support verified (+ doc changes)
|
||||
- KS300 support added (with Temperature, Humidity, Wind speed, Rain).
|
||||
Not verified/undecoded: subzero temp, weak battery, Is-raining flag,
|
||||
wind speed > 100km/h
|
||||
- webpgm2 log fix for "offed" FHT devices (with no actuator data)
|
||||
- webpgm3 upgrade (by Martin Haas, see webpgm/pgm3/docs/CHANGES for details)
|
||||
- HMS logging/state format changed to make it similar to KS300
|
||||
- added HMS100WD (thanks to Sascha Pollok)
|
||||
- ntfy/logging changed to be able to notify for multiple attributes
|
||||
arriving in one message
|
||||
- central FHTcode settable (see commandref.html)
|
||||
- optionally listen for non-local requests (port <num> global)
|
||||
- unknown logging
|
||||
- FAQ
|
||||
|
||||
- 2006-6-22 (2.3)
|
||||
- CRC checking (i.e. ignoring messages with bad CRC, message on verbose 4)
|
||||
- contrib/checkmesg.pl added to check message consistency (debugging)
|
||||
- FHT: unknown_aa, unknown_ba codes added. What they are for?
|
||||
- Empty modpath / no modpath error messages added (some user think modpath is
|
||||
superfluous)
|
||||
- Unparsed messages (verbose 5) now printed as hex
|
||||
- Try to reattach to the usb device if it disappears: no need to
|
||||
restart the server if the device is pulled out from the USB socket and
|
||||
plugged in again (old versions go into a busy loop here).
|
||||
- Supressing the seldom (ca 1 out of 700) short KS300 messages.
|
||||
(not sure how to interpret them)
|
||||
- Added KS300 "israining" status flag. Note: this not always triggers when it
|
||||
is raining, but there seems to be a correlation. To be evaluated in more
|
||||
detail.
|
||||
- notifyon can now execute "private" perl code as well (updated
|
||||
commandref.html, added the file example/99_PRIV.pm)
|
||||
- another "perl code" example is logging the data into the database
|
||||
(with DBI), see the file contrib/91_DbLog.pm. Tested with an Oracle DB.
|
||||
- logs added to the xmllist
|
||||
- FHT80b: Fix measured-temp over 25.5 (handling the tempadd messages better)
|
||||
|
||||
- 2006-07-23 (2.4)
|
||||
- contrib/four2hex (to convert between ELV and our codes) by Peter Stark
|
||||
- make dist added to set version (it won't work in a released version)
|
||||
- reload function to reload (private) perl modules
|
||||
- 20_FHT.pm fix: undef occures once without old data
|
||||
- "setstate comment" is replaced with the attr command (i.e. attribute).
|
||||
The corresponding xmllist COMMENT tag is replaced with the ATTR tag.
|
||||
Devices or logs can have attr definitions.
|
||||
- webfrontend/pgm2 (fhzweb.pl) updated to handle "room" attributes(showing
|
||||
only devices in this room).
|
||||
- version 0.4.2 of webfrontend/pgm3 integrated.
|
||||
- contrib/ks300avg.pl to compute daily and monthly avarage values.
|
||||
- the 40_KS300.pm module is computing daily and monthly avarages for the
|
||||
temp/hum and wind values and sum of the rain. The cum_day and cum_month
|
||||
state variables are used as helper values. To log the avarage use the
|
||||
.*avg.* regexp. The regexp for the intraday log will trigger it also.
|
||||
- Added the contrib file garden.pl as a more complex example: garden
|
||||
irrigation. The program computes the time for irrigation from the avarage
|
||||
temperature reported by the ks300-2.
|
||||
- Enable uppercase hex codes (Bug reported by STefan Mayer)
|
||||
- Renamed the unknown_XX FHT80b codes to code_XXXXXX, this will produce
|
||||
"Undefined type" messages when reading the old save file
|
||||
- RM100-2 added (thanks for the codes from andikt).
|
||||
|
||||
- 2006-08-13 (2.5)
|
||||
Special thanks to STefan Mayer for a lot of suggestions and bug reports
|
||||
- If a command is enclosed in {}, then it will be evaluated as a perl
|
||||
expression, if it is enclosed in "" then it is a shell command, else it is
|
||||
a "normal" fhz1000 command.
|
||||
"at" and "notifyon" follow this convention too.
|
||||
Note: a shell command will always be issued in the background.
|
||||
- won't die anymore if the at spec contains an unknown command
|
||||
- rereadcfg added. Sending a HUP should work better now
|
||||
- escaping % and @ in the notify argument is now possible with %% or @@
|
||||
- new command trigger to test notify commands
|
||||
- where you could specify an fhz command, now you can specify a list of
|
||||
them, separated by ";". Escape is ;;
|
||||
- KS300 sometimes reports "negative" rain when it begins to rain. Filter
|
||||
such values. israining is set when the raincounter changed or the ks300
|
||||
israining bit is set.
|
||||
- sleep command, with millisecond accuracy
|
||||
- HMS 100MG support by Peter Stark.
|
||||
- Making FHT and FS20 messages more uniform
|
||||
- contrib/fs20_holidays.sh by STefan Mayer
|
||||
(simulate presence while on holiday)
|
||||
- webfrontends/pgm4 by STefan Mayer: fs20.php
|
||||
- KS300 avg. monthly values fixed (hopefully)
|
||||
- deleted undocumented "exec" function (you can write it now as {...})
|
||||
|
||||
- 2006-09-08 (2.6)
|
||||
- bugfix: updated the examples (hint from Juergen)
|
||||
- bugfix: leading and trailing whitespaces in commands are ignored now
|
||||
- feature: making life easier for perl oneliners: see commandref.html
|
||||
(motivated by STefans suggestions)
|
||||
- feature: include command and multiline commands in the configfiles (\)
|
||||
- bugfix: web/pgm2 KS300 rain plot knows about the avg data
|
||||
- bugfix: the FHT > 25.5 problem. Needs to be tested.
|
||||
- feature: log unknown devices (peters idea, see notifyon description)
|
||||
- feature: HMS wildcard device id for all HMS devices. See the define/HMS
|
||||
section in the commandref.html for details.
|
||||
NOTE: the wildcard for RM100-2 changed from 1001 to 1003.
|
||||
(peters idea)
|
||||
- feature: rolwzo_no_off.sh contrib file (for those who were already closed
|
||||
out by automatically closing rollades, by Martin)
|
||||
- feature: the current version (0.4.5) of the pgm3 from Martin.
|
||||
|
||||
- 2006-09-13 (2.6a)
|
||||
- bugfix: the FHT > 25.5 problem again. A never ending story.
|
||||
|
||||
- 2006-10-03 (2.7)
|
||||
- bugfix: Another try on the > 25.5 problem. (Peters suggestion)
|
||||
- feature: 99_ALARM.pm from Martin (in the contrib directory)
|
||||
- feature: HMS100TFK von Peter P.
|
||||
- feature: attribute loglevel
|
||||
- feature: attribute dummy
|
||||
- feature: attr command documented
|
||||
- feature: the current version (0.5a) of the pgm3 from Martin.
|
||||
|
||||
- 2006-11-08 (2.8)
|
||||
- feature: store oldvalue for triggers. perl only. requested by peter.
|
||||
- feature: inform cmd. Patch by Martin. There are many Martins around here :-)
|
||||
- bugfix: XML: fix & and < and co
|
||||
- bugfix: Accept KS300 negative temperature values
|
||||
- change: the FS20 msg "rain-msg" is called now "activate"
|
||||
- feature: start/stop rc script from Stefan (in the contrib directory)
|
||||
- feature: attribute extra_notify: setting the device will trigger a notify
|
||||
- feature: optional repeat count for the at command
|
||||
- feature: skip_next attribute for the at command
|
||||
- feature: WS300 support by Martin. Check the contrib/ws300 directory.
|
||||
- bugfix: 91_DbLog.pm: retry if the connection is broken by Peter
|
||||
- feature: Martin's pgm3-0.5.2 (see the CHANGELOG on his webpage)
|
||||
- feature: RRD logging example by Peter (in the contrib/rrd directory)
|
||||
|
||||
- 2006-11-19 (2.9)
|
||||
- bugfix: fhz1000.pl dies at startup if the savefile does not exist
|
||||
- bugfix: oldvalue hash is not initialized at startup (peter, Nov 09)
|
||||
- feature: Notify reorganization (requested by juergen and matthias) :
|
||||
- inform will be notified on both real events and set or trigger commands
|
||||
- filelogs will additionally be notified on set or trigger commands
|
||||
- the extra_notify flag is gone: it is default now, there is a
|
||||
do_not_notify flag for the opposite behaviour.
|
||||
- feature: at timespec as a function. Example: at +*{sunset()}
|
||||
commandref.html and examples revisited.
|
||||
- feature: 99_SUNRISE.pm added to use with the new at functionality
|
||||
(replaces the old 99_SUNSET.pm)
|
||||
- feature: webpgm2 "everything" room, at/notify section, arbitrary command
|
||||
- bugfix: resetting the KS300
|
||||
- feature: updated ws300pc (from martin klerx, Nov 08)
|
||||
- bugfix: parsing timed commands implemented => thermo-off,thermo-on and
|
||||
activate replaced with timed off-for-timer,on-for-timer and
|
||||
on-old-for-timer (reported by martin klerx, Nov 08)
|
||||
- feature: pidfile (requested by peter, Nov 10)
|
||||
- bugfix: function 81 is not allowed
|
||||
|
||||
- 2006-11-27 (2.9a)
|
||||
- bugfix: FileLog+Unknown device generates undefined messages
|
||||
- bugfix: trigger with unknown device generates undefined messages
|
||||
|
||||
- 2006-12-28 (3.0)
|
||||
- bugfix: KS300: Make the temperature negative, not the humidity
|
||||
- bugfix: generate correct xmllist even with fhzdev none (Martin, 12.12)
|
||||
- feature: one set command can handle multiple devices (range and enumeration)
|
||||
- feature: new FS20 command on-till
|
||||
- feature: perl: the current state is stored in the %value hash
|
||||
- feature: perl: sunset renamed to sunset_rel, sunset_abs added (for on-till)
|
||||
- feature: perl: isday function added
|
||||
- feature: follow-on-for-timer attribute added to set the state to off
|
||||
- bugfix: the ws300pc negative-temp bugfix included (from Martin Klerx)
|
||||
- feature: version 0.6.2 of the webpgm3 included (from Martin Haas)
|
||||
|
||||
- 2007-01-08 (3.1)
|
||||
- bugfix: delete checks the arg first "exactly", then as a regexp
|
||||
- bugfix: sun*_rel does not work correctly with offset (Martin)
|
||||
- feature: FAQ entry on how to install the sunrise stuff.
|
||||
- feature: the inner core is modified to be able to handle
|
||||
more than one "IO" device, i.e multiple FHZ at the same time,
|
||||
or FHZ + FS10 + WS300. Consequences:
|
||||
- "fhzdev <usbdevice>" replaced with "define <FHZNAME> FHZ <usbdevice>"
|
||||
- "sendraw <fn> <code>" replaced with "set <FHZNAME> raw <fn> <code>"
|
||||
- module function parameters changed (for module developers)
|
||||
- set FHZ activefor dev
|
||||
- select instead sleep after sending FHZ commands
|
||||
- the at timer is more exact (around 1msec instead of 1 sec)
|
||||
- ignoring FS20 device 0001/00/00
|
||||
- feature: contrib/serial.pm to debug serial devices.
|
||||
- feature: WS300 integrated: no external program needed (Martin)
|
||||
- feature: updated to pgm3-0.7.0, see the CHANGELOG at Martins site
|
||||
|
||||
- 2007-01-14 (3.2)
|
||||
- bugfix: example $state changed to $value (remco)
|
||||
- bugfix: sun*_rel does not work correctly with offset (Sebastian)
|
||||
- feature: new HMS100TF codes (Sebastian)
|
||||
- feature: logging unknown HMS with both unique and class ID (Sebastian)
|
||||
- feature: WS300: "Wetter-Willi-Status", rain_raw/rain_cum added, historic
|
||||
data (changes by Martin & Markus)
|
||||
- bugfix: broken rereadcfg / CommandChain after init
|
||||
(reported by Sebastian and Peter)
|
||||
- bugfix: sunrise_coord returned "3", which is irritating
|
||||
|
||||
- 2007-01-25 (3.3)
|
||||
- bugfix: 50_WS300.pm fix from Martin
|
||||
- bugfix: pidfile does not work as expected (reported by Martin)
|
||||
- bugfix: %U in the log-filename is wrong (bugreport by Juergen)
|
||||
- feature: %V added to the log-filename
|
||||
- feature: KS300 wind calibration possibility added
|
||||
- feature: (software) filtering repeater messages (suggested by Martin)
|
||||
- feature: the "client" fhz1000.pl can address another host
|
||||
- bugfix: empty FHT battery is not reported (by Holger)
|
||||
- feature: new FHT codes, e.g. month/day/hour/minute setting (by Holger)
|
||||
|
||||
- 2007-04-14 (4.0)
|
||||
- bugfix: deny at +{3}... (only +*{3} allowed), reported by Bernd, 25.01
|
||||
- bugfix: allow numbers greater then 9 in at +{<number>}
|
||||
- feature: new 50_WS300.pm from Martin (bugfix + rain statistics, 26.01)
|
||||
- feature: renamed fhz1000 to fhem
|
||||
- feature: added HISTORY and README.DEV
|
||||
- doc: Added description of attribute "model".
|
||||
- bugfix: delete the pidfile when terminating. (reported by Martin and Peter)
|
||||
- feature: attribute showtime in web-pgm2 (show time instead of state)
|
||||
- feature: defattr (default attribute for following defines)
|
||||
- feature: added em1010.pl to the contrib directory
|
||||
- doc: added linux.html (multiple devices, udev-links)
|
||||
- REORGANIZATION:
|
||||
- at/notify "renamed" to "define <name> at/notify"
|
||||
- logfile/modpath/pidfile/port/verbose "renamed" to "attr global xxx"
|
||||
- savefile renamed to "attr global statefile"
|
||||
- save command added, it writes the configfile and the statefile
|
||||
- delattr added
|
||||
- list/xmllist format changed
|
||||
- disable attribute for at/notify/filelog
|
||||
See HISTORY for details and reasoning
|
||||
- added rename command
|
||||
- webpgm2 adapted to the new syntax, added device specific attribute
|
||||
and "set" support, gnuplot files are configurable, links to the
|
||||
documentation added.
|
||||
- bugfix: more thorough serial line initialization
|
||||
|
||||
- 2007-08-05 (4.1)
|
||||
- doc: linux.html (private udev-rules, not 50-..., ATTRS)
|
||||
- bugfix: setting devices with "-" in their name did not work
|
||||
- doc: fhem.pl and commandref.html (notifyon -> notify, correction
|
||||
of examples)
|
||||
- feature: modify command added
|
||||
- feature: The "-" in the name is not allowed any more
|
||||
- bugfix: disabled notify causes "uninitialized value" (STefan, 1.5)
|
||||
- bugfix: deleted FS20 items are still logging (zombie) (Gerhard, 16.5)
|
||||
- bugfix: added FS20S8, removed stty_parmrk (Martin, 24.5)
|
||||
- feature: added archivedir/archivecmd to the FileLog
|
||||
- feature: added EM1010PC/EM1000WZ/EM1000EM support
|
||||
- bugfix: undefined messages for unknown HMS devs (Peter, 8.6)
|
||||
- bugfix: em1010 and %oldvalue bugs (Peter, 9.6)
|
||||
- bugfix: SCIVT solar controller (peterp, 1.7)
|
||||
- bugfix: WS300 loglevel change (from 2 to 5 or device specific loglevel)
|
||||
- feature: First steps for a Fritz!Box port. See the fritzbox.html
|
||||
|
||||
- 2007-12-02 (4.2)
|
||||
- feature: added archivedir/archivecmd to the the main logfile
|
||||
- feature: 99_Sunrise_EL.pm (does not need any Date modules)
|
||||
- bugfix: seldom xmllist error resulting in corrupt xml (Martin/Peter, 4.9)
|
||||
- bugfix: FHT mode holiday_short added (9.9, Dirk)
|
||||
- bugfix: Modifying a device from its own trigger crashes (Klaus, 10.9)
|
||||
- feature: webpgm2 output reformatted
|
||||
- feature: webpgm2 displaying multiple plots
|
||||
- feature: FHT lime-protection code discovered by Dirk (7.10)
|
||||
- feature: softwarebuffer for FHT devices (Dirk 17.10)
|
||||
- feature: FHT low temperatur warning and offset (Dirk 17.10)
|
||||
- change: change FHT state into warnings (Dirk 17.10)
|
||||
NOTE: you'll get an undefined type state &
|
||||
undefined type unknown_85 after upgrade.
|
||||
- feature: Softwarebuffer code simplified (Rudi 22.11)
|
||||
- bugfix: bug #12327 doppeltes my
|
||||
- bugfix: set STATE from trigger
|
||||
- bugfix: readings state vs STATE problem (xmllist/trigger)
|
||||
- change: SUNRISE doc changed (99_SUNRISE.pm -> 99_SUNRISE_EL.pm)
|
||||
- feature: support for the M232 ELV device (Boris, 25.11)
|
||||
- feature: alternativ Quad-based numbers for the FS20 (Matthias, 24.11)
|
||||
- feature: dummy type added (contrib/99_dummy.pm)
|
||||
|
||||
- 2008-07-12 (4.3)
|
||||
- bugfix: KS300 state was wrong after the STATE bugfix
|
||||
- feature: HMS100CO (by Peter)
|
||||
- feature: EMGZ (by Peter)
|
||||
- feature: Generate warning if too many commands were sent in the last hour
|
||||
- doc: linux.html: Introduction (Peter S.)
|
||||
- feature: contrib/82_M232Voltage.pm (by Boris, 24.12)
|
||||
- feature: delattr renamed to deleteattr (Rudi, 29.12)
|
||||
- feature: defattr renamed to setdefaultattr (Rudi, 29.12)
|
||||
- feature: device spec (list/range/regexp) for most commands implemented
|
||||
- feature: %NAME, %EVENT, %TYPE parameters in notify definition
|
||||
- feature: added 93_DbLog.pm, database logging facility (Boris, 30.12.)
|
||||
- feature: webfrontend/pgm2 converted to a FHEM module
|
||||
- bugfix: 99_SUNRISE_EL.pm: may schedule double events
|
||||
- bugfix: 62_EMEM.pl, contrib/em1010.pl: correct readings for energy_kWh
|
||||
and energy_kWh_w (Boris, 06.01.08)
|
||||
- feature: global attr allowfrom, as wished by Holger (8.1.2008)
|
||||
- feature: FHT: multiple commands, softbuffer changes, cmd rename, doc
|
||||
- feature: EM1010PC: automatic reset
|
||||
- feature: contrib/00_LIRC.pm (25.3, by Bernhard)
|
||||
- bugfix : 00_FHZ: additional stty settings for strange Linux versions
|
||||
- bugfix : pgm2 wrong temp summary for FHT's (reported by O.D., 16.4.2008)
|
||||
- feature: FHEM modules may live on a filesystem with "ignorant" casing (FAT)
|
||||
- feature: FileLog "set reopen" for manual tweaking of logfiles.
|
||||
- feature: multiline commands are supported through the command line
|
||||
- feature: pgm2 installation changes, multiple instances, external css
|
||||
- feature: 87_ws2000.pm (thomas 10.05.08)
|
||||
- contrib: ws2000_reader.pl Standalone decoder and server (thomas 10.05.08)
|
||||
- doc: update fhem.html and commandline.html reflecting ws2000 and
|
||||
windows installation(thomas 10.05.08)
|
||||
- feature: add ReadyFn to fhem.pl in main loop to have an alternative for
|
||||
select, which is not working on windows (thomas 11.05)
|
||||
- feature: set timeout to 0.2s, if HandleTimeout returns undef=forever
|
||||
- bugfix : WS2000:fixed serial port access on windows by replacing FD with
|
||||
ReadyFn
|
||||
- bugfix : FileLog: dont use FH->sync on windows (not implemented there)
|
||||
- feature: EM, WS300, FHZ:Add Switch for Device::SerialPort and
|
||||
Win32::SerialPort to get it running in Windows (sorry, untested)
|
||||
- bugfix: FileLog undefined $data in FileLog_Get
|
||||
- feature: fhem.pl check modules for compiletime errors and do not initialize
|
||||
them
|
||||
- feature: M232 add windows support (thomas 12.05.08)
|
||||
- feature: add simple ELV IPWE1 support (thomas 12.05.08)
|
||||
- feature: FileLog get to read logfiles. Used heavily by webpgm2
|
||||
- feature: webpgm2: gnuplot-scroll mode to navigate/zoom in logfiles
|
||||
- bugfix: deleting FS20 device won't result in unknown device (Daniel, 11.7)
|
||||
- feature: webpgm2 generates SVG's from logs: no need for gnuplot
|
||||
- bugfix: examples corrected to work with current syntax
|
||||
|
||||
- 2008-08-04 (4.4)
|
||||
- feature: RM100-2 battery empty warning (mare 23.07.08)
|
||||
- feature: optimising the pgm2/SVG memory usage
|
||||
- feature: autoloading FHEM modules
|
||||
- bugfix: STATE/$value is carrying again the correct value
|
||||
- feature: enhancing the Makefile and the documentation
|
||||
- feature: 90_at is using now InternalTimer, subsecond precision added
|
||||
- feature: HMS100-FIT added (01.01.08 by Peter and 22.01.08 by Juergen)
|
||||
- feature: 91_watchdog added to handle the HMS100-FIT
|
||||
- feature: cum_kWh/cum_m3 added to EMWZ/EMGZ (11.01.08 by Peter)
|
||||
|
||||
- 2008-12-23 (4.5)
|
||||
- bugfix: further 01_FHEMWEB cleanup
|
||||
- feature: CUL support for FS20(r/w), FHT(readonly), KS300 and EM
|
||||
- feature: command list outputs the device attributes too
|
||||
- bugfix: rename bugs fixed
|
||||
- bugfix: better integration of ReadyFn (Windows), slight overall speedup
|
||||
- bugfix: Ignore/correct casing when autoloading modules
|
||||
- bugfix: at is executed twice after a modify (rufus99, 2008-09-10)
|
||||
- feature: FHT internal modifications (better protocol understanding)
|
||||
- feature: add timestamp to inform
|
||||
- feature: The strange stty settings in 00_FHEM.pm are optional
|
||||
- bugfix: webpgm2 iPhone fix
|
||||
- feature: fullinit and reopen commands for FHZ added (Boris 2008-11-01)
|
||||
- bugfix: undefined NotifyFn in fhem.pl (Boris 2008-11-01)
|
||||
- feature: new modules 00_CM11.pm and 20_X10.pm for integration of X10
|
||||
devices in fhem (Boris 2008-11-02)
|
||||
- feature: X10 support for pgm3 (Boris 2008-11-02)
|
||||
- bugfix: FHT short message warning
|
||||
- bugfix: rereadconfig crashes with active webpgm2 connections (2008-11-13)
|
||||
- bugfix: watchdog crash (2008-11-15)
|
||||
- bugfix: Strange call for nonexistent MyCUL: ReadFn
|
||||
- feature: webpgm2: gplot output goes to /tmp/gnuplot.err
|
||||
- feature: devspec TYPE,DEF,STATE. e.g. list TYPE:FS20, set DEF:123 on
|
||||
- bugfix: at schedules 2 events after the DST change (fix not verified)
|
||||
- feature: commandref.html reorg. There are now device sections.
|
||||
- feature: CUL / CUL_EM / CUL_WS documentation
|
||||
- feature: do not block fhem when the CUR is disconnected
|
||||
- bugfix: correct correction factors for EMEM in 15_CUL_EM.pm
|
||||
- bugfix: more stable CUL initialization
|
||||
- feature: reworked 15_CUL_EM.pm to account for timer wraparounds, more
|
||||
readings added
|
||||
- feature: speed gain through disabled refreshvalues query to all FHTs at
|
||||
definition; if you want it back at a "set myFHT report1 255
|
||||
report2 255" command to the config file.
|
||||
- feature: fhem commands may be added in modules. XmlList is external now.
|
||||
- bugfix: rereadcfg from webpgm2 does not crash fhem.pl
|
||||
- feature: jsonlist command from Martin (contrib/JsonList)
|
||||
- feature: contrib/rotateShiftWork from Martin
|
||||
- feature: contrib/fhem2speech from Martin
|
||||
- bugfix: attributes of at devices disappear
|
||||
- feature: attribute rainadjustment for KS300 (Boris 2008-12-17)
|
||||
- bugfix: deleting at / watchdog while active creates an empty device
|
||||
- feature: ExactId trigger added for wildcard HMS devices
|
||||
|
||||
- 2009-07-03 (4.6)
|
||||
- bugfix: fht actuator message clarification by Klaus
|
||||
- feature: getstate command from Martin (25.12)
|
||||
- bugfix: at drifts for relative timespecs
|
||||
- bugfix: Add IODev to CUL/EM/CUL_WS/HMS/KS300
|
||||
- bugfix: FileLog get (pgm2 plots) wont find the first row in the file
|
||||
- feature: 00_CUL: Answer CUR requests (status/time/fht)
|
||||
- bugfix: support for second correction factor for EMWZ in 15_CUL_EM added
|
||||
- feature: CUL further sets/gets added
|
||||
- feature: Removed msghist for multiple FHZ handling, IODev attribute added
|
||||
- bugfix: cut off string "(counter)" from fallback value in 13_KS300.pm
|
||||
- feature: daily/monthly cumulated values for EMWZ/EMGZ/EMWM with 15_CUL_EM
|
||||
- feature: 01_FHEMWEB.pm: multiple room assignments
|
||||
- feature: 01_FHEMWEB.pm: fixedrange with optional [day|week|month|year]
|
||||
- feature: 01_FHEMWEB.pm: attr title and label for flexible .gplot files
|
||||
- feature: fhem.pl: attr global logdir used by wildcard %ld
|
||||
- feature: do not block on disconnected devices (FHZ/CM11/CUL)
|
||||
- bugfix: deleting at definition in the at command
|
||||
- bugfix: deleting a notify/at/watchdog definition in a notify/at/watchdog
|
||||
- feature: devspec <attr>=<value>. E.g. set room=kitchen off; list disabled=
|
||||
- feature: Common Module calling for CUL/FHZ/CM11
|
||||
- feature: Store CUL sensitivity info
|
||||
- feature: avoid the "unknown/help me" message for unloaded devices
|
||||
- feature: structure module for large installations
|
||||
- feature: Cost Control in 15_CUL_EM (CostPerUnit, BasisFeePerMonth)
|
||||
- feature: add counter differential per time in 81_M232Counter.pm
|
||||
- feature: added USB compendium to documentation
|
||||
- feature: pgm3: Documentation for pgm3 updated, HMS100CO added (and bugfix)
|
||||
- bugfix: Defining a repeated at job in a sunrise/sunset at job fails
|
||||
- bugfix: FHT "summer" fix (avoiding a lot of syncnow)
|
||||
- feature: FHEMWEB modules added
|
||||
- feature: holiday module + doc + example + holiday2we attribute
|
||||
- bugfix: sunrise stuff fixed, doc missing
|
||||
- feature: CUL FHT sending added
|
||||
- bugfix: workaround to make M232 counter wraparound
|
||||
- feature: sequence module added
|
||||
- feature: Google Weather API support for FHEM (Boris 2009-06-01)
|
||||
- feature: lazy attribute for FHT devices (Boris 2009-06-09)
|
||||
- feature: tmpcorr attribute for FHT devices
|
||||
- feature: CUL_EM generates an event for each of the READINGS
|
||||
- feature: USF1000S support for FHEM added (Boris 2009-06-20)
|
||||
- feature: CUL supports HMS (culfw >= 1.22 needed)
|
||||
- feature: CUL shutdown procedure added
|
||||
- feature: 14_CUL_WS: better error checking
|
||||
- bugfix: webpgm2 multi line editing is working again
|
||||
|
||||
- 2009-10-23 (4.7)
|
||||
- bugfix: Reattached corrupted CUL device caused uninitialized message
|
||||
- bugfix: CUL/HMS changes, HMS cleanup
|
||||
- bugfix: EM/EMWZ/EMGZ set changed to work in FHEMWEB
|
||||
- bugfix: Avoid unitialized in xmllist for corrupt readings, reporter Boris
|
||||
- bugfix: Add binmode to 01_fhemweb.pm for windows
|
||||
- bugfix: Uniform check for windows, enable CUL for windows.
|
||||
- bugfix: CUL/HMS parsing patches from Peter
|
||||
- bugfix: Fixes for Windows by Klaus
|
||||
- bugfix: Another "rereadcfg" bugfix
|
||||
- feature: Update to the current (1.27) CUL FHT interface
|
||||
- feature: suppress inplausible readings from USF1000
|
||||
- feature: get time, fwrev, set reopen for CM11 (Boris 2009-09-12)
|
||||
- bugfix: FHZ_ReadAnswer bugfix for Windows (Klaus, 20.8.2009)
|
||||
- feature: CUL: device access code reorganized, TCP/IP support added (CUN)
|
||||
- feature: Pachube module from Axel
|
||||
- feature: dumpdef module from Axel in contrib
|
||||
- feature: javascripting support in FHEMWEB (Klaus/Axel)
|
||||
- feature: Module 09_BS.pm for brightness sensor added (Boris 2009-09-20)
|
||||
|
||||
- ==DATE== (4.8)
|
||||
- bugfix: loosing data when sending FS20 messages in a group
|
|
@ -1,745 +0,0 @@
|
|||
################################################################
|
||||
#
|
||||
# Copyright notice
|
||||
#
|
||||
# (c) 2008 Dr. Boris Neubert (omega@online.de)
|
||||
#
|
||||
# This script 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.
|
||||
#
|
||||
# The GNU General Public License can be found at
|
||||
# http://www.gnu.org/copyleft/gpl.html.
|
||||
# A copy is found in the textfile GPL.txt and important notices to the license
|
||||
# from the author is found in LICENSE.txt distributed with these scripts.
|
||||
#
|
||||
# This script 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.
|
||||
#
|
||||
# This copyright notice MUST APPEAR in all copies of the script!
|
||||
#
|
||||
################################################################
|
||||
|
||||
package main;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use Time::HiRes qw(gettimeofday);
|
||||
|
||||
|
||||
sub CM11_Write($$$);
|
||||
sub CM11_Read($);
|
||||
sub CM11_Ready($$);
|
||||
|
||||
my $msg_pollpc = pack("H*", "5a"); # interface poll signal (CM11->PC)
|
||||
my $msg_pollpcpf = pack("H*", "a5"); # power fail poll signal (CM11->PC)
|
||||
my $msg_pollack = pack("H*", "c3"); # response to poll signal (PC->CM11)
|
||||
my $msg_pollackpf= pack("H*", "fb"); # response to power fail poll signal (PC->CM11)
|
||||
my $msg_txok = pack("H*", "00"); # OK for transmission (PC->CM11)
|
||||
my $msg_ifrdy = pack("H*", "55"); # interface ready (CM11->PC)
|
||||
my $msg_statusrq = pack("H*", "8b"); # status request (PC->CM11)
|
||||
|
||||
my %housecodes_rcv = qw(0110 A 1110 B 0010 C 1010 D
|
||||
0001 E 1001 F 0101 G 1101 H
|
||||
0111 I 1111 J 0011 K 1011 L
|
||||
0000 M 1000 N 0100 O 1100 P);
|
||||
|
||||
my %unitcodes_rcv = qw(0110 1 1110 2 0010 3 1010 4
|
||||
0001 5 1001 6 0101 7 1101 8
|
||||
0111 9 1111 10 0011 11 1011 12
|
||||
0000 13 1000 14 0100 15 1100 16);
|
||||
|
||||
my %functions_rcv = qw(0000 ALL_UNITS_OFF
|
||||
0001 ALL_LIGHTS_ON
|
||||
0010 ON
|
||||
0011 OFF
|
||||
0100 DIM
|
||||
0101 BRIGHT
|
||||
0110 ALL_LIGHTS_OFF
|
||||
0111 EXTENDED_CODE
|
||||
1000 HAIL_REQUEST
|
||||
1001 HAIL_ACK
|
||||
1010 PRESET_DIM1
|
||||
1011 PRESET_DIM2
|
||||
1100 EXTENDED_DATA_TRANSFER
|
||||
1101 STATUS_ON
|
||||
1110 STATUS_OFF
|
||||
1111 STATUS_REQUEST);
|
||||
|
||||
|
||||
my %gets = (
|
||||
"fwrev" => "xxx",
|
||||
"time" => "xxx",
|
||||
);
|
||||
|
||||
my %sets = (
|
||||
"reopen" => "xxx",
|
||||
);
|
||||
|
||||
|
||||
#####################################
|
||||
|
||||
sub
|
||||
CM11_Initialize($)
|
||||
{
|
||||
my ($hash) = @_;
|
||||
|
||||
# Provider
|
||||
$hash->{ReadFn} = "CM11_Read";
|
||||
$hash->{WriteFn} = "CM11_Write";
|
||||
$hash->{Clients} = ":X10:";
|
||||
$hash->{ReadyFn} = "CM11_Ready";
|
||||
|
||||
# Normal Device
|
||||
$hash->{DefFn} = "CM11_Define";
|
||||
$hash->{UndefFn} = "CM11_Undef";
|
||||
$hash->{GetFn} = "CM11_Get";
|
||||
$hash->{SetFn} = "CM11_Set";
|
||||
$hash->{StateFn} = "CM11_SetState";
|
||||
$hash->{AttrList}= "do_not_notify:1,0 dummy:1,0 " .
|
||||
"model:CM11 loglevel:0,1,2,3,4,5,6";
|
||||
}
|
||||
#####################################
|
||||
sub
|
||||
CM11_DoInit($$$)
|
||||
{
|
||||
my ($name,$type,$po) = @_;
|
||||
my @init;
|
||||
|
||||
$po->reset_error();
|
||||
$po->baudrate(4800);
|
||||
$po->databits(8);
|
||||
$po->parity('none');
|
||||
$po->stopbits(1);
|
||||
$po->handshake('none');
|
||||
|
||||
if($type && $type eq "strangetty") {
|
||||
|
||||
# This part is for some Linux kernel versions whih has strange default
|
||||
# settings. Device::SerialPort is nice: if the flag is not defined for your
|
||||
# OS then it will be ignored.
|
||||
$po->stty_icanon(0);
|
||||
#$po->stty_parmrk(0); # The debian standard install does not have it
|
||||
$po->stty_icrnl(0);
|
||||
$po->stty_echoe(0);
|
||||
$po->stty_echok(0);
|
||||
$po->stty_echoctl(0);
|
||||
|
||||
# Needed for some strange distros
|
||||
$po->stty_echo(0);
|
||||
$po->stty_icanon(0);
|
||||
$po->stty_isig(0);
|
||||
$po->stty_opost(0);
|
||||
$po->stty_icrnl(0);
|
||||
}
|
||||
|
||||
$po->write_settings;
|
||||
$defs{$name}{STATE} = "Initialized";
|
||||
|
||||
}
|
||||
|
||||
|
||||
#####################################
|
||||
sub
|
||||
CM11_Reopen($)
|
||||
{
|
||||
my ($hash) = @_;
|
||||
|
||||
my $dev = $hash->{DeviceName};
|
||||
$hash->{PortObj}->close();
|
||||
Log 1, "Device $dev closed";
|
||||
for(;;) {
|
||||
sleep(5);
|
||||
if($^O =~ m/Win/) {
|
||||
$hash->{PortObj} = new Win32::SerialPort($dev);
|
||||
}else{
|
||||
$hash->{PortObj} = new Device::SerialPort($dev);
|
||||
}
|
||||
if($hash->{PortObj}) {
|
||||
Log 1, "Device $dev reopened";
|
||||
$hash->{FD} = $hash->{PortObj}->FILENO if($^O !~ m/Win/);
|
||||
CM11_DoInit($hash->{NAME}, $hash->{ttytype}, $hash->{PortObj});
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub
|
||||
CM11_Define($$)
|
||||
{
|
||||
my ($hash, $def) = @_;
|
||||
my @a = split("[ \t][ \t]*", $def);
|
||||
my $po;
|
||||
|
||||
return "wrong syntax: define <name> CM11 devicename ".
|
||||
"[normal|strangetty] [mobile]" if(@a < 3 || @a > 5);
|
||||
|
||||
|
||||
delete $hash->{PortObj};
|
||||
delete $hash->{FD};
|
||||
|
||||
my $name = $a[0];
|
||||
my $dev = $a[2];
|
||||
$hash->{ttytype} = $a[3] if($a[3]);
|
||||
$hash->{MOBILE} = 1 if($a[4] && $a[4] eq "mobile");
|
||||
$hash->{STATE} = "defined";
|
||||
|
||||
$attr{$name}{savefirst} = 1;
|
||||
|
||||
if($dev eq "none") {
|
||||
Log 1, "CM11 device is none, commands will be echoed only";
|
||||
$attr{$name}{dummy} = 1;
|
||||
return undef;
|
||||
}
|
||||
|
||||
$hash->{DeviceName} = $dev;
|
||||
$hash->{PARTIAL} = "";
|
||||
Log 3, "CM11 opening CM11 device $dev";
|
||||
if ($^O=~/Win/) {
|
||||
require Win32::SerialPort;
|
||||
$po = new Win32::SerialPort ($dev);
|
||||
} else {
|
||||
require Device::SerialPort;
|
||||
$po = new Device::SerialPort ($dev);
|
||||
}
|
||||
if(!$po) {
|
||||
my $msg = "Can't open $dev: $!";
|
||||
Log(3, $msg) if($hash->{MOBILE});
|
||||
return $msg if(!$hash->{MOBILE});
|
||||
$readyfnlist{"$name.$dev"} = $hash;
|
||||
return "";
|
||||
}
|
||||
Log 3, "CM11 opened CM11 device $dev";
|
||||
|
||||
$hash->{PortObj} = $po;
|
||||
if( $^O !~ /Win/ ) {
|
||||
$hash->{FD} = $po->FILENO;
|
||||
$selectlist{"$name.$dev"} = $hash;
|
||||
} else {
|
||||
$readyfnlist{"$name.$dev"} = $hash;
|
||||
}
|
||||
|
||||
CM11_DoInit($name, $hash->{ttytype}, $po);
|
||||
|
||||
#CM11_SetInterfaceTime($hash);
|
||||
#CM11_GetInterfaceStatus($hash);
|
||||
return undef;
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub
|
||||
CM11_Undef($$)
|
||||
{
|
||||
my ($hash, $arg) = @_;
|
||||
my $name = $hash->{NAME};
|
||||
|
||||
foreach my $d (sort keys %defs) {
|
||||
if(defined($defs{$d}) &&
|
||||
defined($defs{$d}{IODev}) &&
|
||||
$defs{$d}{IODev} == $hash)
|
||||
{
|
||||
my $lev = ($reread_active ? 4 : 2);
|
||||
Log GetLogLevel($name,$lev), "deleting port for $d";
|
||||
delete $defs{$d}{IODev};
|
||||
}
|
||||
}
|
||||
$hash->{PortObj}->close() if($hash->{PortObj});
|
||||
return undef;
|
||||
}
|
||||
|
||||
|
||||
#####################################
|
||||
sub
|
||||
CM11_SetState($$$$)
|
||||
{
|
||||
my ($hash, $tim, $vt, $val) = @_;
|
||||
return undef;
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub
|
||||
CM11_LogReadWrite($@)
|
||||
{
|
||||
my ($rw,$hash, $msg, $trlr) = @_;
|
||||
my $name= $hash->{NAME};
|
||||
Log GetLogLevel($name,5),
|
||||
"CM11 device " . $name . ": $rw " .
|
||||
sprintf("%2d: ", length($msg)) . unpack("H*", $msg);
|
||||
}
|
||||
|
||||
sub
|
||||
CM11_LogRead(@)
|
||||
{
|
||||
CM11_LogReadWrite("read ", @_);
|
||||
}
|
||||
|
||||
sub
|
||||
CM11_LogWrite(@)
|
||||
{
|
||||
CM11_LogReadWrite("write", @_);
|
||||
}
|
||||
|
||||
#####################################
|
||||
|
||||
sub
|
||||
CM11_SimpleWrite($$)
|
||||
{
|
||||
my ($hash, $msg) = @_;
|
||||
return if(!$hash || !defined($hash->{PortObj}));
|
||||
CM11_LogWrite($hash,$msg);
|
||||
$hash->{PortObj}->write($msg);
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub
|
||||
CM11_ReadDirect($$)
|
||||
{
|
||||
# This is a direct read for CM11_Write
|
||||
my ($hash,$arg) = @_;
|
||||
return undef if(!$hash || !defined($hash->{FD}));
|
||||
|
||||
my $name= $hash->{NAME};
|
||||
my $prefix= "CM11 device " . $name . ":";
|
||||
my $rin= '';
|
||||
my $nfound;
|
||||
|
||||
if($^O eq 'MSWin32') {
|
||||
$nfound= CM11_Ready($hash, undef);
|
||||
} else {
|
||||
vec($rin, $hash->{FD}, 1) = 1;
|
||||
my $to = 20; # seconds timeout (response might be damn slow)
|
||||
$to = $hash->{RA_Timeout} if($hash->{RA_Timeout}); # ...or less
|
||||
$nfound = select($rin, undef, undef, $to);
|
||||
if($nfound < 0) {
|
||||
next if ($! == EAGAIN() || $! == EINTR() || $! == 0);
|
||||
Log GetLogLevel($name,3), "$prefix Select error $nfound / $!";
|
||||
return undef;
|
||||
}
|
||||
}
|
||||
if(!$nfound) {
|
||||
Log GetLogLevel($name,3), "$prefix Timeout reading $arg";
|
||||
return undef;
|
||||
}
|
||||
|
||||
my $buf = $hash->{PortObj}->input();
|
||||
CM11_LogRead($hash,$buf);
|
||||
return $buf;
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub
|
||||
CM11_Write($$$)
|
||||
{
|
||||
# send two bytes, verify checksum, send ok
|
||||
my ($hash,$b1,$b2) = @_;
|
||||
my $name = $hash->{NAME};
|
||||
my $prefix= "CM11 device $name:";
|
||||
|
||||
if(!$hash || !defined($hash->{PortObj})) {
|
||||
Log GetLogLevel($name,3),
|
||||
"$prefix device is not active, cannot send";
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
# checksum
|
||||
my $b1d = unpack('C', $b1);
|
||||
my $b2d = unpack('C', $b2);
|
||||
my $checksum_w = ($b1d + $b2d) & 0xff;
|
||||
|
||||
my $data;
|
||||
|
||||
# try 5 times to send
|
||||
my $try= 5;
|
||||
for(;;) {
|
||||
$try--;
|
||||
# send two bytes
|
||||
$data= $b1 . $b2;
|
||||
CM11_LogWrite($hash,$data);
|
||||
$hash->{PortObj}->write($data);
|
||||
|
||||
# get checksum
|
||||
my $checksum= CM11_ReadDirect($hash, "checksum");
|
||||
return 0 if(!defined($checksum)); # read failure
|
||||
|
||||
my $checksum_r= unpack('C', $checksum);
|
||||
if($checksum_w ne $checksum_r) {
|
||||
Log 5,
|
||||
"$prefix wrong checksum (send: $checksum_w, received: $checksum_r)";
|
||||
return 0 if(!$try);
|
||||
my $nexttry= 6-$try;
|
||||
Log 5,
|
||||
"$prefix retrying (" . $nexttry . "/5)";
|
||||
} else {
|
||||
Log 5, "$prefix checksum correct, OK for transmission";
|
||||
last;
|
||||
}
|
||||
}
|
||||
|
||||
# checksum ok => send OK for transmission
|
||||
$data= $msg_txok;
|
||||
CM11_LogWrite($hash,$data);
|
||||
$hash->{PortObj}->write($data);
|
||||
my $ready= CM11_ReadDirect($hash, "ready");
|
||||
return 0 if(!defined($ready)); # read failure
|
||||
if($ready ne $msg_ifrdy) {
|
||||
Log GetLogLevel($name,3),
|
||||
"$prefix strange ready signal (" . unpack('C', $ready) . ")";
|
||||
return 0
|
||||
} else {
|
||||
Log 5, "$prefix ready";
|
||||
}
|
||||
|
||||
# we are fine
|
||||
return 1;
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub
|
||||
CM11_GetInterfaceStatus($)
|
||||
{
|
||||
my ($hash)= @_;
|
||||
|
||||
CM11_SimpleWrite($hash, $msg_statusrq);
|
||||
my $statusmsg= "";
|
||||
while(length($statusmsg)<14) {
|
||||
my $buf= CM11_ReadDirect($hash, "status");
|
||||
return if(!defined($buf)); # read error
|
||||
$statusmsg.= $buf;
|
||||
}
|
||||
return $statusmsg;
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub CM11_Get($@)
|
||||
{
|
||||
my ($hash, @a) = @_;
|
||||
|
||||
return "CM11: get needs only one parameter" if(@a != 2);
|
||||
return "Unknown argument $a[1], choose one of " . join(",", sort keys %gets)
|
||||
if(!defined($gets{$a[1]}));
|
||||
|
||||
my ($fn, $arg) = split(" ", $gets{$a[1]});
|
||||
|
||||
my $v = join(" ", @a);
|
||||
my $name = $hash->{NAME};
|
||||
Log GetLogLevel($name,2), "CM11 get $v";
|
||||
|
||||
my $statusmsg= CM11_GetInterfaceStatus($hash);
|
||||
if(!defined($statusmsg)) {
|
||||
$v= "error";
|
||||
Log 2, "CM11 error, device is irresponsive."
|
||||
} else {
|
||||
my $msg= unpack("H*", $statusmsg);
|
||||
Log 5, "CM11 got ". $msg;
|
||||
|
||||
if($a[1] eq "fwrev") {
|
||||
$v = hex(substr($msg, 14, 1));
|
||||
} elsif($a[1] eq "time") {
|
||||
my $sec= hex(substr($msg, 4, 2));
|
||||
my $hour= hex(substr($msg, 8, 2))*2;
|
||||
my $min= hex(substr($msg, 6, 2));
|
||||
if($min>59) {
|
||||
$min-= 60;
|
||||
$hour++;
|
||||
}
|
||||
my $day= hex(substr($msg, 10, 2));
|
||||
$day+= 256 if(hex(substr($msg, 12, 1)) & 0xf);
|
||||
$v= sprintf("%d.%02d:%02d:%02d", $day,$hour,$min,$sec);
|
||||
}
|
||||
}
|
||||
$hash->{READINGS}{$a[1]}{VAL} = $v;
|
||||
$hash->{READINGS}{$a[1]}{TIME} = TimeNow();
|
||||
|
||||
return "$a[0] $a[1] => $v";
|
||||
}
|
||||
|
||||
|
||||
#####################################
|
||||
sub
|
||||
CM11_Set($@)
|
||||
{
|
||||
my ($hash, @a) = @_;
|
||||
|
||||
return "CM11: set needs one parameter" if(@a != 2);
|
||||
return "Unknown argument $a[1], choose one of " . join(" ", sort keys %sets)
|
||||
if(!defined($sets{$a[1]}));
|
||||
|
||||
my ($fn, $arg) = split(" ", $sets{$a[1]});
|
||||
|
||||
my $v = join(" ", @a);
|
||||
my $name = $hash->{NAME};
|
||||
Log GetLogLevel($name,2), "CM11 set $v";
|
||||
|
||||
if($a[1] eq "reopen") {
|
||||
CM11_Reopen($hash);
|
||||
}
|
||||
|
||||
return undef;
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub
|
||||
CM11_SetInterfaceTime($)
|
||||
{
|
||||
my ($hash)= @_;
|
||||
|
||||
# 7 Bytes, Bits 0..55 are
|
||||
# 55 to 48 timer download header (0x9b)
|
||||
# 47 to 40 Current time (seconds)
|
||||
# 39 to 32 Current time (minutes ranging from 0 to 119)
|
||||
# 31 to 23 Current time (hours/2, ranging from 0 to 11)
|
||||
# 23 to 16 Current year day (bits 0 to 7)
|
||||
# 15 Current year day (bit 8)
|
||||
# 14 to 8 Day mask (SMTWTFS)
|
||||
# 7 to 4 Monitored house code
|
||||
# 3 Reserved
|
||||
# 2 Battery timer clear flag
|
||||
# 1 Monitored status clear flag
|
||||
# 0 Timer purge flag
|
||||
|
||||
# make the interface happy (time is set to zero)
|
||||
my $data = pack('C7', 0x9b,0x00,0x00,0x00,0x00,0x00,0x03);
|
||||
CM11_SimpleWrite($hash, $data);
|
||||
# get checksum (ignored)
|
||||
my $checksum= CM11_ReadDirect($hash, "checksum");
|
||||
return 0 if(!defined($checksum)); # read failure
|
||||
# tx OK
|
||||
CM11_SimpleWrite($hash, $msg_txok);
|
||||
# get ready (ignored)
|
||||
my $ready= CM11_ReadDirect($hash, "ready");
|
||||
return 0 if(!defined($ready)); # read failure
|
||||
return 1;
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub
|
||||
CM11_Read($)
|
||||
{
|
||||
#
|
||||
# prolog
|
||||
#
|
||||
|
||||
my ($hash) = @_;
|
||||
|
||||
my $buf = $hash->{PortObj}->input();
|
||||
my $name = $hash->{NAME};
|
||||
|
||||
# prefix for logging
|
||||
my $prefix= "CM11 device " . $name . ":";
|
||||
|
||||
# Lets' try again: Some drivers return len(0) on the first read...
|
||||
if(defined($buf) && length($buf) == 0) {
|
||||
$buf = $hash->{PortObj}->input();
|
||||
}
|
||||
|
||||
# USB troubleshooting
|
||||
if(!defined($buf) || length($buf) == 0) {
|
||||
my $dev = $hash->{DeviceName};
|
||||
Log 1, "USB device $dev disconnected, waiting to reappear";
|
||||
$hash->{PortObj}->close();
|
||||
DoTrigger($name, "DISCONNECTED");
|
||||
|
||||
delete($hash->{PortObj});
|
||||
delete($selectlist{"$name.$dev"});
|
||||
$readyfnlist{"$name.$dev"} = $hash; # Start polling
|
||||
$hash->{STATE} = "disconnected";
|
||||
|
||||
# Without the following sleep the open of the device causes a SIGSEGV,
|
||||
# and following opens block infinitely. Only a reboot helps.
|
||||
sleep(5);
|
||||
}
|
||||
|
||||
#
|
||||
# begin of message digesting
|
||||
#
|
||||
|
||||
# concatenate yet unparsed message and newly received data
|
||||
my $x10data = $hash->{PARTIAL} . $buf;
|
||||
CM11_LogRead($hash,$buf);
|
||||
Log 5, "$prefix Data: " . unpack('H*',$x10data);
|
||||
|
||||
# normally the while loop will run only once
|
||||
while(length($x10data) > 0) {
|
||||
|
||||
# we cut off everything before the latest poll signal
|
||||
my $p= index(reverse($x10data), $msg_pollpc);
|
||||
if($p<0) { $p= index(reverse($x10data), $msg_pollpcpf); }
|
||||
if($p>=0) { $x10data= substr($x10data, -$p-1); }
|
||||
|
||||
# to start with, a single 0x5a is received
|
||||
if( substr($x10data,0,1) eq $msg_pollpc ) { # CM11 polls PC
|
||||
Log 5, "$prefix start of message";
|
||||
CM11_SimpleWrite($hash, $msg_pollack); # PC ready
|
||||
$x10data= substr($x10data,1); # $x10data now empty
|
||||
next;
|
||||
}
|
||||
|
||||
# experimental code follows
|
||||
#if( substr($x10data,0,2) eq pack("H*", "98e6") ) { # CM11 polls PC
|
||||
# Log 5, "$prefix 98e6";
|
||||
# CM11_SimpleWrite($hash, $msg_pollack); # PC ready
|
||||
# $x10data= "";
|
||||
# next;
|
||||
#}
|
||||
#if( substr($x10data,0,1) eq pack("H*", "98") ) { # CM11 polls PC
|
||||
# Log 5, "$prefix 98";
|
||||
# next;
|
||||
#}
|
||||
|
||||
# a single 0xa5 is a power-fail macro download poll
|
||||
if( substr($x10data,0,1) eq $msg_pollpcpf ) { # CM11 polls PC
|
||||
Log 5, "$prefix power-fail poll";
|
||||
# the documentation wrongly says that the macros should be downloaded
|
||||
# in fact, the time must be set!
|
||||
if(CM11_SetInterfaceTime($hash)) {
|
||||
Log 5, "$prefix power-fail poll satisfied";
|
||||
} else {
|
||||
Log 5, "$prefix power-fail poll satisfaction failed";
|
||||
}
|
||||
$x10data= substr($x10data,1); # $x10data now empty
|
||||
next;
|
||||
}
|
||||
|
||||
# a single 0x55 is a leftover from a failed transmission
|
||||
if( substr($x10data,0,1) eq $msg_ifrdy ) { # CM11 polls PC
|
||||
Log 5, "$prefix skipping leftover ready signal";
|
||||
$x10data= substr($x10data,1);
|
||||
next;
|
||||
}
|
||||
|
||||
# the message comes in small chunks of 1 or few bytes instead of the
|
||||
# whole buffer at once
|
||||
my $len= ord(substr($x10data,0,1))-1; # upload buffer size
|
||||
last if(length($x10data)< $len+2); # wait for complete msg
|
||||
|
||||
# message is now complete, start interpretation
|
||||
|
||||
# mask: Bits 0 (LSB)..7 (MSB) correspond to data bytes 0..7
|
||||
# bit= 0: unitcode, bit= 1: function
|
||||
my $mask= unpack('B8', substr($x10data,1,1));
|
||||
$x10data= substr($x10data,2); # cut off length and mask
|
||||
|
||||
# $x10data now contains $len data bytes
|
||||
my $databytes= unpack('H*', substr($x10data,0));
|
||||
Log 5, "$prefix message complete " .
|
||||
"(length $len, mask $mask, data $databytes)";
|
||||
|
||||
# the following lines decode the messages into unitcodes and functions
|
||||
# in general we have 0..n unitcodes followed by 1..m functions in the
|
||||
# message
|
||||
my $i= 0;
|
||||
my $dmsg= "";
|
||||
while($i< $len) {
|
||||
|
||||
my $data= substr($x10data, $i);
|
||||
my $bits = unpack('B8', $data);
|
||||
my $nibble_hi = substr($bits, 0, 4);
|
||||
my $nibble_lo = substr($bits, 4, 4);
|
||||
|
||||
my $housecode= $housecodes_rcv{$nibble_hi};
|
||||
|
||||
# one hash for unitcodes X_UNIT and one hash for functions
|
||||
# X_FUNC is maintained per housecode X= A..P
|
||||
my $housecode_unit= $housecode . "_UNIT";
|
||||
my $housecode_func= $housecode . "_FUNC";
|
||||
|
||||
my $isfunc= (substr($mask, -$i-1, 1));
|
||||
if($isfunc) {
|
||||
# data byte is function
|
||||
my $x10func= $functions_rcv{$nibble_lo};
|
||||
if(($x10func eq "DIM") || ($x10func eq "BRIGHT")) {
|
||||
my $level= ord(substr($x10data, ++$i));
|
||||
$x10func.= " $level";
|
||||
}
|
||||
elsif($x10func eq "EXTENDED_DATA_TRANSFER") {
|
||||
$data= substr($x10data, 2+(++$i));
|
||||
my $command= substr($x10data, ++$i);
|
||||
$x10func.= unpack("H*", $data) . ":" .
|
||||
unpack("H*", $command);
|
||||
}
|
||||
$hash->{$housecode_func}= $x10func;
|
||||
Log 5, "$prefix $housecode_func: " .
|
||||
$hash->{$housecode_func};
|
||||
# dispatch message to clients
|
||||
|
||||
my $hu = $hash->{$housecode_unit};
|
||||
$hu= "" unless(defined($hu));
|
||||
my $hf = $hash->{$housecode_func};
|
||||
my $dmsg= "X10:$housecode;$hu;$hf";
|
||||
Dispatch($hash, $dmsg);
|
||||
} else {
|
||||
# data byte is unitcode
|
||||
# if a command was executed before, clear unitcode list
|
||||
if(defined($hash->{$housecode_func})) {
|
||||
undef $hash->{$housecode_unit};
|
||||
undef $hash->{$housecode_func};
|
||||
}
|
||||
# get unitcode of unitcode
|
||||
my $unitcode= $unitcodes_rcv{$nibble_lo};
|
||||
# append to list of unitcodes
|
||||
my $unitcodes= $hash->{$housecode_unit};
|
||||
if(defined($hash->{$housecode_unit})) {
|
||||
$unitcodes= $hash->{$housecode_unit} . " ";
|
||||
} else {
|
||||
$unitcodes= "";
|
||||
}
|
||||
$hash->{$housecode_unit}= "$unitcodes$unitcode";
|
||||
Log 5, "$prefix $housecode_unit: " .
|
||||
$hash->{$housecode_unit};
|
||||
}
|
||||
$i++;
|
||||
}
|
||||
$x10data= '';
|
||||
}
|
||||
|
||||
$hash->{PARTIAL} = $x10data;
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub
|
||||
CM11_Ready($$)
|
||||
{
|
||||
my ($hash, $dev) = @_;
|
||||
my $po=$hash->{PortObj};
|
||||
|
||||
if(!$po) { # Looking for the device
|
||||
|
||||
my $dev = $hash->{DeviceName};
|
||||
my $name = $hash->{NAME};
|
||||
|
||||
$hash->{PARTIAL} = "";
|
||||
if ($^O=~/Win/) {
|
||||
$po = new Win32::SerialPort ($dev);
|
||||
} else {
|
||||
$po = new Device::SerialPort ($dev);
|
||||
}
|
||||
return undef if(!$po);
|
||||
|
||||
Log 1, "USB device $dev reappeared";
|
||||
$hash->{PortObj} = $po;
|
||||
if( $^O !~ /Win/ ) {
|
||||
$hash->{FD} = $po->FILENO;
|
||||
delete($readyfnlist{"$name.$dev"});
|
||||
$selectlist{"$name.$dev"} = $hash;
|
||||
} else {
|
||||
$readyfnlist{"$name.$dev"} = $hash;
|
||||
}
|
||||
|
||||
CM11_DoInit($name, $hash->{ttytype}, $po);
|
||||
DoTrigger($name, "CONNECTED");
|
||||
return undef;
|
||||
|
||||
}
|
||||
|
||||
# This is relevant for windows only
|
||||
return undef if !$po;
|
||||
my ($BlockingFlags, $InBytes, $OutBytes, $ErrorFlags)=$po->status;
|
||||
return ($InBytes>0);
|
||||
}
|
||||
|
||||
1;
|
|
@ -1,994 +0,0 @@
|
|||
##############################################
|
||||
|
||||
package main;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use Time::HiRes qw(gettimeofday);
|
||||
|
||||
|
||||
sub CUL_Clear($);
|
||||
sub CUL_Write($$$);
|
||||
sub CUL_Read($);
|
||||
sub CUL_ReadAnswer($$$);
|
||||
sub CUL_Ready($);
|
||||
sub CUL_HandleCurRequest($$);
|
||||
sub CUL_HandleWriteQueue($);
|
||||
|
||||
sub CUL_OpenDev($$);
|
||||
sub CUL_CloseDev($);
|
||||
sub CUL_SimpleWrite(@);
|
||||
sub CUL_SimpleRead($);
|
||||
|
||||
my $initstr = "X21"; # Only translated messages + RSSI
|
||||
my %gets = (
|
||||
"version" => "V",
|
||||
"raw" => "",
|
||||
"ccconf" => "=",
|
||||
"uptime" => "t",
|
||||
"file" => "",
|
||||
"time" => "c03",
|
||||
"fhtbuf" => "T03"
|
||||
);
|
||||
|
||||
my %sets = (
|
||||
"raw" => "",
|
||||
"freq" => "",
|
||||
"bWidth" => "",
|
||||
"rAmpl" => "",
|
||||
"sens" => "",
|
||||
"verbose" => "X",
|
||||
"led" => "l",
|
||||
"patable" => "x",
|
||||
"file" => "",
|
||||
"time" => ""
|
||||
);
|
||||
|
||||
my @ampllist = (24, 27, 30, 33, 36, 38, 40, 42);
|
||||
|
||||
sub
|
||||
CUL_Initialize($)
|
||||
{
|
||||
my ($hash) = @_;
|
||||
|
||||
# Provider
|
||||
$hash->{ReadFn} = "CUL_Read";
|
||||
$hash->{WriteFn} = "CUL_Write";
|
||||
$hash->{Clients} = ":FS20:FHT:KS300:CUL_EM:CUL_WS:USF1000:HMS:";
|
||||
my %mc = (
|
||||
"1:USF1000" => "^81..(04|0c)..0101a001a5ceaa00....",
|
||||
"2:FS20" => "^81..(04|0c)..0101a001",
|
||||
"3:FHT" => "^81..(04|09|0d)..(0909a001|83098301|c409c401)..",
|
||||
"4:KS300" => "^810d04..4027a001",
|
||||
"5:CUL_WS" => "^K.....",
|
||||
"6:CUL_EM" => "^E0.................\$",
|
||||
"7:HMS" => "^810e04....(1|5|9).a001",
|
||||
);
|
||||
$hash->{MatchList} = \%mc;
|
||||
$hash->{ReadyFn} = "CUL_Ready";
|
||||
|
||||
# Normal devices
|
||||
$hash->{DefFn} = "CUL_Define";
|
||||
$hash->{UndefFn} = "CUL_Undef";
|
||||
$hash->{GetFn} = "CUL_Get";
|
||||
$hash->{SetFn} = "CUL_Set";
|
||||
$hash->{StateFn} = "CUL_SetState";
|
||||
$hash->{AttrList}= "do_not_notify:1,0 dummy:1,0 filtertimeout " .
|
||||
"showtime:1,0 model:CUL,CUR loglevel:0,1,2,3,4,5,6 " .
|
||||
"CUR_id_list";
|
||||
$hash->{ShutdownFn} = "CUL_Shutdown";
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub
|
||||
CUL_Define($$)
|
||||
{
|
||||
my ($hash, $def) = @_;
|
||||
my @a = split("[ \t][ \t]*", $def);
|
||||
|
||||
return "wrong syntax: define <name> CUL devicename <FHTID>"
|
||||
if(@a < 4 || @a > 5);
|
||||
|
||||
CUL_CloseDev($hash);
|
||||
|
||||
my $name = $a[0];
|
||||
my $dev = $a[2];
|
||||
return "FHTID must be H1H2, with H1 and H2 hex and both smaller than 64"
|
||||
if(uc($a[3]) !~ m/^[0-6][0-9A-F][0-6][0-9A-F]$/);
|
||||
$hash->{FHTID} = uc($a[3]);
|
||||
|
||||
$attr{$name}{savefirst} = 1;
|
||||
|
||||
if($dev eq "none") {
|
||||
Log 1, "CUL device is none, commands will be echoed only";
|
||||
$attr{$name}{dummy} = 1;
|
||||
return undef;
|
||||
}
|
||||
|
||||
$hash->{DeviceName} = $dev;
|
||||
my $ret = CUL_OpenDev($hash, 0);
|
||||
return $ret;
|
||||
}
|
||||
|
||||
|
||||
#####################################
|
||||
sub
|
||||
CUL_Undef($$)
|
||||
{
|
||||
my ($hash, $arg) = @_;
|
||||
my $name = $hash->{NAME};
|
||||
|
||||
foreach my $d (sort keys %defs) {
|
||||
if(defined($defs{$d}) &&
|
||||
defined($defs{$d}{IODev}) &&
|
||||
$defs{$d}{IODev} == $hash)
|
||||
{
|
||||
my $lev = ($reread_active ? 4 : 2);
|
||||
Log GetLogLevel($name,$lev), "deleting port for $d";
|
||||
delete $defs{$d}{IODev};
|
||||
}
|
||||
}
|
||||
|
||||
CUL_SimpleWrite($hash, "X00"); # Switch reception off, it may hang up the CUL
|
||||
CUL_CloseDev($hash);
|
||||
return undef;
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub
|
||||
CUL_Shutdown($)
|
||||
{
|
||||
my ($hash) = @_;
|
||||
CUL_SimpleWrite($hash, "X00") if(!CUL_isCUR($hash));
|
||||
return undef;
|
||||
}
|
||||
|
||||
sub
|
||||
CUL_isCUR($)
|
||||
{
|
||||
my ($hash) = @_;
|
||||
return ($hash->{VERSION} && $hash->{VERSION} =~ m/CUR/);
|
||||
}
|
||||
|
||||
|
||||
#####################################
|
||||
sub
|
||||
CUL_Set($@)
|
||||
{
|
||||
my ($hash, @a) = @_;
|
||||
|
||||
return "\"set CUL\" needs at least one parameter" if(@a < 2);
|
||||
return "Unknown argument $a[1], choose one of " . join(" ", sort keys %sets)
|
||||
if(!defined($sets{$a[1]}));
|
||||
|
||||
my $name = shift @a;
|
||||
my $type = shift @a;
|
||||
my $arg = join("", @a);
|
||||
|
||||
if($type eq "freq") { # MHz
|
||||
|
||||
my $f = $arg/26*65536;
|
||||
|
||||
my $f2 = sprintf("%02x", $f / 65536);
|
||||
my $f1 = sprintf("%02x", int($f % 65536) / 256);
|
||||
my $f0 = sprintf("%02x", $f % 256);
|
||||
$arg = sprintf("%.3f", (hex($f2)*65536+hex($f1)*256+hex($f0))/65536*26);
|
||||
my $msg = "Setting FREQ2..0 (0D,0E,0F) to $f2 $f1 $f0 = $arg MHz";
|
||||
Log GetLogLevel($name,4), $msg;
|
||||
CUL_SimpleWrite($hash, "W0F$f2");
|
||||
CUL_SimpleWrite($hash, "W10$f1");
|
||||
CUL_SimpleWrite($hash, "W11$f0");
|
||||
CUL_SimpleWrite($hash, $initstr); # Will reprogram the CC1101
|
||||
return $msg;
|
||||
|
||||
} elsif($type eq "bWidth") { # KHz
|
||||
|
||||
my ($err, $ob);
|
||||
if(!IsDummy($hash->{NAME})) {
|
||||
CUL_SimpleWrite($hash, "C10");
|
||||
($err, $ob) = CUL_ReadAnswer($hash, $type, 0);
|
||||
return "Can't get old MDMCFG4 value" if($err || $ob !~ m,/ (.*)\r,);
|
||||
$ob = $1 & 0x0f;
|
||||
}
|
||||
|
||||
my ($bits, $bw) = (0,0);
|
||||
for (my $e = 0; $e < 4; $e++) {
|
||||
for (my $m = 0; $m < 4; $m++) {
|
||||
$bits = ($e<<6)+($m<<4);
|
||||
$bw = int(26000/(8 * (4+$m) * (1 << $e))); # KHz
|
||||
goto GOTBW if($arg >= $bw);
|
||||
}
|
||||
}
|
||||
|
||||
GOTBW:
|
||||
$ob = sprintf("%02x", $ob+$bits);
|
||||
my $msg = "Setting MDMCFG4 (10) to $ob = $bw KHz";
|
||||
|
||||
Log GetLogLevel($name,4), $msg;
|
||||
CUL_SimpleWrite($hash, "W12$ob");
|
||||
CUL_SimpleWrite($hash, $initstr);
|
||||
return $msg;
|
||||
|
||||
} elsif($type eq "rAmpl") { # dB
|
||||
|
||||
return "a numerical value between 24 and 42 is expected"
|
||||
if($arg !~ m/^\d+$/ || $arg < 24 || $arg > 42);
|
||||
my ($v, $w);
|
||||
for($v = 0; $v < @ampllist; $v++) {
|
||||
last if($ampllist[$v] > $arg);
|
||||
}
|
||||
$v = sprintf("%02d", $v-1);
|
||||
$w = $ampllist[$v];
|
||||
my $msg = "Setting AGCCTRL2 (1B) to $v / $w dB";
|
||||
CUL_SimpleWrite($hash, "W1D$v");
|
||||
CUL_SimpleWrite($hash, $initstr);
|
||||
return $msg;
|
||||
|
||||
} elsif($type eq "sens") { # dB
|
||||
|
||||
return "a numerical value between 4 and 16 is expected"
|
||||
if($arg !~ m/^\d+$/ || $arg < 4 || $arg > 16);
|
||||
my $w = int($arg/4)*4;
|
||||
my $v = sprintf("9%d",$arg/4-1);
|
||||
my $msg = "Setting AGCCTRL0 (1D) to $v / $w dB";
|
||||
CUL_SimpleWrite($hash, "W1F$v");
|
||||
CUL_SimpleWrite($hash, $initstr);
|
||||
return $msg;
|
||||
|
||||
} elsif($type eq "file") {
|
||||
|
||||
return "Only supported for CUR devices (see VERSION)" if(!CUL_isCUR($hash));
|
||||
|
||||
return "$name: Need 2 further arguments: source destination"
|
||||
if(@a != 2);
|
||||
my ($buf, $msg, $err);
|
||||
return "$a[0]: $!" if(!open(FH, $a[0]));
|
||||
$buf = join("", <FH>);
|
||||
close(FH);
|
||||
|
||||
my $len = length($buf);
|
||||
CUL_Clear($hash);
|
||||
CUL_SimpleWrite($hash, "X00");
|
||||
|
||||
CUL_SimpleWrite($hash, sprintf("w%08X$a[1]", $len));
|
||||
($err, $msg) = CUL_ReadAnswer($hash, $type, 1);
|
||||
goto WRITEEND if($err);
|
||||
if($msg ne sprintf("%08X\r\n", $len)) {
|
||||
$err = "Bogus length received: $msg";
|
||||
goto WRITEEND;
|
||||
}
|
||||
|
||||
my $off = 0;
|
||||
while($off < $len) {
|
||||
my $mlen = ($len-$off) > 32 ? 32 : ($len-$off);
|
||||
CUL_SimpleWrite($hash, substr($buf,$off,$mlen), 1);
|
||||
$off += $mlen;
|
||||
}
|
||||
|
||||
WRITEEND:
|
||||
CUL_SimpleWrite($hash, $initstr);
|
||||
return "$name: $err" if($err);
|
||||
|
||||
} elsif($type eq "time") {
|
||||
|
||||
return "Only supported for CUR devices (see VERSION)" if(!CUL_isCUR($hash));
|
||||
my @a = localtime;
|
||||
my $msg = sprintf("c%02d%02d%02d", $a[2],$a[1],$a[0]);
|
||||
CUL_SimpleWrite($hash, $msg);
|
||||
|
||||
} else {
|
||||
|
||||
return "Expecting a 0-padded hex number"
|
||||
if((length($arg)&1) == 1 && $type ne "raw");
|
||||
$initstr = "X$arg" if($type eq "verbose");
|
||||
Log GetLogLevel($name,4), "set $name $type $arg";
|
||||
CUL_SimpleWrite($hash, $sets{$type} . $arg);
|
||||
|
||||
}
|
||||
return undef;
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub
|
||||
CUL_Get($@)
|
||||
{
|
||||
my ($hash, @a) = @_;
|
||||
|
||||
return "\"get CUL\" needs at least one parameter" if(@a < 2);
|
||||
return "Unknown argument $a[1], choose one of " . join(" ", sort keys %gets)
|
||||
if(!defined($gets{$a[1]}));
|
||||
|
||||
my $arg = ($a[2] ? $a[2] : "");
|
||||
my ($msg, $err);
|
||||
my $name = $a[0];
|
||||
|
||||
return "No $a[1] for dummies" if(IsDummy($name));
|
||||
|
||||
if($a[1] eq "ccconf") {
|
||||
|
||||
my %r = ( "0D"=>1,"0E"=>1,"0F"=>1,"10"=>1,"1B"=>1,"1D"=>1 );
|
||||
foreach my $a (sort keys %r) {
|
||||
CUL_SimpleWrite($hash, "C$a");
|
||||
($err, $msg) = CUL_ReadAnswer($hash, "C$a", 0);
|
||||
return $err if($err);
|
||||
my @answ = split(" ", $msg);
|
||||
$r{$a} = $answ[4];
|
||||
}
|
||||
$msg = sprintf("freq:%.3fMHz bWidth:%dKHz rAmpl:%ddB sens:%ddB",
|
||||
26*(($r{"0D"}*256+$r{"0E"})*256+$r{"0F"})/65536, #Freq
|
||||
26000/(8 * (4+(($r{"10"}>>4)&3)) * (1 << (($r{"10"}>>6)&3))), #Bw
|
||||
$ampllist[$r{"1B"}],
|
||||
4+4*($r{"1D"}&3) #Sens
|
||||
);
|
||||
|
||||
} elsif($a[1] eq "file") {
|
||||
|
||||
return "Only supported for CUR devices (see VERSION)" if(!CUL_isCUR($hash));
|
||||
|
||||
CUL_Clear($hash);
|
||||
CUL_SimpleWrite($hash, "X00");
|
||||
|
||||
if(int(@a) == 2) { # No argument: List directory
|
||||
|
||||
CUL_SimpleWrite($hash, "r.");
|
||||
($err, $msg) = CUL_ReadAnswer($hash, $a[1], 0);
|
||||
goto READEND if($err);
|
||||
|
||||
$msg =~ s/[\r\n]//g;
|
||||
my @a;
|
||||
foreach my $f (split(" ", $msg)) {
|
||||
my ($name, $size) = split("/", $f);
|
||||
push @a, sprintf("%-14s %5d", $name, hex($size));
|
||||
}
|
||||
$msg = join("\n", @a);
|
||||
|
||||
} else { # Read specific file
|
||||
|
||||
if(@a != 4) {
|
||||
$err = "Need 2 further arguments: source [destination|-]";
|
||||
goto READEND;
|
||||
}
|
||||
|
||||
CUL_SimpleWrite($hash, "r$a[2]");
|
||||
($err, $msg) = CUL_ReadAnswer($hash, $a[1], 0);
|
||||
goto READEND if($err);
|
||||
|
||||
if($msg eq "X") {
|
||||
$err = "$a[2]: file not found on CUL";
|
||||
goto READEND if($err);
|
||||
}
|
||||
$msg =~ s/[\r\n]//g;
|
||||
my ($len, $buf) = (hex($msg), "");
|
||||
$msg = "";
|
||||
while(length($msg) != $len) {
|
||||
($err, $buf) = CUL_ReadAnswer($hash, $a[1], 1);
|
||||
goto READEND if($err);
|
||||
$msg .= $buf;
|
||||
}
|
||||
|
||||
if($a[3] ne "-") {
|
||||
if(!open(FH, ">$a[3]")) {
|
||||
$err = "$a[3]: $!";
|
||||
goto READEND;
|
||||
}
|
||||
print FH $msg;
|
||||
close(FH);
|
||||
$msg = "";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
READEND:
|
||||
CUL_SimpleWrite($hash, $initstr);
|
||||
return "$name: $err" if($err);
|
||||
return $msg;
|
||||
|
||||
} else {
|
||||
|
||||
CUL_SimpleWrite($hash, $gets{$a[1]} . $arg);
|
||||
($err, $msg) = CUL_ReadAnswer($hash, $a[1], 0);
|
||||
$msg = "No answer" if(!defined($msg));
|
||||
$msg =~ s/[\r\n]//g;
|
||||
|
||||
}
|
||||
|
||||
$hash->{READINGS}{$a[1]}{VAL} = $msg;
|
||||
$hash->{READINGS}{$a[1]}{TIME} = TimeNow();
|
||||
|
||||
return "$a[0] $a[1] => $msg";
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub
|
||||
CUL_SetState($$$$)
|
||||
{
|
||||
my ($hash, $tim, $vt, $val) = @_;
|
||||
return undef;
|
||||
}
|
||||
|
||||
sub
|
||||
CUL_Clear($)
|
||||
{
|
||||
my $hash = shift;
|
||||
|
||||
# Clear the pipe
|
||||
$hash->{RA_Timeout} = 0.1;
|
||||
for(;;) {
|
||||
my ($err, undef) = CUL_ReadAnswer($hash, "Clear", 0);
|
||||
last if($err && $err =~ m/^Timeout/);
|
||||
}
|
||||
delete($hash->{RA_Timeout});
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub
|
||||
CUL_DoInit($)
|
||||
{
|
||||
my $hash = shift;
|
||||
my $name = $hash->{NAME};
|
||||
my $err;
|
||||
my $msg = undef;
|
||||
|
||||
CUL_Clear($hash);
|
||||
my ($ver, $try) = ("", 0);
|
||||
while($try++ < 3 && $ver !~ m/^V/) {
|
||||
CUL_SimpleWrite($hash, "V");
|
||||
($err, $ver) = CUL_ReadAnswer($hash, "Version", 0);
|
||||
return "$name: $err" if($err);
|
||||
}
|
||||
|
||||
if($ver !~ m/^V/) {
|
||||
$attr{$name}{dummy} = 1;
|
||||
$msg = "Not an CUL device, got for V: $ver";
|
||||
Log 1, $msg;
|
||||
return $msg;
|
||||
}
|
||||
$hash->{VERSION} = $ver;
|
||||
|
||||
if($ver =~ m/CUR/) {
|
||||
my @a = localtime;
|
||||
my $msg = sprintf("c%02d%02d%02d%02d%02d%02d",
|
||||
($a[5]+1900)%100,$a[4]+1,$a[3],$a[2],$a[1],$a[0]);
|
||||
CUL_SimpleWrite($hash, $msg);
|
||||
}
|
||||
|
||||
CUL_SimpleWrite($hash, $initstr);
|
||||
|
||||
# FHTID
|
||||
my $fhtid;
|
||||
CUL_SimpleWrite($hash, "T01");
|
||||
($err, $fhtid) = CUL_ReadAnswer($hash, "FHTID", 0);
|
||||
return "$name: $err" if($err);
|
||||
$fhtid =~ s/[\r\n]//g;
|
||||
Log 5, "GOT CUL fhtid: $fhtid";
|
||||
if(!defined($fhtid) || $fhtid ne $hash->{FHTID}) {
|
||||
Log 2, "Setting CUL fhtid from $fhtid to " . $hash->{FHTID};
|
||||
CUL_SimpleWrite($hash, "T01" . $hash->{FHTID});
|
||||
}
|
||||
|
||||
$hash->{STATE} = "Initialized" if(!$hash->{STATE});
|
||||
|
||||
# Reset the counter
|
||||
delete($hash->{XMIT_TIME});
|
||||
delete($hash->{NR_CMD_LAST_H});
|
||||
return undef;
|
||||
}
|
||||
|
||||
#####################################
|
||||
# This is a direct read for commands like get
|
||||
sub
|
||||
CUL_ReadAnswer($$$)
|
||||
{
|
||||
my ($hash, $arg, $anydata) = @_;
|
||||
|
||||
return ("No FD", undef)
|
||||
if(!$hash || ($^O !~ /Win/ && !defined($hash->{FD})));
|
||||
|
||||
my ($mculdata, $rin) = ("", '');
|
||||
my $buf;
|
||||
my $to = 3; # 3 seconds timeout
|
||||
$to = $hash->{RA_Timeout} if($hash->{RA_Timeout}); # ...or less
|
||||
for(;;) {
|
||||
|
||||
if($^O =~ m/Win/ && $hash->{USBDev}) {
|
||||
$hash->{USBDev}->read_const_time($to*1000); # set timeout (ms)
|
||||
# Read anstatt input sonst funzt read_const_time nicht.
|
||||
$buf = $hash->{USBDev}->read(999);
|
||||
return ("Timeout reading answer for get $arg", undef)
|
||||
if(length($buf) == 0);
|
||||
|
||||
} else {
|
||||
return ("Device lost when reading answer for get $arg", undef)
|
||||
if(!$hash->{FD});
|
||||
|
||||
vec($rin, $hash->{FD}, 1) = 1;
|
||||
my $nfound = select($rin, undef, undef, $to);
|
||||
if($nfound < 0) {
|
||||
next if ($! == EAGAIN() || $! == EINTR() || $! == 0);
|
||||
die("Select error $nfound / $!", undef);
|
||||
}
|
||||
return ("Timeout reading answer for get $arg", undef)
|
||||
if($nfound == 0);
|
||||
$buf = CUL_SimpleRead($hash);
|
||||
}
|
||||
|
||||
if($buf) {
|
||||
Log 5, "CUL/RAW: $buf";
|
||||
$mculdata .= $buf;
|
||||
}
|
||||
return (undef, $mculdata) if($mculdata =~ m/\r\n/ || $anydata);
|
||||
}
|
||||
}
|
||||
|
||||
#####################################
|
||||
# Check if the 1% limit is reached and trigger notifies
|
||||
sub
|
||||
CUL_XmitLimitCheck($$)
|
||||
{
|
||||
my ($hash,$fn) = @_;
|
||||
my $now = time();
|
||||
|
||||
if(!$hash->{XMIT_TIME}) {
|
||||
$hash->{XMIT_TIME}[0] = $now;
|
||||
$hash->{NR_CMD_LAST_H} = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
my $nowM1h = $now-3600;
|
||||
my @b = grep { $_ > $nowM1h } @{$hash->{XMIT_TIME}};
|
||||
|
||||
if(@b > 163) { # Maximum nr of transmissions per hour (unconfirmed).
|
||||
|
||||
my $name = $hash->{NAME};
|
||||
Log GetLogLevel($name,2), "CUL TRANSMIT LIMIT EXCEEDED";
|
||||
DoTrigger($name, "TRANSMIT LIMIT EXCEEDED");
|
||||
|
||||
} else {
|
||||
|
||||
push(@b, $now);
|
||||
|
||||
}
|
||||
$hash->{XMIT_TIME} = \@b;
|
||||
$hash->{NR_CMD_LAST_H} = int(@b);
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub
|
||||
CUL_Write($$$)
|
||||
{
|
||||
my ($hash,$fn,$msg) = @_;
|
||||
|
||||
my $name = $hash->{NAME};
|
||||
|
||||
###################
|
||||
# Rewrite message from FHZ -> CUL
|
||||
if(length($fn) <= 1) { # CUL Native
|
||||
;
|
||||
|
||||
} elsif($fn eq "04" && substr($msg,0,6) eq "010101") { # FS20
|
||||
$fn = "F";
|
||||
$msg = substr($msg,6);
|
||||
|
||||
} elsif($fn eq "04" && substr($msg,0,6) eq "020183") { # FHT
|
||||
$fn = "T";
|
||||
$msg = substr($msg,6,4) . substr($msg,10);
|
||||
CUL_SimpleWrite($hash, $fn . $msg);
|
||||
return;
|
||||
|
||||
} else {
|
||||
Log GetLogLevel($name,2), "CUL cannot translate $fn $msg";
|
||||
return;
|
||||
}
|
||||
|
||||
Log 5, "CUL sending $fn$msg";
|
||||
my $bstring = "$fn$msg";
|
||||
|
||||
if($fn eq "F") {
|
||||
|
||||
if(!CUL_AddFS20Queue($hash, $bstring)) {
|
||||
CUL_XmitLimitCheck($hash,$bstring);
|
||||
CUL_SimpleWrite($hash, $bstring);
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
CUL_SimpleWrite($hash, $bstring);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
sub
|
||||
CUL_AddFS20Queue($$)
|
||||
{
|
||||
my ($hash, $bstring) = @_;
|
||||
|
||||
if(!$hash->{QUEUE}) {
|
||||
##############
|
||||
# Write the next buffer not earlier than 0.23 seconds
|
||||
# = 3* (12*0.8+1.2+1.0*5*9+0.8+10) = 226.8ms
|
||||
# else it will be sent too early by the CUL, resulting in a collision
|
||||
$hash->{QUEUE} = [ $bstring ];
|
||||
InternalTimer(gettimeofday()+0.3, "CUL_HandleWriteQueue", $hash, 1);
|
||||
return 0;
|
||||
}
|
||||
push(@{$hash->{QUEUE}}, $bstring);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
#####################################
|
||||
sub
|
||||
CUL_HandleWriteQueue($)
|
||||
{
|
||||
my $hash = shift;
|
||||
my $arr = $hash->{QUEUE};
|
||||
|
||||
if(defined($arr) && @{$arr} > 0) {
|
||||
shift(@{$arr});
|
||||
if(@{$arr} == 0) {
|
||||
delete($hash->{QUEUE});
|
||||
return;
|
||||
}
|
||||
my $bstring = $arr->[0];
|
||||
if($bstring eq "-") {
|
||||
CUL_HandleWriteQueue($hash);
|
||||
} else {
|
||||
CUL_XmitLimitCheck($hash,$bstring);
|
||||
CUL_SimpleWrite($hash, $bstring);
|
||||
InternalTimer(gettimeofday()+0.3, "CUL_HandleWriteQueue", $hash, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub
|
||||
CUL_Read($)
|
||||
{
|
||||
my ($hash) = @_;
|
||||
|
||||
my $buf = CUL_SimpleRead($hash);
|
||||
my $name = $hash->{NAME};
|
||||
|
||||
###########
|
||||
# Lets' try again: Some drivers return len(0) on the first read...
|
||||
if(defined($buf) && length($buf) == 0) {
|
||||
$buf = CUL_SimpleRead($hash);
|
||||
}
|
||||
|
||||
if(!defined($buf) || length($buf) == 0) {
|
||||
|
||||
my $dev = $hash->{DeviceName};
|
||||
Log 1, "$dev disconnected, waiting to reappear";
|
||||
CUL_CloseDev($hash);
|
||||
$readyfnlist{"$name.$dev"} = $hash; # Start polling
|
||||
$hash->{STATE} = "disconnected";
|
||||
|
||||
# Without the following sleep the open of the device causes a SIGSEGV,
|
||||
# and following opens block infinitely. Only a reboot helps.
|
||||
sleep(5);
|
||||
|
||||
DoTrigger($name, "DISCONNECTED");
|
||||
return "";
|
||||
}
|
||||
|
||||
my $culdata = $hash->{PARTIAL};
|
||||
Log 5, "CUL/RAW: $culdata/$buf";
|
||||
$culdata .= $buf;
|
||||
|
||||
while($culdata =~ m/\n/) {
|
||||
|
||||
my ($rmsg, $rssi);
|
||||
($rmsg,$culdata) = split("\n", $culdata, 2);
|
||||
$rmsg =~ s/\r//;
|
||||
goto NEXTMSG if($rmsg eq "");
|
||||
|
||||
my $dmsg = $rmsg;
|
||||
if($initstr =~ m/X2/ && $dmsg =~ m/^[FTKEHR]([A-F0-9][A-F0-9])+$/) { # RSSI
|
||||
my $l = length($dmsg);
|
||||
$rssi = hex(substr($dmsg, $l-2, 2));
|
||||
$dmsg = substr($dmsg, 0, $l-2);
|
||||
$rssi = ($rssi>=128 ? (($rssi-256)/2-74) : ($rssi/2-74));
|
||||
Log GetLogLevel($name,4), "$name: $dmsg $rssi";
|
||||
} else {
|
||||
Log GetLogLevel($name,4), "$name: $dmsg";
|
||||
}
|
||||
|
||||
###########################################
|
||||
#Translate Message from CUL to FHZ
|
||||
next if(!$dmsg || length($dmsg) < 1); # Bogus messages
|
||||
my $fn = substr($dmsg,0,1);
|
||||
my $len = length($dmsg);
|
||||
|
||||
if($fn eq "F" && $len >= 9) { # Reformat for 10_FS20.pm
|
||||
|
||||
CUL_AddFS20Queue($hash, "-"); # Avoid sending response too early
|
||||
|
||||
if(defined($attr{$name}) && defined($attr{$name}{CUR_id_list})) {
|
||||
my $id= substr($dmsg,1,4);
|
||||
if($attr{$name}{CUR_id_list} =~ m/$id/) { # CUR Request
|
||||
CUL_HandleCurRequest($hash,$dmsg);
|
||||
goto NEXTMSG;
|
||||
}
|
||||
}
|
||||
|
||||
$dmsg = sprintf("81%02x04xx0101a001%s00%s",
|
||||
$len/2+7, substr($dmsg,1,6), substr($dmsg,7));
|
||||
$dmsg = lc($dmsg);
|
||||
|
||||
} elsif($fn eq "T" && $len >= 11) { # Reformat for 11_FHT.pm
|
||||
|
||||
$dmsg = sprintf("81%02x04xx0909a001%s00%s",
|
||||
$len/2+7, substr($dmsg,1,6), substr($dmsg,7));
|
||||
$dmsg = lc($dmsg);
|
||||
|
||||
} elsif($fn eq "H" && $len >= 13) { # Reformat for 12_HMS.pm
|
||||
|
||||
my $type = hex(substr($dmsg,6,1));
|
||||
my $stat = $type > 1 ? hex(substr($dmsg,7,2)) : hex(substr($dmsg,5,2));
|
||||
my $prf = $type > 1 ? "02" : "05";
|
||||
my $bat = $type > 1 ? hex(substr($dmsg,5,1))+1 : 1;
|
||||
my $HA = substr($dmsg,1,4);
|
||||
my $values = $type > 1 ? "000000" : substr($dmsg,7);
|
||||
$dmsg = sprintf("81%02x04xx%s%x%xa001%s0000%02x%s",
|
||||
$len/2+8, # Packet-Length
|
||||
$prf, $bat, $type,
|
||||
$HA, # House-Code
|
||||
$stat,
|
||||
$values); # Values
|
||||
$dmsg = lc($dmsg);
|
||||
|
||||
} elsif($fn eq "K" && $len >= 5) {
|
||||
|
||||
if($len == 15) { # Reformat for 13_KS300.pm
|
||||
my @a = split("", $dmsg);
|
||||
$dmsg = sprintf("81%02x04xx4027a001", $len/2+6);
|
||||
for(my $i = 1; $i < 14; $i+=2) { # Swap nibbles.
|
||||
$dmsg .= $a[$i+1] . $a[$i];
|
||||
}
|
||||
$dmsg = lc($dmsg);
|
||||
}
|
||||
# Other K... Messages ar sent to CUL_WS
|
||||
|
||||
} elsif($fn eq "E" && $len >= 11) { # CUL_EM / Native
|
||||
;
|
||||
} else {
|
||||
Log GetLogLevel($name,2), "CUL: unknown message $dmsg";
|
||||
goto NEXTMSG;
|
||||
}
|
||||
|
||||
$hash->{RSSI} = $rssi if(defined($rssi));
|
||||
$hash->{RAWMSG} = $rmsg;
|
||||
my $foundp = Dispatch($hash, $dmsg);
|
||||
if($foundp) {
|
||||
foreach my $d (@{$foundp}) {
|
||||
next if(!$defs{$d});
|
||||
$defs{$d}{RSSI} = $rssi if($rssi);
|
||||
$defs{$d}{RAWMSG} = $rmsg;
|
||||
}
|
||||
}
|
||||
|
||||
NEXTMSG:
|
||||
}
|
||||
$hash->{PARTIAL} = $culdata;
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub
|
||||
CUL_Ready($)
|
||||
{
|
||||
my ($hash) = @_;
|
||||
|
||||
return CUL_OpenDev($hash, 1)
|
||||
if($hash->{STATE} eq "disconnected");
|
||||
|
||||
# This is relevant for windows/USB only
|
||||
my $po=$hash->{USBDev};
|
||||
my ($BlockingFlags, $InBytes, $OutBytes, $ErrorFlags) = $po->status;
|
||||
return ($InBytes>0);
|
||||
}
|
||||
|
||||
sub
|
||||
CUL_SendCurMsg($$$)
|
||||
{
|
||||
my ($hash,$id,$msg) = @_;
|
||||
|
||||
$msg = substr($msg, 0, 12) if(length($msg) > 12);
|
||||
my $rmsg = "F" . $id . unpack('H*', $msg);
|
||||
Log 1, "CUL_SendCurMsg: $id:$msg / $rmsg";
|
||||
sleep(1); # Poor mans CSMA/CD
|
||||
CUL_SimpleWrite($hash, $rmsg);
|
||||
}
|
||||
|
||||
sub
|
||||
CUL_HandleCurRequest($$)
|
||||
{
|
||||
my ($hash,$msg) = @_;
|
||||
|
||||
|
||||
Log 1, "CUR Request: $msg";
|
||||
my $l = length($msg);
|
||||
return if($l < 9);
|
||||
|
||||
my $id = substr($msg,1,4);
|
||||
my $cm = substr($msg,5,2);
|
||||
my $a1 = substr($msg,7,2);
|
||||
my $a2 = pack('H*', substr($msg,9)) if($l > 9);
|
||||
|
||||
if($cm eq "00") { # Get status
|
||||
$msg = defined($defs{$a2}) ? $defs{$a2}{STATE} : "Undefined $a2";
|
||||
$msg =~ s/: /:/g;
|
||||
$msg =~ s/ / /g;
|
||||
$msg =~ s/.*[a-z]-//g; # FHT desired-temp, but keep T:-1
|
||||
$msg =~ s/\(.*//g; # FHT (Celsius)
|
||||
$msg =~ s/.*5MIN:/5MIN:/g; # EM
|
||||
$msg =~ s/\.$//;
|
||||
$msg =~ s/ *//; # One letter seldom makes sense
|
||||
CUL_SendCurMsg($hash,$id, "d" . $msg); # Display the message on the CUR
|
||||
}
|
||||
|
||||
if($cm eq "01") { # Send time
|
||||
my @a = localtime;
|
||||
$msg = sprintf("c%02d%02d%02d", $a[2],$a[1],$a[0]);
|
||||
CUL_SendCurMsg($hash,$id, $msg);
|
||||
}
|
||||
|
||||
if($cm eq "02") { # FHT desired temp
|
||||
$msg = sprintf("set %s desired-temp %.1f", $a2, $a1/2);
|
||||
fhem( $msg );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
########################
|
||||
sub
|
||||
CUL_SimpleWrite(@)
|
||||
{
|
||||
my ($hash, $msg, $noapp) = @_;
|
||||
return if(!$hash);
|
||||
|
||||
$msg .= "\n" unless($noapp);
|
||||
|
||||
$hash->{USBDev}->write($msg . "\n") if($hash->{USBDev});
|
||||
syswrite($hash->{TCPDev}, $msg) if($hash->{TCPDev});
|
||||
|
||||
#Log 1, "CUL_SimpleWrite >$msg<";
|
||||
select(undef, undef, undef, 0.001);
|
||||
}
|
||||
|
||||
########################
|
||||
sub
|
||||
CUL_SimpleRead($)
|
||||
{
|
||||
my ($hash) = @_;
|
||||
|
||||
if($hash->{USBDev}) {
|
||||
return $hash->{USBDev}->input();
|
||||
}
|
||||
|
||||
if($hash->{TCPDev}) {
|
||||
my $buf;
|
||||
if(!defined(sysread($hash->{TCPDev}, $buf, 256))) {
|
||||
CUL_CloseDev($hash);
|
||||
my $name = $hash->{NAME};
|
||||
my $dev = $hash->{DeviceName};
|
||||
$readyfnlist{"$name.$dev"} = $hash; # Start polling
|
||||
$hash->{STATE} = "disconnected";
|
||||
return undef;
|
||||
}
|
||||
|
||||
return $buf;
|
||||
}
|
||||
return undef;
|
||||
}
|
||||
|
||||
########################
|
||||
sub
|
||||
CUL_CloseDev($)
|
||||
{
|
||||
my ($hash) = @_;
|
||||
my $name = $hash->{NAME};
|
||||
my $dev = $hash->{DeviceName};
|
||||
|
||||
return if(!$dev);
|
||||
|
||||
if($hash->{TCPDev}) {
|
||||
$hash->{TCPDev}->close();
|
||||
delete($hash->{TCPDev});
|
||||
|
||||
} elsif($hash->{USBDev}) {
|
||||
$hash->{USBDev}->close() ;
|
||||
delete($hash->{USBDev});
|
||||
|
||||
}
|
||||
delete($selectlist{"$name.$dev"});
|
||||
delete($readyfnlist{"$name.$dev"});
|
||||
delete($hash->{FD});
|
||||
}
|
||||
|
||||
########################
|
||||
sub
|
||||
CUL_OpenDev($$)
|
||||
{
|
||||
my ($hash, $reopen) = @_;
|
||||
my $dev = $hash->{DeviceName};
|
||||
my $name = $hash->{NAME};
|
||||
my $po;
|
||||
|
||||
|
||||
$hash->{PARTIAL} = "";
|
||||
Log 3, "CUL opening CUL device $dev"
|
||||
if(!$reopen);
|
||||
|
||||
if($dev =~ m/^(.+):([0-9]+)$/) { # host:port
|
||||
|
||||
# This part is called every time the timeout (5sec) is expired _OR_
|
||||
# somebody is communicating over another TCP connection. As the connect
|
||||
# for non-existent devices has a delay of 3 sec, we are sitting all the
|
||||
# time in this connect. NEXT_OPEN tries to avoid this problem.
|
||||
if($hash->{NEXT_OPEN} && time() < $hash->{NEXT_OPEN}) {
|
||||
return;
|
||||
}
|
||||
|
||||
my $conn = IO::Socket::INET->new(PeerAddr => $dev);
|
||||
if($conn) {
|
||||
delete($hash->{NEXT_OPEN})
|
||||
|
||||
} else {
|
||||
Log(3, "Can't connect to $dev: $!") if(!$reopen);
|
||||
$readyfnlist{"$name.$dev"} = $hash;
|
||||
$hash->{STATE} = "disconnected";
|
||||
$hash->{NEXT_OPEN} = time()+60;
|
||||
return "";
|
||||
}
|
||||
|
||||
$hash->{TCPDev} = $conn;
|
||||
$hash->{FD} = $conn->fileno();
|
||||
delete($readyfnlist{"$name.$dev"});
|
||||
$selectlist{"$name.$dev"} = $hash;
|
||||
|
||||
} else { # USB Device
|
||||
if ($^O=~/Win/) {
|
||||
require Win32::SerialPort;
|
||||
$po = new Win32::SerialPort ($dev);
|
||||
} else {
|
||||
require Device::SerialPort;
|
||||
$po = new Device::SerialPort ($dev);
|
||||
}
|
||||
|
||||
if(!$po) {
|
||||
return undef if($reopen);
|
||||
Log(3, "Can't open $dev: $!");
|
||||
$readyfnlist{"$name.$dev"} = $hash;
|
||||
$hash->{STATE} = "disconnected";
|
||||
return "";
|
||||
}
|
||||
$hash->{USBDev} = $po;
|
||||
if( $^O =~ /Win/ ) {
|
||||
$readyfnlist{"$name.$dev"} = $hash;
|
||||
} else {
|
||||
$hash->{FD} = $po->FILENO;
|
||||
delete($readyfnlist{"$name.$dev"});
|
||||
$selectlist{"$name.$dev"} = $hash;
|
||||
}
|
||||
}
|
||||
|
||||
if($reopen) {
|
||||
Log 1, "CUL $dev reappeared ($name)";
|
||||
} else {
|
||||
Log 3, "CUL opened $dev for $name";
|
||||
}
|
||||
|
||||
$hash->{STATE}=""; # Allow InitDev to set the state
|
||||
my $ret = CUL_DoInit($hash);
|
||||
|
||||
if($ret) {
|
||||
CUL_CloseDev($hash);
|
||||
Log 1, "Cannot init $dev, ignoring it";
|
||||
}
|
||||
|
||||
DoTrigger($name, "CONNECTED") if($reopen);
|
||||
return $ret;
|
||||
}
|
||||
|
||||
1;
|
|
@ -1,707 +0,0 @@
|
|||
##############################################
|
||||
package main;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use Time::HiRes qw(gettimeofday);
|
||||
|
||||
|
||||
sub FHZ_Write($$$);
|
||||
sub FHZ_Read($);
|
||||
sub FHZ_ReadAnswer($$$);
|
||||
sub FHZ_Crc(@);
|
||||
sub FHZ_CheckCrc($);
|
||||
sub FHZ_XmitLimitCheck($$);
|
||||
sub FHZ_DoInit($$$);
|
||||
|
||||
my $msgstart = pack('H*', "81");# Every msg starts wit this
|
||||
|
||||
my %gets = (
|
||||
"init1" => "c9 02011f64",
|
||||
"init2" => "c9 02011f60",
|
||||
"init3" => "c9 02011f0a",
|
||||
"serial" => "04 c90184570208",
|
||||
"fhtbuf" => "04 c90185",
|
||||
);
|
||||
my %sets = (
|
||||
"time" => "c9 020161",
|
||||
"initHMS" => "04 c90186",
|
||||
"initFS20" => "04 c90196",
|
||||
"FHTcode" => "04 c901839e0101",
|
||||
|
||||
"raw" => "xx xx",
|
||||
"initfull" => "xx xx",
|
||||
"reopen" => "xx xx",
|
||||
);
|
||||
my %setnrparam = (
|
||||
"time" => 0,
|
||||
"initHMS" => 0,
|
||||
"initFS20" => 0,
|
||||
"FHTcode" => 1,
|
||||
"raw" => 2,
|
||||
"initfull" => 0,
|
||||
"reopen" => 0,
|
||||
);
|
||||
|
||||
my %codes = (
|
||||
"^8501..\$" => "fhtbuf",
|
||||
);
|
||||
|
||||
#####################################
|
||||
# Note: we are a data provider _and_ a consumer at the same time
|
||||
sub
|
||||
FHZ_Initialize($)
|
||||
{
|
||||
my ($hash) = @_;
|
||||
|
||||
# Provider
|
||||
$hash->{ReadFn} = "FHZ_Read";
|
||||
$hash->{WriteFn} = "FHZ_Write";
|
||||
$hash->{Clients} = ":FHZ:FS20:FHT:HMS:KS300:USF1000:BS:";
|
||||
my %mc = (
|
||||
"1:USF1000" => "^81..(04|0c)..0101a001a5ceaa00....",
|
||||
"2:BS" => "^81..(04|0c)..0101a001a5cf......",
|
||||
"3:FS20" => "^81..(04|0c)..0101a001",
|
||||
"4:FHT" => "^81..(04|09|0d)..(0909a001|83098301|c409c401)..",
|
||||
"5:HMS" => "^810e04....(1|5|9).a001",
|
||||
"6:KS300" => "^810d04..4027a001",
|
||||
);
|
||||
$hash->{MatchList} = \%mc;
|
||||
$hash->{ReadyFn} = "FHZ_Ready";
|
||||
|
||||
# Consumer
|
||||
$hash->{Match} = "^81..C9..0102";
|
||||
$hash->{ParseFn} = "FHZ_Parse";
|
||||
|
||||
# Normal devices
|
||||
$hash->{DefFn} = "FHZ_Define";
|
||||
$hash->{UndefFn} = "FHZ_Undef";
|
||||
$hash->{GetFn} = "FHZ_Get";
|
||||
$hash->{SetFn} = "FHZ_Set";
|
||||
$hash->{StateFn} = "FHZ_SetState";
|
||||
$hash->{AttrList}= "do_not_notify:1,0 dummy:1,0 filtertimeout repeater:1,0 " .
|
||||
"showtime:1,0 model:fhz1000,fhz1300 loglevel:0,1,2,3,4,5,6 ".
|
||||
"fhtsoftbuffer:1,0";
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub
|
||||
FHZ_Ready($)
|
||||
{
|
||||
my ($hash) = @_;
|
||||
my $po=$hash->{PortObj};
|
||||
|
||||
if(!$po) { # Looking for the device
|
||||
|
||||
my $dev = $hash->{DeviceName};
|
||||
my $name = $hash->{NAME};
|
||||
|
||||
$hash->{PARTIAL} = "";
|
||||
if($^O =~ m/Win/) {
|
||||
$po = new Win32::SerialPort ($dev);
|
||||
} else {
|
||||
$po = new Device::SerialPort ($dev);
|
||||
}
|
||||
return undef if(!$po);
|
||||
|
||||
Log 1, "USB device $dev reappeared";
|
||||
$hash->{PortObj} = $po;
|
||||
if($^O !~ m/Win/) {
|
||||
$hash->{FD} = $po->FILENO;
|
||||
delete($readyfnlist{"$name.$dev"});
|
||||
$selectlist{"$name.$dev"} = $hash;
|
||||
} else {
|
||||
$readyfnlist{"$name.$dev"} = $hash;
|
||||
}
|
||||
|
||||
FHZ_DoInit($name, $hash->{ttytype}, $po);
|
||||
DoTrigger($name, "CONNECTED");
|
||||
return undef;
|
||||
|
||||
}
|
||||
|
||||
# This is relevant for windows only
|
||||
my ($BlockingFlags, $InBytes, $OutBytes, $ErrorFlags)=$po->status;
|
||||
return ($InBytes>0);
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub
|
||||
FHZ_Set($@)
|
||||
{
|
||||
my ($hash, @a) = @_;
|
||||
|
||||
return "Need one to three parameter" if(@a < 2);
|
||||
return "Unknown argument $a[1], choose one of " . join(" ", sort keys %sets)
|
||||
if(!defined($sets{$a[1]}));
|
||||
return "Need one to three parameter" if(@a > 4);
|
||||
return "Wrong number of parameters for $a[1], need " . ($setnrparam{$a[1]}+2)
|
||||
if(@a != ($setnrparam{$a[1]} + 2));
|
||||
|
||||
my ($fn, $arg) = split(" ", $sets{$a[1]});
|
||||
|
||||
my $v = join(" ", @a);
|
||||
my $name = $hash->{NAME};
|
||||
Log GetLogLevel($name,2), "FHZ set $v";
|
||||
|
||||
if($a[1] eq "initfull") {
|
||||
|
||||
my @init;
|
||||
push(@init, "get $name init2");
|
||||
push(@init, "get $name serial");
|
||||
push(@init, "set $name initHMS");
|
||||
push(@init, "set $name initFS20");
|
||||
push(@init, "set $name time");
|
||||
push(@init, "set $name raw 04 01010100010000");
|
||||
CommandChain(3, \@init);
|
||||
|
||||
} elsif($a[1] eq "reopen") {
|
||||
|
||||
FHZ_Reopen($hash);
|
||||
|
||||
} elsif($a[1] eq "raw") {
|
||||
|
||||
$fn = $a[2];
|
||||
$arg = $a[3];
|
||||
|
||||
} elsif($a[1] eq "time") {
|
||||
|
||||
my @t = localtime;
|
||||
$arg .= sprintf("%02x%02x%02x%02x%02x",
|
||||
$t[5]%100, $t[4]+1, $t[3], $t[2], $t[1]);
|
||||
|
||||
} elsif($a[1] eq "FHTcode") {
|
||||
|
||||
return "invalid argument, must be hex" if(!$a[2] ||
|
||||
$a[2] !~ m/^[A-F0-9]{2}$/);
|
||||
$arg .= $a[2];
|
||||
|
||||
}
|
||||
|
||||
FHZ_Write($hash, $fn, $arg) if(!IsDummy($hash->{NAME}));
|
||||
return undef;
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub
|
||||
FHZ_Get($@)
|
||||
{
|
||||
my ($hash, @a) = @_;
|
||||
|
||||
return "\"get FHZ\" needs only one parameter" if(@a != 2);
|
||||
return "Unknown argument $a[1], choose one of " . join(",", sort keys %gets)
|
||||
if(!defined($gets{$a[1]}));
|
||||
|
||||
my ($fn, $arg) = split(" ", $gets{$a[1]});
|
||||
|
||||
my $v = join(" ", @a);
|
||||
my $name = $hash->{NAME};
|
||||
Log GetLogLevel($name,2), "FHZ get $v";
|
||||
|
||||
FHZ_ReadAnswer($hash, "Flush", 0);
|
||||
FHZ_Write($hash, $fn, $arg) if(!IsDummy($hash->{NAME}));
|
||||
|
||||
my $msg = FHZ_ReadAnswer($hash, $a[1], 1.0);
|
||||
Log 5, "GET Got: $msg";
|
||||
return $msg if(!$msg || $msg !~ /^81..c9..0102/);
|
||||
|
||||
if($a[1] eq "serial") {
|
||||
$v = substr($msg, 22, 8)
|
||||
|
||||
} elsif($a[1] eq "fhtbuf") {
|
||||
$v = substr($msg, 16, 2);
|
||||
|
||||
} else {
|
||||
$v = substr($msg, 12);
|
||||
}
|
||||
$hash->{READINGS}{$a[1]}{VAL} = $v;
|
||||
$hash->{READINGS}{$a[1]}{TIME} = TimeNow();
|
||||
|
||||
return "$a[0] $a[1] => $v";
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub
|
||||
FHZ_SetState($$$$)
|
||||
{
|
||||
my ($hash, $tim, $vt, $val) = @_;
|
||||
|
||||
return "Undefined value $vt" if(!defined($gets{$vt}));
|
||||
return undef;
|
||||
}
|
||||
|
||||
|
||||
#####################################
|
||||
sub
|
||||
FHZ_DoInit($$$)
|
||||
{
|
||||
my ($name,$type,$po) = @_;
|
||||
my @init;
|
||||
|
||||
$po->reset_error();
|
||||
$po->baudrate(9600);
|
||||
$po->databits(8);
|
||||
$po->parity('none');
|
||||
$po->stopbits(1);
|
||||
$po->handshake('none');
|
||||
|
||||
if($type && $type eq "strangetty") {
|
||||
|
||||
# This part is for some Linux kernel versions whih has strange default
|
||||
# settings. Device::SerialPort is nice: if the flag is not defined for your
|
||||
# OS then it will be ignored.
|
||||
$po->stty_icanon(0);
|
||||
#$po->stty_parmrk(0); # The debian standard install does not have it
|
||||
$po->stty_icrnl(0);
|
||||
$po->stty_echoe(0);
|
||||
$po->stty_echok(0);
|
||||
$po->stty_echoctl(0);
|
||||
|
||||
# Needed for some strange distros
|
||||
$po->stty_echo(0);
|
||||
$po->stty_icanon(0);
|
||||
$po->stty_isig(0);
|
||||
$po->stty_opost(0);
|
||||
$po->stty_icrnl(0);
|
||||
}
|
||||
|
||||
$po->write_settings;
|
||||
|
||||
|
||||
push(@init, "get $name init2");
|
||||
push(@init, "get $name serial");
|
||||
push(@init, "set $name initHMS");
|
||||
push(@init, "set $name initFS20");
|
||||
push(@init, "set $name time");
|
||||
|
||||
# Workaround: Sending "set 0001 00 off" after initialization to enable
|
||||
# the fhz1000 receiver, else we won't get anything reported.
|
||||
push(@init, "set $name raw 04 01010100010000");
|
||||
|
||||
CommandChain(3, \@init);
|
||||
|
||||
# Reset the counter
|
||||
my $hash = $defs{$name};
|
||||
delete($hash->{XMIT_TIME});
|
||||
delete($hash->{NR_CMD_LAST_H});
|
||||
$hash->{STATE} = "Initialized";
|
||||
return undef;
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub
|
||||
FHZ_Define($$)
|
||||
{
|
||||
my ($hash, $def) = @_;
|
||||
my @a = split("[ \t][ \t]*", $def);
|
||||
my $po;
|
||||
|
||||
return "wrong syntax: define <name> FHZ devicename ".
|
||||
"[normal|strangetty] [mobile]" if(@a < 3 || @a > 5);
|
||||
|
||||
delete $hash->{PortObj};
|
||||
delete $hash->{FD};
|
||||
|
||||
my $name = $a[0];
|
||||
my $dev = $a[2];
|
||||
$hash->{ttytype} = $a[3] if($a[3]);
|
||||
$hash->{MOBILE} = 1 if($a[4] && $a[4] eq "mobile");
|
||||
$hash->{STATE} = "defined";
|
||||
|
||||
$attr{$name}{savefirst} = 1;
|
||||
$attr{$name}{fhtsoftbuffer} = 0;
|
||||
|
||||
if($dev eq "none") {
|
||||
Log 1, "FHZ device is none, commands will be echoed only";
|
||||
$attr{$name}{dummy} = 1;
|
||||
return undef;
|
||||
}
|
||||
|
||||
$hash->{DeviceName} = $dev;
|
||||
$hash->{PARTIAL} = "";
|
||||
Log 3, "FHZ opening FHZ device $dev";
|
||||
if($^O =~ m/Win/) {
|
||||
require Win32::SerialPort;
|
||||
$po = new Win32::SerialPort ($dev);
|
||||
} else {
|
||||
require Device::SerialPort;
|
||||
$po = new Device::SerialPort ($dev);
|
||||
}
|
||||
if(!$po) {
|
||||
my $msg = "Can't open $dev: $!";
|
||||
Log(3, $msg) if($hash->{MOBILE});
|
||||
return $msg if(!$hash->{MOBILE});
|
||||
$readyfnlist{"$name.$dev"} = $hash;
|
||||
return "";
|
||||
}
|
||||
Log 3, "FHZ opened FHZ device $dev";
|
||||
|
||||
$hash->{PortObj} = $po;
|
||||
if($^O !~ m/Win/) {
|
||||
$hash->{FD} = $po->FILENO;
|
||||
$selectlist{"$name.$dev"} = $hash;
|
||||
} else {
|
||||
$readyfnlist{"$name.$dev"} = $hash;
|
||||
}
|
||||
|
||||
FHZ_DoInit($name, $hash->{ttytype}, $po);
|
||||
return undef;
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub
|
||||
FHZ_Undef($$)
|
||||
{
|
||||
my ($hash, $arg) = @_;
|
||||
my $name = $hash->{NAME};
|
||||
|
||||
foreach my $d (sort keys %defs) {
|
||||
if(defined($defs{$d}) &&
|
||||
defined($defs{$d}{IODev}) &&
|
||||
$defs{$d}{IODev} == $hash)
|
||||
{
|
||||
my $lev = ($reread_active ? 4 : 2);
|
||||
Log GetLogLevel($name,$lev), "deleting port for $d";
|
||||
delete $defs{$d}{IODev};
|
||||
}
|
||||
}
|
||||
$hash->{PortObj}->close() if($hash->{PortObj});
|
||||
delete($hash->{PortObj});
|
||||
delete($hash->{FD});
|
||||
return undef;
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub
|
||||
FHZ_Parse($$)
|
||||
{
|
||||
my ($hash,$msg) = @_;
|
||||
|
||||
my $omsg = $msg;
|
||||
$msg = substr($msg, 12); # The first 12 bytes are not really interesting
|
||||
|
||||
my $type = "";
|
||||
my $name = $hash->{NAME};
|
||||
foreach my $c (keys %codes) {
|
||||
if($msg =~ m/$c/) {
|
||||
$type = $codes{$c};
|
||||
last;
|
||||
}
|
||||
}
|
||||
|
||||
if(!$type) {
|
||||
Log 4, "FHZ $name unknown: $omsg";
|
||||
$hash->{CHANGED}[0] = "$msg";
|
||||
return $hash->{NAME};
|
||||
}
|
||||
|
||||
|
||||
if($type eq "fhtbuf") {
|
||||
$msg = substr($msg, 4, 2);
|
||||
}
|
||||
|
||||
Log 4, "FHZ $name $type: $msg";
|
||||
$hash->{CHANGED}[0] = "$type: $msg";
|
||||
return $hash->{NAME};
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub
|
||||
FHZ_Crc(@)
|
||||
{
|
||||
my $sum = 0;
|
||||
map { $sum += $_; } @_;
|
||||
return $sum & 0xFF;
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub
|
||||
FHZ_CheckCrc($)
|
||||
{
|
||||
my $msg = shift;
|
||||
return 0 if(length($msg) < 8);
|
||||
|
||||
my @data;
|
||||
for(my $i = 8; $i < length($msg); $i += 2) {
|
||||
push(@data, ord(pack('H*', substr($msg, $i, 2))));
|
||||
}
|
||||
my $crc = hex(substr($msg, 6, 2));
|
||||
|
||||
# FS20 Repeater generate a CRC which is one or two greater then the computed
|
||||
# one. The FHZ1000 filters such pakets, so we do not see them
|
||||
return (($crc eq FHZ_Crc(@data)) ? 1 : 0);
|
||||
}
|
||||
|
||||
|
||||
#####################################
|
||||
# This is a direct read for commands like get
|
||||
sub
|
||||
FHZ_ReadAnswer($$$)
|
||||
{
|
||||
my ($hash,$arg, $to) = @_;
|
||||
|
||||
return undef if(!$hash || ($^O!~/Win/ && !defined($hash->{FD})));
|
||||
|
||||
my ($mfhzdata, $rin) = ("", '');
|
||||
my $buf;
|
||||
|
||||
for(;;) {
|
||||
|
||||
if($^O =~ m/Win/) {
|
||||
$hash->{PortObj}->read_const_time($to*1000); # set timeout (ms)
|
||||
# Read anstatt input sonst funzt read_const_time nicht.
|
||||
$buf = $hash->{PortObj}->read(999);
|
||||
return "Timeout reading answer for get $arg"
|
||||
if(length($buf) == 0);
|
||||
|
||||
} else {
|
||||
vec($rin, $hash->{FD}, 1) = 1;
|
||||
my $nfound = select($rin, undef, undef, $to);
|
||||
if($nfound < 0) {
|
||||
next if ($! == EAGAIN() || $! == EINTR() || $! == 0);
|
||||
die("Select error $nfound / $!\n");
|
||||
}
|
||||
return "Timeout reading answer for get $arg"
|
||||
if($nfound == 0);
|
||||
$buf = $hash->{PortObj}->input();
|
||||
|
||||
}
|
||||
|
||||
Log 4, "FHZ/RAW: " . unpack('H*',$buf);
|
||||
$mfhzdata .= $buf;
|
||||
next if(length($mfhzdata) < 2);
|
||||
|
||||
my $len = ord(substr($mfhzdata,1,1)) + 2;
|
||||
if($len>20) {
|
||||
Log 4, "Oversized message (" . unpack('H*',$mfhzdata) .
|
||||
"), dropping it ...";
|
||||
return undef;
|
||||
}
|
||||
return unpack('H*', $mfhzdata) if(length($mfhzdata) == $len);
|
||||
}
|
||||
}
|
||||
|
||||
##############
|
||||
# Compute CRC, add header, glue fn and messages
|
||||
sub
|
||||
FHZ_CompleteMsg($$)
|
||||
{
|
||||
my ($fn,$msg) = @_;
|
||||
my $len = length($msg);
|
||||
my @data;
|
||||
for(my $i = 0; $i < $len; $i += 2) {
|
||||
push(@data, ord(pack('H*', substr($msg, $i, 2))));
|
||||
}
|
||||
return pack('C*', 0x81, $len/2+2, ord(pack('H*',$fn)), FHZ_Crc(@data), @data);
|
||||
}
|
||||
|
||||
|
||||
#####################################
|
||||
# Check if the 1% limit is reached and trigger notifies
|
||||
sub
|
||||
FHZ_XmitLimitCheck($$)
|
||||
{
|
||||
my ($hash,$bstring) = @_;
|
||||
my $now = time();
|
||||
|
||||
$bstring = unpack('H*', $bstring);
|
||||
return if($bstring =~ m/c90185$/); # fhtbuf
|
||||
|
||||
if(!$hash->{XMIT_TIME}) {
|
||||
$hash->{XMIT_TIME}[0] = $now;
|
||||
$hash->{NR_CMD_LAST_H} = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
my $nowM1h = $now-3600;
|
||||
my @b = grep { $_ > $nowM1h } @{$hash->{XMIT_TIME}};
|
||||
|
||||
if(@b > 163) { # Maximum nr of transmissions per hour (unconfirmed).
|
||||
|
||||
my $me = $hash->{NAME};
|
||||
Log GetLogLevel($me,2), "FHZ TRANSMIT LIMIT EXCEEDED";
|
||||
DoTrigger($me, "TRANSMIT LIMIT EXCEEDED");
|
||||
|
||||
} else {
|
||||
|
||||
push(@b, $now);
|
||||
|
||||
}
|
||||
$hash->{XMIT_TIME} = \@b;
|
||||
$hash->{NR_CMD_LAST_H} = int(@b);
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub
|
||||
FHZ_Write($$$)
|
||||
{
|
||||
my ($hash,$fn,$msg) = @_;
|
||||
|
||||
if(!$hash || !defined($hash->{PortObj})) {
|
||||
Log 5, "FHZ device $hash->{NAME} is not active, cannot send";
|
||||
return;
|
||||
}
|
||||
|
||||
my $bstring = FHZ_CompleteMsg($fn, $msg);
|
||||
Log 5, "Sending " . unpack('H*', $bstring);
|
||||
|
||||
if(!$hash->{QUEUE}) {
|
||||
|
||||
FHZ_XmitLimitCheck($hash,$bstring);
|
||||
$hash->{QUEUE} = [ $bstring ];
|
||||
$hash->{PortObj}->write($bstring);
|
||||
|
||||
##############
|
||||
# Write the next buffer not earlier than 0.22 seconds (= 65.6ms + 10ms +
|
||||
# 65.6ms + 10ms + 65.6ms), else it will be discarded by the FHZ1X00 PC
|
||||
InternalTimer(gettimeofday()+0.25, "FHZ_HandleWriteQueue", $hash, 1);
|
||||
|
||||
} else {
|
||||
push(@{$hash->{QUEUE}}, $bstring);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub
|
||||
FHZ_HandleWriteQueue($)
|
||||
{
|
||||
my $hash = shift;
|
||||
my $arr = $hash->{QUEUE};
|
||||
|
||||
if(defined($arr) && @{$arr} > 0) {
|
||||
shift(@{$arr});
|
||||
if(@{$arr} == 0) {
|
||||
delete($hash->{QUEUE});
|
||||
return;
|
||||
}
|
||||
my $bstring = $arr->[0];
|
||||
FHZ_XmitLimitCheck($hash,$bstring);
|
||||
$hash->{PortObj}->write($bstring);
|
||||
InternalTimer(gettimeofday()+0.25, "FHZ_HandleWriteQueue", $hash, 1);
|
||||
}
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub
|
||||
FHZ_Reopen($)
|
||||
{
|
||||
my ($hash) = @_;
|
||||
|
||||
my $dev = $hash->{DeviceName};
|
||||
$hash->{PortObj}->close();
|
||||
Log 1, "USB device $dev closed";
|
||||
for(;;) {
|
||||
sleep(5);
|
||||
if($^O =~ m/Win/) {
|
||||
$hash->{PortObj} = new Win32::SerialPort($dev);
|
||||
}else{
|
||||
$hash->{PortObj} = new Device::SerialPort($dev);
|
||||
}
|
||||
if($hash->{PortObj}) {
|
||||
Log 1, "USB device $dev reopened";
|
||||
$hash->{FD} = $hash->{PortObj}->FILENO if($^O !~ m/Win/);
|
||||
FHZ_DoInit($hash->{NAME}, $hash->{ttytype}, $hash->{PortObj});
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub
|
||||
FHZ_Read($)
|
||||
{
|
||||
my ($hash) = @_;
|
||||
|
||||
my $buf = $hash->{PortObj}->input();
|
||||
my $iohash = $modules{$hash->{TYPE}}; # Our (FHZ) module pointer
|
||||
my $name = $hash->{NAME};
|
||||
|
||||
###########
|
||||
# Lets' try again: Some drivers return len(0) on the first read...
|
||||
if(defined($buf) && length($buf) == 0) {
|
||||
$buf = $hash->{PortObj}->input();
|
||||
}
|
||||
|
||||
if(!defined($buf) || length($buf) == 0) {
|
||||
|
||||
my $dev = $hash->{DeviceName};
|
||||
Log 1, "USB device $dev disconnected, waiting to reappear";
|
||||
delete($hash->{FD});
|
||||
$hash->{PortObj}->close();
|
||||
delete($hash->{PortObj});
|
||||
delete($hash->{FD});
|
||||
delete($selectlist{"$name.$dev"});
|
||||
$readyfnlist{"$name.$dev"} = $hash; # Start polling
|
||||
$hash->{STATE} = "disconnected";
|
||||
|
||||
# Without the following sleep the open of the device causes a SIGSEGV,
|
||||
# and following opens block infinitely. Only a reboot helps.
|
||||
sleep(5);
|
||||
|
||||
DoTrigger($name, "DISCONNECTED");
|
||||
}
|
||||
|
||||
|
||||
my $fhzdata = $hash->{PARTIAL};
|
||||
Log 4, "FHZ/RAW: " . unpack('H*',$buf) .
|
||||
" (Unparsed: " . unpack('H*', $fhzdata) . ")";
|
||||
$fhzdata .= $buf;
|
||||
|
||||
while(length($fhzdata) > 2) {
|
||||
|
||||
###################################
|
||||
# Skip trash.
|
||||
my $si = index($fhzdata, $msgstart);
|
||||
if($si) {
|
||||
if($si == -1) {
|
||||
Log(5, "Bogus message received, no start character found");
|
||||
$fhzdata = "";
|
||||
last;
|
||||
} else {
|
||||
Log(5, "Bogus message received, skipping to start character");
|
||||
$fhzdata = substr($fhzdata, $si);
|
||||
}
|
||||
}
|
||||
|
||||
my $len = ord(substr($fhzdata,1,1)) + 2;
|
||||
if($len>20) {
|
||||
Log 4,
|
||||
"Oversized message (" . unpack('H*',$fhzdata) . "), dropping it ...";
|
||||
$fhzdata = "";
|
||||
next;
|
||||
}
|
||||
|
||||
last if(length($fhzdata) < $len);
|
||||
|
||||
my $dmsg = unpack('H*', substr($fhzdata, 0, $len));
|
||||
if(FHZ_CheckCrc($dmsg)) {
|
||||
|
||||
if(substr($fhzdata,2,1) eq $msgstart) { # Skip function 0x81
|
||||
$fhzdata = substr($fhzdata, 2);
|
||||
next;
|
||||
}
|
||||
|
||||
$hash->{RAWMSG} = $dmsg;
|
||||
my $foundp = Dispatch($hash, $dmsg);
|
||||
if($foundp) {
|
||||
foreach my $d (@{$foundp}) {
|
||||
next if(!$defs{$d});
|
||||
$defs{$d}{RAWMSG} = $dmsg;
|
||||
}
|
||||
}
|
||||
|
||||
$fhzdata = substr($fhzdata, $len);
|
||||
|
||||
} else {
|
||||
|
||||
Log 4, "Bad CRC message, skipping it (Bogus message follows)";
|
||||
$fhzdata = substr($fhzdata, 2);
|
||||
|
||||
}
|
||||
}
|
||||
$hash->{PARTIAL} = $fhzdata;
|
||||
}
|
||||
|
||||
1;
|
|
@ -1,97 +0,0 @@
|
|||
##############################################
|
||||
package main;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use Time::HiRes qw(gettimeofday);
|
||||
use Lirc::Client;
|
||||
use IO::Select;
|
||||
|
||||
my $def;
|
||||
|
||||
#####################################
|
||||
# Note: we are a data provider _and_ a consumer at the same time
|
||||
sub
|
||||
LIRC_Initialize($)
|
||||
{
|
||||
my ($hash) = @_;
|
||||
Log 1, "LIRC_Initialize";
|
||||
|
||||
# Provider
|
||||
$hash->{ReadFn} = "LIRC_Read";
|
||||
$hash->{Clients} = ":LIRC:";
|
||||
|
||||
# Consumer
|
||||
$hash->{DefFn} = "LIRC_Define";
|
||||
$hash->{UndefFn} = "LIRC_Undef";
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub
|
||||
LIRC_Define($$)
|
||||
{
|
||||
my ($hash, $def) = @_;
|
||||
my @a = split("[ \t][ \t]*", $def);
|
||||
|
||||
$hash->{STATE} = "Initialized";
|
||||
|
||||
delete $hash->{LircObj};
|
||||
delete $hash->{FD};
|
||||
|
||||
my $name = $a[0];
|
||||
my $config = $a[2];
|
||||
|
||||
Log 3, "LIRC opening LIRC device $config";
|
||||
my $lirc = Lirc::Client->new({
|
||||
prog => 'fhem',
|
||||
rcfile => "$config",
|
||||
debug => 0,
|
||||
fake => 0,
|
||||
});
|
||||
return "Can't open $config: $!\n" if(!$lirc);
|
||||
Log 3, "LIRC opened $name device $config";
|
||||
|
||||
my $select = IO::Select->new();
|
||||
$select->add( $lirc->sock );
|
||||
|
||||
$hash->{LircObj} = $lirc;
|
||||
$hash->{FD} = $lirc->sock;
|
||||
$selectlist{"$name.$config"} = $hash;
|
||||
$hash->{SelectObj} = $select;
|
||||
$hash->{DeviceName} = $name;
|
||||
$hash->{STATE} = "Opened";
|
||||
|
||||
return undef;
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub
|
||||
LIRC_Undef($$)
|
||||
{
|
||||
my ($hash, $arg) = @_;
|
||||
|
||||
$hash->{LircObj}->close() if($hash->{LircObj});
|
||||
return undef;
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub
|
||||
LIRC_Read($)
|
||||
{
|
||||
my ($hash) = @_;
|
||||
|
||||
my $lirc= $hash->{LircObj};
|
||||
my $select= $hash->{SelectObj};
|
||||
|
||||
if( my @ready = $select->can_read(0) ){
|
||||
# an ir event has been received (if you are tracking other filehandles, you need to make sure it is lirc)
|
||||
my @codes = $lirc->next_codes; # should not block
|
||||
for my $code (@codes){
|
||||
Log 3, "LIRC code: $code\n";
|
||||
DoTrigger($code, "toggle");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
1;
|
|
@ -1,129 +0,0 @@
|
|||
#
|
||||
#
|
||||
# 09_BS.pm
|
||||
# written by Dr. Boris Neubert 2009-06-20
|
||||
# e-mail: omega at online dot de
|
||||
#
|
||||
##############################################
|
||||
package main;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
my $PI= 3.141592653589793238;
|
||||
|
||||
my %defptr;
|
||||
|
||||
#############################
|
||||
sub
|
||||
BS_Initialize($)
|
||||
{
|
||||
my ($hash) = @_;
|
||||
|
||||
$hash->{Match} = "^81..(04|0c)..0101a001a5cf......";
|
||||
$hash->{DefFn} = "BS_Define";
|
||||
$hash->{UndefFn} = "BS_Undef";
|
||||
$hash->{ParseFn} = "BS_Parse";
|
||||
$hash->{AttrList} = "IODev do_not_notify:1,0 showtime:0,1 dummy:1,0 model:BS loglevel:0,1,2,3,4,5,6";
|
||||
|
||||
}
|
||||
|
||||
|
||||
#############################
|
||||
sub
|
||||
BS_Define($$)
|
||||
{
|
||||
my ($hash, $def) = @_;
|
||||
my @a = split("[ \t][ \t]*", $def);
|
||||
|
||||
my $u= "wrong syntax: define <name> BS <sensor> [RExt]";
|
||||
return $u if((int(@a)< 3) || (int(@a)>4));
|
||||
|
||||
my $name = $a[0];
|
||||
my $sensor = $a[2];
|
||||
if($sensor !~ /[123456789]/) {
|
||||
return "erroneous sensor specification $sensor, use one of 1..9";
|
||||
}
|
||||
$sensor= "0$sensor";
|
||||
|
||||
my $RExt = 50000; # default is 50kOhm
|
||||
$RExt= $a[3] if(int(@a)==4);
|
||||
$hash->{SENSOR}= "$sensor";
|
||||
$hash->{RExt}= $RExt;
|
||||
|
||||
my $dev= "a5cf $sensor";
|
||||
$hash->{DEF}= $dev;
|
||||
|
||||
$defptr{$dev} = $hash;
|
||||
AssignIoPort($hash);
|
||||
}
|
||||
|
||||
#############################
|
||||
sub
|
||||
BS_Undef($$)
|
||||
{
|
||||
my ($hash, $name) = @_;
|
||||
delete($defptr{$hash->{DEF}});
|
||||
return undef;
|
||||
}
|
||||
|
||||
#############################
|
||||
sub
|
||||
BS_Parse($$)
|
||||
{
|
||||
my ($hash, $msg) = @_; # hash points to the FHZ, not to the BS
|
||||
|
||||
|
||||
# Msg format:
|
||||
# 01 23 45 67 8901 2345 6789 01 23 45 67
|
||||
# 81 0c 04 .. 0101 a001 a5cf xx 00 zz zz
|
||||
|
||||
my $sensor= substr($msg, 20, 2);
|
||||
my $dev= "a5cf $sensor";
|
||||
|
||||
my $def= $defptr{$dev};
|
||||
if(!defined($def)) {
|
||||
Log 3, sprintf("BS Unknown device %s, please define it", $sensor);
|
||||
return "UNDEFINED BS";
|
||||
}
|
||||
|
||||
my $name= $def->{NAME};
|
||||
|
||||
return "" if($def->{IODev} && $def->{IODev}{NAME} ne $hash->{NAME});
|
||||
|
||||
my $t= TimeNow();
|
||||
|
||||
my $flags= hex(substr($msg, 24, 1)) & 0xdc;
|
||||
my $value= hex(substr($msg, 25, 3)) & 0x3ff;
|
||||
|
||||
my $RExt= $def->{RExt};
|
||||
my $brightness= $value/10.24; # Vout in percent of reference voltage 1.1V
|
||||
|
||||
# brightness in lux= 100lux*(VOut/RExt/1.8muA)^2;
|
||||
my $VOut= $value*1.1/1024.0;
|
||||
my $temp= $VOut/$RExt/1.8E-6;
|
||||
my $lux= 100.0*$temp*$temp;
|
||||
|
||||
my $state= sprintf("brightness: %.2f lux: %.0f flags: %d",
|
||||
$brightness, $lux, $flags);
|
||||
|
||||
$def->{CHANGED}[0] = $state;
|
||||
$def->{STATE} = $state;
|
||||
$def->{READINGS}{state}{TIME} = $t;
|
||||
$def->{READINGS}{state}{VAL} = $state;
|
||||
Log GetLogLevel($name, 4), "BS $name: $state";
|
||||
|
||||
$def->{READINGS}{brightness}{TIME} = $t;
|
||||
$def->{READINGS}{brightness}{VAL} = $brightness;
|
||||
$def->{READINGS}{lux}{TIME} = $t;
|
||||
$def->{READINGS}{lux}{VAL} = $lux;
|
||||
$def->{READINGS}{flags}{TIME} = $t;
|
||||
$def->{READINGS}{flags}{VAL} = $flags;
|
||||
|
||||
return $name;
|
||||
|
||||
}
|
||||
|
||||
#############################
|
||||
|
||||
1;
|
|
@ -1,174 +0,0 @@
|
|||
#
|
||||
#
|
||||
# 09_USF1000.pm
|
||||
# written by Dr. Boris Neubert 2009-06-20
|
||||
# e-mail: omega at online dot de
|
||||
#
|
||||
##############################################
|
||||
package main;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
my $PI= 3.141592653589793238;
|
||||
|
||||
my %defptr;
|
||||
my $dev= "a5ce aa";
|
||||
|
||||
#############################
|
||||
sub
|
||||
USF1000_Initialize($)
|
||||
{
|
||||
my ($hash) = @_;
|
||||
|
||||
$hash->{Match} = "^81..(04|0c)..0101a001a5ceaa00....";
|
||||
$hash->{DefFn} = "USF1000_Define";
|
||||
$hash->{UndefFn} = "USF1000_Undef";
|
||||
$hash->{ParseFn} = "USF1000_Parse";
|
||||
$hash->{AttrList} = "IODev do_not_notify:1,0 showtime:0,1 dummy:1,0 model:usf1000s loglevel:0,1,2,3,4,5,6";
|
||||
|
||||
}
|
||||
|
||||
|
||||
#############################
|
||||
sub
|
||||
USF1000_Define($$)
|
||||
{
|
||||
my ($hash, $def) = @_;
|
||||
my @a = split("[ \t][ \t]*", $def);
|
||||
|
||||
my $u= "wrong syntax: define <name> USF1000 geometry";
|
||||
my $g= "wrong geometry for USF1000";
|
||||
|
||||
# geometry (units: meter)
|
||||
# cub length width height offset cuboid 3+4
|
||||
# cylv diameter height offset vertical cylinder 3+3
|
||||
# the offset is measured from the TOP of the box!
|
||||
|
||||
return $u if(int(@a)< 6);
|
||||
|
||||
my $name = $a[0];
|
||||
my $geometry = $a[2];
|
||||
|
||||
if($geometry eq "cub") {
|
||||
# cuboid
|
||||
return $g if(int(@a)< 7);
|
||||
$hash->{GEOMETRY}= $geometry;
|
||||
$hash->{LENGTH}= $a[3];
|
||||
$hash->{WIDTH}= $a[4];
|
||||
$hash->{HEIGHT}= $a[5];
|
||||
$hash->{OFFSET}= $a[6];
|
||||
$hash->{CAPACITY}= int($hash->{LENGTH}*$hash->{WIDTH}*$hash->{HEIGHT}*100.0+0.5)*10.0;
|
||||
} elsif($geometry eq "cylv") {
|
||||
# vertical cylinder
|
||||
return $g if(int(@a)< 6);
|
||||
$hash->{GEOMETRY}= $geometry;
|
||||
$hash->{DIAMETER}= $a[3];
|
||||
$hash->{HEIGHT}= $a[4];
|
||||
$hash->{OFFSET}= $a[5];
|
||||
$hash->{CAPACITY}= int($PI*$hash->{DIAMETER}*$hash->{DIAMETER}/4.0*$hash->{HEIGHT}*100.0+0.5)*10.0;
|
||||
} else {
|
||||
return $g;
|
||||
}
|
||||
|
||||
$defptr{$dev} = $hash;
|
||||
AssignIoPort($hash);
|
||||
}
|
||||
|
||||
#############################
|
||||
sub
|
||||
USF1000_Undef($$)
|
||||
{
|
||||
my ($hash, $name) = @_;
|
||||
delete($defptr{$dev});
|
||||
return undef;
|
||||
}
|
||||
|
||||
#############################
|
||||
sub
|
||||
USF1000_Parse($$)
|
||||
{
|
||||
my ($hash, $msg) = @_; # hash points to the FHZ, not to the USF1000
|
||||
|
||||
if(!defined($defptr{$dev})) {
|
||||
Log 3, "USF1000 Unknown device, please define it";
|
||||
return "UNDEFINED USF1000";
|
||||
}
|
||||
|
||||
my $def= $defptr{$dev};
|
||||
my $name= $def->{NAME};
|
||||
|
||||
return "" if($def->{IODev} && $def->{IODev}{NAME} ne $hash->{NAME});
|
||||
|
||||
|
||||
my $t= TimeNow();
|
||||
|
||||
# Msg format:
|
||||
# 01 23 45 67 8901 2345 6789 01 23 45 67
|
||||
# 81 0c 04 .. 0101 a001 a5ce aa 00 cc xx
|
||||
|
||||
my $cc= substr($msg, 24, 2);
|
||||
my $xx= substr($msg, 26, 2);
|
||||
|
||||
|
||||
my $lowbattery= (hex($cc) & 0x40 ? 1 : 0);
|
||||
my $testmode= (hex($cc) & 0x80 ? 1 : 0);
|
||||
my $distance= hex($xx)/100.0; # in meters
|
||||
my $valid= (($distance>0.00) && ($distance<2.55));
|
||||
|
||||
|
||||
if($valid) {
|
||||
my $wlevel = $def->{HEIGHT}-($distance-$def->{OFFSET}); # water level
|
||||
|
||||
my $geometry= $def->{GEOMETRY};
|
||||
my $capacity= $def->{CAPACITY}; # capacity of tank (for distance= offset) in liters
|
||||
my $volume; # current volume in tank in liters
|
||||
my $flevel; # fill level in percent
|
||||
|
||||
if($geometry eq "cub") {
|
||||
# cuboid
|
||||
$volume = $def->{LENGTH}*$def->{WIDTH}*$wlevel*1000.0;
|
||||
} elsif($geometry eq "cylv") {
|
||||
# vertical cylinder
|
||||
$volume = $PI*$def->{DIAMETER}*$def->{DIAMETER}/4.0*$wlevel*1000.0;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$flevel = int($volume/$capacity*100.0+0.5);
|
||||
$volume= int($volume/10.0+0.5)*10.0;
|
||||
|
||||
|
||||
my $state= sprintf("v: %d V: %d", $flevel, $volume);
|
||||
|
||||
$def->{CHANGED}[0] = $state;
|
||||
$def->{STATE} = $state;
|
||||
$def->{READINGS}{state}{TIME} = $t;
|
||||
$def->{READINGS}{state}{VAL} = $state;
|
||||
Log GetLogLevel($name, 4), "USF1000 $name: $state";
|
||||
|
||||
$def->{READINGS}{distance}{TIME} = $t;
|
||||
$def->{READINGS}{distance}{VAL} = $distance;
|
||||
$def->{READINGS}{level}{TIME} = $t;
|
||||
$def->{READINGS}{level}{VAL} = $flevel;
|
||||
$def->{READINGS}{volume}{TIME} = $t;
|
||||
$def->{READINGS}{volume}{VAL} = $volume;
|
||||
}
|
||||
|
||||
my $warnings= ($lowbattery ? "Battery low" : "");
|
||||
if($testmode) {
|
||||
$warnings.= "; " if($warnings);
|
||||
$warnings.= "Test mode";
|
||||
}
|
||||
$warnings= $warnings ? $warnings : "none";
|
||||
|
||||
$def->{READINGS}{"warnings"}{TIME} = $t;
|
||||
$def->{READINGS}{"warnings"}{VAL} = $warnings;
|
||||
|
||||
return $name;
|
||||
|
||||
}
|
||||
|
||||
#############################
|
||||
|
||||
1;
|
|
@ -1,420 +0,0 @@
|
|||
##############################################
|
||||
package main;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
|
||||
my %codes = (
|
||||
"00" => "off",
|
||||
"01" => "dim06%",
|
||||
"02" => "dim12%",
|
||||
"03" => "dim18%",
|
||||
"04" => "dim25%",
|
||||
"05" => "dim31%",
|
||||
"06" => "dim37%",
|
||||
"07" => "dim43%",
|
||||
"08" => "dim50%",
|
||||
"09" => "dim56%",
|
||||
"0a" => "dim62%",
|
||||
"0b" => "dim68%",
|
||||
"0c" => "dim75%",
|
||||
"0d" => "dim81%",
|
||||
"0e" => "dim87%",
|
||||
"0f" => "dim93%",
|
||||
"10" => "dim100%",
|
||||
"11" => "on", # Set to previous dim value (before switching it off)
|
||||
"12" => "toggle", # between off and previous dim val
|
||||
"13" => "dimup",
|
||||
"14" => "dimdown",
|
||||
"15" => "dimupdown",
|
||||
"16" => "timer",
|
||||
"17" => "sendstate",
|
||||
"18" => "off-for-timer",
|
||||
"19" => "on-for-timer",
|
||||
"1a" => "on-old-for-timer",
|
||||
"1b" => "reset",
|
||||
"1c" => "ramp-on-time", #time to reach the desired dim value on dimmers
|
||||
"1d" => "ramp-off-time", #time to reach the off state on dimmers
|
||||
"1e" => "on-old-for-timer-prev", # old val for timer, then go to prev. state
|
||||
"1f" => "on-100-for-timer-prev", # 100% for timer, then go to previous state
|
||||
|
||||
);
|
||||
|
||||
my %readonly = (
|
||||
"thermo-on" => 1,
|
||||
"thermo-off" => 1,
|
||||
);
|
||||
|
||||
use vars qw(%fs20_c2b); # Peter would like to access it from outside
|
||||
|
||||
# defptr{XMIT BTN}{DEVNAME} -> Ptr to global defs entry for this device
|
||||
my %defptr;
|
||||
|
||||
my %follow;
|
||||
my $fs20_simple ="off off-for-timer on on-for-timer on-till reset timer toggle";
|
||||
my %models = (
|
||||
fs20hgs => 'sender',
|
||||
fs20ls => 'sender',
|
||||
fs20pira => 'sender',
|
||||
fs20piri => 'sender',
|
||||
fs20s20 => 'sender',
|
||||
fs20s16 => 'sender',
|
||||
fs20s8 => 'sender',
|
||||
fs20s4 => 'sender',
|
||||
fs20s4a => 'sender',
|
||||
fs20s4m => 'sender',
|
||||
fs20s4u => 'sender',
|
||||
fs20s4ub => 'sender',
|
||||
fs20sd => 'sender',
|
||||
fs20sn => 'sender',
|
||||
fs20sr => 'sender',
|
||||
fs20ss => 'sender',
|
||||
fs20str => 'sender',
|
||||
fs20tfk => 'sender',
|
||||
fs20tk => 'sender',
|
||||
fs20uts => 'sender',
|
||||
fs20ze => 'sender',
|
||||
|
||||
fs20ms2 => 'simple',
|
||||
fs20as1 => 'simple',
|
||||
fs20as4 => 'simple',
|
||||
fs20di => 'dimmer',
|
||||
fs20di10 => 'dimmer',
|
||||
fs20du => 'dimmer',
|
||||
fs20rst => 'simple',
|
||||
fs20sa => 'simple',
|
||||
fs20sig => 'simple',
|
||||
fs20sm4 => 'simple',
|
||||
fs20st => 'simple',
|
||||
fs20su => 'simple',
|
||||
fs20sv => 'simple',
|
||||
fs20ue1 => 'simple',
|
||||
fs20usr => 'simple',
|
||||
);
|
||||
|
||||
|
||||
sub
|
||||
FS20_Initialize($)
|
||||
{
|
||||
my ($hash) = @_;
|
||||
|
||||
foreach my $k (keys %codes) {
|
||||
$fs20_c2b{$codes{$k}} = $k;
|
||||
}
|
||||
$fs20_c2b{"on-till"} = 99;
|
||||
|
||||
$hash->{Match} = "^81..(04|0c)..0101a001";
|
||||
$hash->{SetFn} = "FS20_Set";
|
||||
$hash->{StateFn} = "FS20_SetState";
|
||||
$hash->{DefFn} = "FS20_Define";
|
||||
$hash->{UndefFn} = "FS20_Undef";
|
||||
$hash->{ParseFn} = "FS20_Parse";
|
||||
$hash->{AttrList} = "IODev follow-on-for-timer:1,0 do_not_notify:1,0 dummy:1,0 showtime:1,0 model;fs20hgs,fs20hgs,fs20pira,fs20piri,fs20s20,fs20s8,fs20s4,fs20s4a,fs20s4m,fs20s4u,fs20s4ub,fs20sd,fs20sn,fs20sr,fs20ss,fs20str,fs20tfk,fs20tfk,fs20tk,fs20uts,fs20ze,fs20as1,fs20as4,fs20di,fs20du,fs20ls,fs20ms2,fs20rst,fs20sa,fs20sig,fs20st,fs20sv,fs20usr loglevel:0,1,2,3,4,5,6";
|
||||
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub
|
||||
FS20_SetState($$$$)
|
||||
{
|
||||
my ($hash, $tim, $vt, $val) = @_;
|
||||
|
||||
$val = $1 if($val =~ m/^(.*) \d+$/);
|
||||
return "Undefined value $val" if(!defined($fs20_c2b{$val}));
|
||||
return undef;
|
||||
}
|
||||
|
||||
#############################
|
||||
sub
|
||||
Do_On_Till($@)
|
||||
{
|
||||
my ($hash, @a) = @_;
|
||||
return "Timespec (HH:MM[:SS]) needed for the on-till command" if(@a != 3);
|
||||
|
||||
my ($err, $hr, $min, $sec, $fn) = GetTimeSpec($a[2]);
|
||||
return $err if($err);
|
||||
|
||||
my @lt = localtime;
|
||||
my $hms_till = sprintf("%02d:%02d:%02d", $hr, $min, $sec);
|
||||
my $hms_now = sprintf("%02d:%02d:%02d", $lt[2], $lt[1], $lt[0]);
|
||||
if($hms_now ge $hms_till) {
|
||||
Log 4, "on-till: won't switch as now ($hms_now) is later than $hms_till";
|
||||
return "";
|
||||
}
|
||||
|
||||
my @b = ($a[0], "on");
|
||||
FS20_Set($hash, @b);
|
||||
CommandDefine(undef, $hash->{NAME} . "_till at $hms_till set $a[0] off");
|
||||
|
||||
}
|
||||
|
||||
|
||||
###################################
|
||||
sub
|
||||
FS20_Set($@)
|
||||
{
|
||||
my ($hash, @a) = @_;
|
||||
my $ret = undef;
|
||||
my $na = int(@a);
|
||||
|
||||
return "no set value specified" if($na < 2 || $na > 3);
|
||||
return "Readonly value $a[1]" if(defined($readonly{$a[1]}));
|
||||
|
||||
my $c = $fs20_c2b{$a[1]};
|
||||
if(!defined($c)) {
|
||||
|
||||
# Model specific set arguments
|
||||
if(defined($attr{$a[0]}) && defined($attr{$a[0]}{"model"})) {
|
||||
my $mt = $models{$attr{$a[0]}{"model"}};
|
||||
return "Unknown argument $a[1], choose one of "
|
||||
if($mt && $mt eq "sender");
|
||||
return "Unknown argument $a[1], choose one of $fs20_simple"
|
||||
if($mt && $mt eq "simple");
|
||||
}
|
||||
return "Unknown argument $a[1], choose one of " .
|
||||
join(" ", sort keys %fs20_c2b);
|
||||
|
||||
}
|
||||
|
||||
return Do_On_Till($hash, @a) if($a[1] eq "on-till");
|
||||
|
||||
return "Bad time spec" if($na == 3 && $a[2] !~ m/^\d*\.?\d+$/);
|
||||
|
||||
my $v = join(" ", @a);
|
||||
Log GetLogLevel($a[0],2), "FS20 set $v";
|
||||
(undef, $v) = split(" ", $v, 2); # Not interested in the name...
|
||||
|
||||
my $val;
|
||||
|
||||
if($na == 3) { # Timed command.
|
||||
$c =~ s/1/3/; # Set the extension bit
|
||||
|
||||
########################
|
||||
# Calculating the time.
|
||||
LOOP: for(my $i = 0; $i <= 12; $i++) {
|
||||
for(my $j = 0; $j <= 15; $j++) {
|
||||
$val = (2**$i)*$j*0.25;
|
||||
if($val >= $a[2]) {
|
||||
if($val != $a[2]) {
|
||||
$ret = "FS20 Setting timeout to $val from $a[2]";
|
||||
Log GetLogLevel($a[0],2), $ret;
|
||||
}
|
||||
$c .= sprintf("%x%x", $i, $j);
|
||||
last LOOP;
|
||||
}
|
||||
}
|
||||
}
|
||||
return "Specified timeout too large, max is 15360" if(length($c) == 2);
|
||||
}
|
||||
|
||||
IOWrite($hash, "04", "010101" . $hash->{XMIT} . $hash->{BTN} . $c)
|
||||
if(!IsDummy($a[0]));
|
||||
|
||||
###########################################
|
||||
# Set the state of a device to off if on-for-timer is called
|
||||
if($follow{$a[0]}) {
|
||||
CommandDelete(undef, $a[0] . "_timer");
|
||||
delete $follow{$a[0]};
|
||||
}
|
||||
if($a[1] =~ m/for-timer/ && $na == 3 &&
|
||||
defined($attr{$a[0]}) && defined($attr{$a[0]}{"follow-on-for-timer"})) {
|
||||
my $to = sprintf("%02d:%02d:%02d", $val/3600, ($val%3600)/60, $val%60);
|
||||
$follow{$a[0]} = $to;
|
||||
Log 4, "Follow: +$to setstate $a[0] off";
|
||||
CommandDefine(undef, $a[0] . "_timer at +$to setstate $a[0] off");
|
||||
}
|
||||
|
||||
##########################
|
||||
# Look for all devices with the same code, and set state, timestamp
|
||||
my $code = "$hash->{XMIT} $hash->{BTN}";
|
||||
my $tn = TimeNow();
|
||||
foreach my $n (keys %{ $defptr{$code} }) {
|
||||
|
||||
my $lh = $defptr{$code}{$n};
|
||||
$lh->{CHANGED}[0] = $v;
|
||||
$lh->{STATE} = $v;
|
||||
$lh->{READINGS}{state}{TIME} = $tn;
|
||||
$lh->{READINGS}{state}{VAL} = $v;
|
||||
}
|
||||
return $ret;
|
||||
}
|
||||
|
||||
#############################
|
||||
sub
|
||||
FS20_Define($$)
|
||||
{
|
||||
my ($hash, $def) = @_;
|
||||
my @a = split("[ \t][ \t]*", $def);
|
||||
|
||||
my $u = "wrong syntax: define <name> FS20 housecode " .
|
||||
"addr [fg addr] [lm addr] [gm FF]";
|
||||
|
||||
return $u if(int(@a) < 4);
|
||||
return "Define $a[0]: wrong housecode format: specify a 4 digit hex value ".
|
||||
"or an 8 digit quad value"
|
||||
if( ($a[2] !~ m/^[a-f0-9]{4}$/i) && ($a[2] !~ m/^[1-4]{8}$/i) );
|
||||
|
||||
return "Define $a[0]: wrong btn format: specify a 2 digit hex value " .
|
||||
"or a 4 digit quad value"
|
||||
if( ($a[3] !~ m/^[a-f0-9]{2}$/i) && ($a[3] !~ m/^[1-4]{4}$/i) );
|
||||
|
||||
my $housecode = $a[2];
|
||||
$housecode = four2hex($housecode,4) if (length($housecode) == 8);
|
||||
|
||||
my $btncode = $a[3];
|
||||
$btncode = four2hex($btncode,2) if (length($btncode) == 4);
|
||||
|
||||
$hash->{XMIT} = lc($housecode);
|
||||
$hash->{BTN} = lc($btncode);
|
||||
|
||||
my $code = "$housecode $btncode";
|
||||
my $ncode = 1;
|
||||
my $name = $a[0];
|
||||
|
||||
$hash->{CODE}{$ncode++} = $code;
|
||||
$defptr{$code}{$name} = $hash;
|
||||
|
||||
for(my $i = 4; $i < int(@a); $i += 2) {
|
||||
|
||||
return "No address specified for $a[$i]" if($i == int(@a)-1);
|
||||
|
||||
$a[$i] = lc($a[$i]);
|
||||
if($a[$i] eq "fg") {
|
||||
return "Bad fg address for $name, see the doc"
|
||||
if( ($a[$i+1] !~ m/^f[a-f0-9]$/) && ($a[$i+1] !~ m/^44[1-4][1-4]$/));
|
||||
} elsif($a[$i] eq "lm") {
|
||||
return "Bad lm address for $name, see the doc"
|
||||
if( ($a[$i+1] !~ m/^[a-f0-9]f$/) && ($a[$i+1] !~ m/^[1-4][1-4]44$/));
|
||||
} elsif($a[$i] eq "gm") {
|
||||
return "Bad gm address for $name, must be ff"
|
||||
if( ($a[$i+1] ne "ff") && ($a[$i+1] ne "4444"));
|
||||
} else {
|
||||
return $u;
|
||||
}
|
||||
|
||||
my $grpcode = $a[$i+1];
|
||||
if (length($grpcode) == 4) {
|
||||
$grpcode = four2hex($grpcode,2);
|
||||
}
|
||||
|
||||
$code = "$housecode $grpcode";
|
||||
$hash->{CODE}{$ncode++} = $code;
|
||||
$defptr{$code}{$name} = $hash;
|
||||
}
|
||||
AssignIoPort($hash);
|
||||
}
|
||||
|
||||
#############################
|
||||
sub
|
||||
FS20_Undef($$)
|
||||
{
|
||||
my ($hash, $name) = @_;
|
||||
foreach my $c (keys %{ $hash->{CODE} } ) {
|
||||
$c = $hash->{CODE}{$c};
|
||||
|
||||
# As after a rename the $name my be different from the $defptr{$c}{$n}
|
||||
# we look for the hash.
|
||||
foreach my $dname (keys %{ $defptr{$c} }) {
|
||||
delete($defptr{$c}{$dname}) if($defptr{$c}{$dname} == $hash);
|
||||
}
|
||||
}
|
||||
return undef;
|
||||
}
|
||||
|
||||
sub
|
||||
FS20_Parse($$)
|
||||
{
|
||||
my ($hash, $msg) = @_;
|
||||
|
||||
# Msg format:
|
||||
# 81 0b 04 f7 0101 a001 HHHH 01 00 11
|
||||
|
||||
my $dev = substr($msg, 16, 4);
|
||||
my $btn = substr($msg, 20, 2);
|
||||
my $cde = substr($msg, 24, 2);
|
||||
|
||||
|
||||
my $dur = 0;
|
||||
my $cx = hex($cde);
|
||||
if($cx & 0x20) {
|
||||
$dur = hex(substr($msg, 26, 2));
|
||||
my $i = ($dur & 0xf0) / 16;
|
||||
my $j = ($dur & 0xf);
|
||||
$dur = (2**$i)*$j*0.25;
|
||||
$cde = sprintf("%02x", $cx & ~0x20);
|
||||
}
|
||||
|
||||
my $v = $codes{$cde};
|
||||
$v = "unknown_$cde" if(!defined($v));
|
||||
$v .= " $dur" if($dur);
|
||||
|
||||
my $def = $defptr{"$dev $btn"};
|
||||
if($def) {
|
||||
my @list;
|
||||
foreach my $n (keys %{ $def }) {
|
||||
my $lh = $def->{$n};
|
||||
return "" if($lh->{IODev} && $lh->{IODev}{NAME} ne $hash->{NAME});
|
||||
$lh->{CHANGED}[0] = $v;
|
||||
$lh->{STATE} = $v;
|
||||
$lh->{READINGS}{state}{TIME} = TimeNow();
|
||||
$lh->{READINGS}{state}{VAL} = $v;
|
||||
Log GetLogLevel($n,2), "FS20 $n $v";
|
||||
|
||||
if($follow{$n}) {
|
||||
CommandDelete(undef, $n . "_timer");
|
||||
delete $follow{$n};
|
||||
}
|
||||
if($v =~ m/for-timer/ &&
|
||||
defined($attr{$n}) &&
|
||||
defined($attr{$n}{"follow-on-for-timer"})) {
|
||||
my $to = sprintf("%02d:%02d:%02d", $dur/3600, ($dur%3600)/60, $dur%60);
|
||||
Log 4, "Follow: +$to setstate $n off";
|
||||
CommandDefine(undef, $n . "_timer at +$to setstate $n off");
|
||||
$follow{$n} = $to;
|
||||
}
|
||||
|
||||
push(@list, $n);
|
||||
}
|
||||
return @list;
|
||||
|
||||
} else {
|
||||
# Special FHZ initialization parameter. In Multi-FHZ-Mode we receive
|
||||
# it by the second FHZ
|
||||
return "" if($dev eq "0001" && $btn eq "00" && $cde eq "00");
|
||||
|
||||
my $dev_four = hex2four($dev);
|
||||
my $btn_four = hex2four($btn);
|
||||
Log 3, "FS20 Unknown device $dev ($dev_four), " .
|
||||
"Button $btn ($btn_four) Code $cde ($v), please define it";
|
||||
return "UNDEFINED FS20: $dev/$btn/$cde";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#############################
|
||||
sub
|
||||
hex2four($)
|
||||
{
|
||||
my $v = shift;
|
||||
my $r = "";
|
||||
foreach my $x (split("", $v)) {
|
||||
$r .= sprintf("%d%d", (hex($x)/4)+1, (hex($x)%4)+1);
|
||||
}
|
||||
return $r;
|
||||
}
|
||||
|
||||
#############################
|
||||
sub
|
||||
four2hex($$)
|
||||
{
|
||||
my ($v,$len) = @_;
|
||||
my $r = 0;
|
||||
foreach my $x (split("", $v)) {
|
||||
$r = $r*4+($x-1);
|
||||
}
|
||||
return sprintf("%0*x", $len,$r);
|
||||
}
|
||||
|
||||
|
||||
1;
|
|
@ -1,567 +0,0 @@
|
|||
#############################################
|
||||
package main;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
sub doSoftBuffer($);
|
||||
sub softBufferTimer($);
|
||||
sub getFhtMin($);
|
||||
sub getFhtBuffer($);
|
||||
|
||||
my %codes = (
|
||||
"00" => "actuator",
|
||||
"01" => "actuator1",
|
||||
"02" => "actuator2",
|
||||
"03" => "actuator3",
|
||||
"04" => "actuator4",
|
||||
"05" => "actuator5",
|
||||
"06" => "actuator6",
|
||||
"07" => "actuator7",
|
||||
"08" => "actuator8",
|
||||
|
||||
"14" => "mon-from1",
|
||||
"15" => "mon-to1",
|
||||
"16" => "mon-from2",
|
||||
"17" => "mon-to2",
|
||||
"18" => "tue-from1",
|
||||
"19" => "tue-to1",
|
||||
"1a" => "tue-from2",
|
||||
"1b" => "tue-to2",
|
||||
"1c" => "wed-from1",
|
||||
"1d" => "wed-to1",
|
||||
"1e" => "wed-from2",
|
||||
"1f" => "wed-to2",
|
||||
"20" => "thu-from1",
|
||||
"21" => "thu-to1",
|
||||
"22" => "thu-from2",
|
||||
"23" => "thu-to2",
|
||||
"24" => "fri-from1",
|
||||
"25" => "fri-to1",
|
||||
"26" => "fri-from2",
|
||||
"27" => "fri-to2",
|
||||
"28" => "sat-from1",
|
||||
"29" => "sat-to1",
|
||||
"2a" => "sat-from2",
|
||||
"2b" => "sat-to2",
|
||||
"2c" => "sun-from1",
|
||||
"2d" => "sun-to1",
|
||||
"2e" => "sun-from2",
|
||||
"2f" => "sun-to2",
|
||||
|
||||
"3e" => "mode",
|
||||
"3f" => "holiday1", # Not verified
|
||||
"40" => "holiday2", # Not verified
|
||||
"41" => "desired-temp",
|
||||
"XX" => "measured-temp", # sum of next. two, never really sent
|
||||
"42" => "measured-low",
|
||||
"43" => "measured-high",
|
||||
"44" => "warnings",
|
||||
"45" => "manu-temp", # No clue what it does.
|
||||
|
||||
"4b" => "ack",
|
||||
"53" => "can-xmit",
|
||||
"54" => "can-rcv",
|
||||
|
||||
"60" => "year",
|
||||
"61" => "month",
|
||||
"62" => "day",
|
||||
"63" => "hour",
|
||||
"64" => "minute",
|
||||
"65" => "report1",
|
||||
"66" => "report2",
|
||||
"69" => "ack2",
|
||||
|
||||
"7d" => "start-xmit",
|
||||
"7e" => "end-xmit",
|
||||
|
||||
"82" => "day-temp",
|
||||
"84" => "night-temp",
|
||||
"85" => "lowtemp-offset", # Alarm-Temp.-Differenz
|
||||
"8a" => "windowopen-temp",
|
||||
);
|
||||
|
||||
my %cantset = (
|
||||
"ack" => 1,
|
||||
"ack2" => 1,
|
||||
"can-xmit" => 1,
|
||||
"can-rcv" => 1,
|
||||
"start-xmit" => 1,
|
||||
"end-xmit" => 1,
|
||||
|
||||
"measured-temp" => 1,
|
||||
"measured-high" => 1,
|
||||
"measured-low" => 1,
|
||||
"warnings" => 1,
|
||||
);
|
||||
|
||||
|
||||
my %priority = (
|
||||
"desired-temp"=> 1,
|
||||
"mode" => 2,
|
||||
"report1" => 3,
|
||||
"report2" => 3,
|
||||
"holiday1" => 4,
|
||||
"holiday2" => 5,
|
||||
"day-temp" => 6,
|
||||
"night-temp" => 7,
|
||||
);
|
||||
|
||||
my %c2m = (0 => "auto", 1 => "manual", 2 => "holiday", 3 => "holiday_short");
|
||||
my %m2c; # Reverse c2m
|
||||
my %c2b; # command->button hash (reverse of codes)
|
||||
my %c2bset; # command->button hash (settable values)
|
||||
my %defptr;
|
||||
|
||||
my $defmin = 0; # min fhtbuf free bytes before sending commands
|
||||
my $retryafter = 240; # in seconds, only when fhtsoftbuffer is active
|
||||
my $cmdcount = 0;
|
||||
|
||||
#####################################
|
||||
sub
|
||||
FHT_Initialize($)
|
||||
{
|
||||
my ($hash) = @_;
|
||||
|
||||
foreach my $k (keys %codes) {
|
||||
my $v = $codes{$k};
|
||||
$c2b{$v} = $k;
|
||||
$c2bset{$v} = $k if(!$cantset{$v});
|
||||
}
|
||||
foreach my $k (keys %c2m) {
|
||||
$m2c{$c2m{$k}} = $k;
|
||||
}
|
||||
|
||||
# 810c0426 0909a001 1111 1600
|
||||
# 810c04b3 0909a001 1111 44006900
|
||||
# 810b0402 83098301 1111 41301d
|
||||
# 81090421 c409c401 1111 00
|
||||
# 810c0d20 0909a001 3232 7e006724 (NYI)
|
||||
$hash->{Match} = "^81..(04|09|0d)..(0909a001|83098301|c409c401)..";
|
||||
$hash->{SetFn} = "FHT_Set";
|
||||
$hash->{StateFn} = "FHT_SetState";
|
||||
$hash->{DefFn} = "FHT_Define";
|
||||
$hash->{UndefFn} = "FHT_Undef";
|
||||
$hash->{ParseFn} = "FHT_Parse";
|
||||
$hash->{AttrList} = "IODev do_not_notify:0,1 model;fht80b dummy:0,1 " .
|
||||
"showtime:0,1 loglevel:0,1,2,3,4,5,6 retrycount minfhtbuffer ".
|
||||
"lazy tmpcorr";
|
||||
}
|
||||
|
||||
|
||||
sub
|
||||
FHT_Set($@)
|
||||
{
|
||||
my ($hash, @a) = @_;
|
||||
my $ret = "";
|
||||
|
||||
return "\"set $a[0]\" needs at least two parameters" if(@a < 2);
|
||||
|
||||
my $name = shift(@a);
|
||||
|
||||
# Replace refreshvalues with report1 and report2, and time with hour/minute
|
||||
for(my $i = 0; $i < @a; $i++) {
|
||||
splice(@a,$i,1,("report1","255","report2","255"))
|
||||
if($a[$i] eq "refreshvalues");
|
||||
|
||||
if($a[$i] eq "time") {
|
||||
my @t = localtime;
|
||||
splice(@a,$i,1,("hour",$t[2],"minute",$t[1]));
|
||||
IOWrite($hash, "", sprintf("T04%x", $t[0])) # CUL hack
|
||||
if($hash->{IODev} && $hash->{IODev}->{TYPE} eq "CUL");
|
||||
}
|
||||
}
|
||||
|
||||
my $ncmd = 0;
|
||||
my $arg = "020183" . $hash->{CODE};
|
||||
my ($cmd, $allcmd, $val) = ("", "", "");
|
||||
|
||||
my $lazy= defined($attr{$name}) &&
|
||||
defined($attr{$name}{"lazy"}) &&
|
||||
($attr{$name}{"lazy"}>0);
|
||||
my $readings= $hash->{READINGS};
|
||||
|
||||
|
||||
while(@a) {
|
||||
$cmd = shift(@a);
|
||||
|
||||
return "Unknown argument $cmd, choose one of " . join(" ",sort keys %c2bset)
|
||||
if(!defined($c2b{$cmd}));
|
||||
return "Readonly parameter $cmd"
|
||||
if(defined($cantset{$cmd}));
|
||||
return "\"set $name $cmd\" needs a parameter"
|
||||
if(@a < 1);
|
||||
|
||||
$val = shift(@a);
|
||||
$arg .= $c2b{$cmd};
|
||||
|
||||
if ($cmd =~ m/-temp/) {
|
||||
|
||||
return "Invalid temperature, use NN.N" if($val !~ m/^\d*\.?\d+$/);
|
||||
return "Invalid temperature, must between 5.5 and 30.5"
|
||||
if($val < 5.5 || $val > 30.5);
|
||||
my $a = int($val*2);
|
||||
$arg .= sprintf("%02x", $a);
|
||||
$ret .= sprintf("Rounded temperature to %.1f", $a/2) if($a/2 != $val);
|
||||
$val = sprintf("%.1f", $a/2);
|
||||
|
||||
} elsif($cmd =~ m/-from/ || $cmd =~ m/-to/) {
|
||||
|
||||
return "Invalid timeformat, use HH:MM"
|
||||
if($val !~ m/^([0-2]\d):([0-5]\d)/);
|
||||
my $a = ($1*6) + ($2/10);
|
||||
$arg .= sprintf("%02x", $a);
|
||||
my $nt = sprintf("%02d:%02d", $1, int($2/10)*10);
|
||||
$ret .= "Rounded $cmd to $nt" if($nt ne $val);
|
||||
$val = $nt;
|
||||
|
||||
} elsif($cmd eq "mode") {
|
||||
|
||||
return "Invalid mode, use one of " . join(" ", sort keys %m2c)
|
||||
if(!defined($m2c{$val}));
|
||||
$arg .= sprintf("%02x", $m2c{$val});
|
||||
|
||||
} elsif ($cmd eq "lowtemp-offset") {
|
||||
|
||||
return "Invalid lowtemperature-offset, must between 1 and 5"
|
||||
if($val !~ m/^[1-5]$/);
|
||||
$arg .= sprintf("%02x", $val);
|
||||
$val = "$val.0";
|
||||
|
||||
} else { # Holiday1, Holiday2
|
||||
|
||||
return "Invalid argument, must be between 1 and 255"
|
||||
if($val !~ m/^\d+$/ || $val < 0 || $val > 255);
|
||||
$arg .= sprintf("%02x", $val) if(defined($val));
|
||||
|
||||
}
|
||||
|
||||
|
||||
if($lazy &&
|
||||
$cmd ne "report1" && $cmd ne "report2" && $cmd ne "refreshvalues" &&
|
||||
defined($readings->{$cmd}) && $readings->{$cmd}{VAL} eq $val) {
|
||||
$ret .= "Lazy mode ignores $cmd";
|
||||
Log GetLogLevel($name,2), "Lazy mode ignores $cmd $val";
|
||||
|
||||
} else {
|
||||
$ncmd++;
|
||||
$allcmd .=" " if($allcmd);
|
||||
$allcmd .= $cmd;
|
||||
$allcmd .= " $val" if($val);
|
||||
}
|
||||
}
|
||||
|
||||
return "Too many commands specified, an FHT only supports up to 8"
|
||||
if($ncmd > 8);
|
||||
|
||||
return $ret if(!$ncmd);
|
||||
|
||||
my $ioname = "";
|
||||
$ioname = $hash->{IODev}->{NAME} if($hash->{IODev});
|
||||
if($attr{$ioname} && $attr{$ioname}{fhtsoftbuffer}) {
|
||||
my $io = $hash->{IODev};
|
||||
my %h = (HASH => $hash, CMD => $allcmd, ARG => $arg);
|
||||
|
||||
my $prio = $priority{$cmd};
|
||||
$prio = "9" if(!$prio);
|
||||
my $key = $prio . ":" . gettimeofday() . ":" . $cmdcount++;
|
||||
|
||||
$io->{SOFTBUFFER}{$key} = \%h;
|
||||
doSoftBuffer($io);
|
||||
|
||||
} else {
|
||||
|
||||
IOWrite($hash, "04", $arg) if(!IsDummy($name));
|
||||
Log GetLogLevel($name,2), "FHT set $name $allcmd";
|
||||
|
||||
}
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
|
||||
#####################################
|
||||
sub
|
||||
FHT_SetState($$$$)
|
||||
{
|
||||
my ($hash, $tim, $vt, $val) = @_;
|
||||
|
||||
return "Ignoring FHZ state" if($vt =~ m/^FHZ:/);
|
||||
$vt =~ s/^FHZ://;
|
||||
return "Undefined type $vt" if(!defined($c2b{$vt}));
|
||||
return undef;
|
||||
}
|
||||
|
||||
|
||||
#####################################
|
||||
sub
|
||||
FHT_Define($$)
|
||||
{
|
||||
my ($hash, $def) = @_;
|
||||
my @a = split("[ \t][ \t]*", $def);
|
||||
|
||||
return "wrong syntax: define <name> FHT CODE" if(int(@a) != 3);
|
||||
$a[2] = lc($a[2]);
|
||||
return "Define $a[0]: wrong CODE format: specify a 4 digit hex value"
|
||||
if($a[2] !~ m/^[a-f0-9][a-f0-9][a-f0-9][a-f0-9]$/i);
|
||||
|
||||
|
||||
$hash->{CODE} = $a[2];
|
||||
$hash->{CODE} = $a[2];
|
||||
$defptr{$a[2]} = $hash;
|
||||
$attr{$a[0]}{retrycount} = 3;
|
||||
|
||||
AssignIoPort($hash);
|
||||
|
||||
#Log GetLogLevel($a[0],2),"Asking the FHT device $a[0]/$a[2] to send its data";
|
||||
#FHT_Set($hash, ($a[0], "report1", "255", "report2", "255"));
|
||||
|
||||
return undef;
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub
|
||||
FHT_Undef($$)
|
||||
{
|
||||
my ($hash, $name) = @_;
|
||||
delete($defptr{$hash->{CODE}}) if($hash && $hash->{CODE});
|
||||
return undef;
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub
|
||||
FHT_Parse($$)
|
||||
{
|
||||
my ($hash, $msg) = @_;
|
||||
|
||||
$msg = lc($msg);
|
||||
my $dev = substr($msg, 16, 4);
|
||||
my $cde = substr($msg, 20, 2);
|
||||
my $val = substr($msg, 26, 2) if(length($msg) > 26);
|
||||
my $confirm = 0;
|
||||
|
||||
if(!defined($defptr{$dev})) {
|
||||
Log 3, "FHT Unknown device $dev, please define it";
|
||||
return "UNDEFINED FHT $dev";
|
||||
}
|
||||
|
||||
my $def = $defptr{$dev};
|
||||
my $name = $def->{NAME};
|
||||
|
||||
return "" if($def->{IODev} && $def->{IODev}{NAME} ne $hash->{NAME});
|
||||
|
||||
# Short message
|
||||
if(length($msg) < 26) {
|
||||
Log 4,"FHT Short message. Device $name, Message: $msg";
|
||||
return "";
|
||||
}
|
||||
|
||||
if(!$val || $cde eq "65" || $cde eq "66") {
|
||||
# This is a confirmation message. We reformat it so that
|
||||
# it looks like a real message, and let the rest parse it
|
||||
Log 4, "FHT $name confirmation: $cde";
|
||||
$val = substr($msg, 22, 2);
|
||||
$confirm = 1;
|
||||
}
|
||||
|
||||
$val = hex($val);
|
||||
|
||||
my $cmd = $codes{$cde};
|
||||
if(!$cmd) {
|
||||
Log 4, "FHT $name (Unknown: $cde => $val)";
|
||||
$def->{CHANGED}[0] = "unknown_$cde: $val";
|
||||
return $name;
|
||||
}
|
||||
|
||||
my $tn = TimeNow();
|
||||
|
||||
###########################
|
||||
# Reformat the values so they are readable.
|
||||
# The first four are confirmation messages, so they must be converted to
|
||||
# the same format as the input (for the softbuffer)
|
||||
|
||||
if($cmd =~ m/-from/ || $cmd =~ m/-to/) {
|
||||
$val = sprintf("%02d:%02d", $val/6, ($val%6)*10);
|
||||
|
||||
} elsif($cmd eq "mode") {
|
||||
$val = $c2m{$val} if(defined($c2m{$val}));
|
||||
|
||||
} elsif($cmd =~ m/.*-temp/) {
|
||||
$val = sprintf("%.1f", $val / 2)
|
||||
|
||||
} elsif($cmd eq "lowtemp-offset") {
|
||||
$val = sprintf("%d.0", $val)
|
||||
|
||||
} elsif($cmd =~ m/^actuator/) {
|
||||
|
||||
my $sval = substr($msg,24,2);
|
||||
my $fv = sprintf("%d%%", int(100*$val/255+0.5));
|
||||
|
||||
if($sval =~ m/[ab]0/i) { $val = $fv; } # sync in the summer
|
||||
elsif($sval =~ m/.0/) { $val = "syncnow"; }
|
||||
elsif($sval =~ m/.1/) { $val = "99%" } # FHT set to 30.5, FHT80B=="ON"
|
||||
elsif($sval =~ m/.2/) { $val = "0%" } # FHT set to 5.5
|
||||
elsif($sval =~ m/.6/) { $val = "$fv" }
|
||||
elsif($sval =~ m/.8/) { $val = "offset: $fv" }
|
||||
elsif($sval =~ m/.a/) { $val = "lime-protection" }
|
||||
elsif($sval =~ m/.c/) { $val = sprintf("synctime: %d", int($val>>1)); }
|
||||
elsif($sval =~ m/.e/) { $val = "test" }
|
||||
elsif($sval =~ m/.f/) { $val = "pair" }
|
||||
|
||||
else { $val = "unknown_$sval: $fv" }
|
||||
|
||||
} elsif($cmd eq "measured-low") {
|
||||
$def->{READINGS}{$cmd}{TIME} = $tn;
|
||||
$def->{READINGS}{$cmd}{VAL} = $val;
|
||||
return "";
|
||||
|
||||
} elsif($cmd eq "measured-high") {
|
||||
$def->{READINGS}{$cmd}{TIME} = $tn;
|
||||
$def->{READINGS}{$cmd}{VAL} = $val;
|
||||
|
||||
if(defined($def->{READINGS}{"measured-low"}{VAL})) {
|
||||
|
||||
my $off = ($attr{$name} && $attr{$name}{tmpcorr}) ?
|
||||
$attr{$name}{tmpcorr} : 0;
|
||||
$val = $val*256 + $def->{READINGS}{"measured-low"}{VAL};
|
||||
$val /= 10;
|
||||
$val = sprintf("%.1f (Celsius)", $val+$off);
|
||||
$cmd = "measured-temp"
|
||||
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
|
||||
} elsif($cmd eq "warnings") {
|
||||
my $nVal;
|
||||
if($val & 1) { $nVal = "Battery low"; }
|
||||
if($val & 2) { $nVal .= "; " if($nVal); $nVal .= "Temperature too low"; }
|
||||
if($val &32) { $nVal .= "; " if($nVal); $nVal .= "Window open"; }
|
||||
if($val &16) { $nVal .= "; " if($nVal); $nVal .= "Fault on window sensor"; }
|
||||
$val = $nVal? $nVal : "none";
|
||||
|
||||
}
|
||||
|
||||
if(substr($msg,24,1) eq "7") {
|
||||
$cmd = "FHZ:$cmd";
|
||||
} else {
|
||||
$def->{READINGS}{$cmd}{TIME} = $tn;
|
||||
$def->{READINGS}{$cmd}{VAL} = $val;
|
||||
$def->{STATE} = "$cmd: $val" if($cmd eq "measured-temp");
|
||||
}
|
||||
$def->{CHANGED}[0] = "$cmd: $val";
|
||||
|
||||
Log 4, "FHT $name $cmd: $val";
|
||||
|
||||
################################
|
||||
# Softbuffer: delete confirmed commands
|
||||
if($confirm) {
|
||||
my $found;
|
||||
my $io = $def->{IODev};
|
||||
foreach my $key (sort keys %{$io->{SOFTBUFFER}}) {
|
||||
my $h = $io->{SOFTBUFFER}{$key};
|
||||
my $hcmd = $h->{CMD};
|
||||
my $hname = $h->{HASH}->{NAME};
|
||||
Log 4, "FHT softbuffer check: $hname / $hcmd";
|
||||
if($hname eq $name && $hcmd =~ m/^$cmd $val/) {
|
||||
$found = $key;
|
||||
Log 4, "FHT softbuffer found";
|
||||
last;
|
||||
}
|
||||
}
|
||||
delete($io->{SOFTBUFFER}{$found}) if($found);
|
||||
}
|
||||
|
||||
return $name;
|
||||
}
|
||||
|
||||
|
||||
# Check the softwarebuffer and send/resend commands
|
||||
sub
|
||||
doSoftBuffer($)
|
||||
{
|
||||
my ($io) = @_;
|
||||
|
||||
my $now = gettimeofday();
|
||||
|
||||
my $count = 0;
|
||||
my $fhzbuflen = -999;
|
||||
foreach my $key (keys %{ $io->{SOFTBUFFER} }) {
|
||||
|
||||
$count++;
|
||||
my $h = $io->{SOFTBUFFER}{$key};
|
||||
my $name = $h->{HASH}->{NAME};
|
||||
|
||||
if($h->{NSENT}) {
|
||||
next if($now-$h->{SENDTIME} < $retryafter);
|
||||
my $retry = $attr{$name}{retrycount};
|
||||
if($h->{NSENT} > $retry) {
|
||||
Log GetLogLevel($name,2), "$name set $h->{CMD}: ".
|
||||
"no confirmation after $h->{NSENT} tries, giving up";
|
||||
delete($io->{SOFTBUFFER}{$key});
|
||||
next;
|
||||
}
|
||||
}
|
||||
|
||||
$fhzbuflen = getFhtBuffer($io) if($fhzbuflen == -999);
|
||||
my $arglen = length($h->{ARG})/2 - 2; # Length in bytes
|
||||
|
||||
next if($fhzbuflen < $arglen || $fhzbuflen < getFhtMin($io));
|
||||
IOWrite($h->{HASH}, "04", $h->{ARG}) if(!IsDummy($name));
|
||||
Log GetLogLevel($name,2), "FHT set $name $h->{CMD}";
|
||||
|
||||
$fhzbuflen -= $arglen;
|
||||
$h->{SENDTIME} = $now;
|
||||
$h->{NSENT}++;
|
||||
|
||||
}
|
||||
|
||||
if($count && !$io->{SOFTBUFFERTIMER}) {
|
||||
$io->{SOFTBUFFERTIMER} = 1;
|
||||
InternalTimer(gettimeofday()+30, "softBufferTimer", $io, 0);
|
||||
}
|
||||
}
|
||||
|
||||
#####################################
|
||||
# Wrapper for the InternalTimer
|
||||
sub
|
||||
softBufferTimer($)
|
||||
{
|
||||
my ($io) = @_;
|
||||
delete($io->{SOFTBUFFERTIMER});
|
||||
doSoftBuffer($io);
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub
|
||||
getFhtMin($)
|
||||
{
|
||||
my ($io) = @_;
|
||||
my $ioname = $io->{NAME};
|
||||
return $attr{$ioname}{minfhtbuffer}
|
||||
if($attr{$ioname} && $attr{$ioname}{minfhtbuffer});
|
||||
return $defmin;
|
||||
}
|
||||
|
||||
#####################################
|
||||
# get the FHZ hardwarebuffer without logentry as decimal value
|
||||
sub
|
||||
getFhtBuffer($)
|
||||
{
|
||||
my ($io) = @_;
|
||||
my $count = 0;
|
||||
|
||||
return getFhtMin($io) if(IsDummy($io->{NAME}));
|
||||
|
||||
for(;;) {
|
||||
FHZ_Write($io, "04", "c90185");
|
||||
my $msg = FHZ_ReadAnswer($io, "fhtbuf", 1.0);
|
||||
if(!defined($msg)) { $msg= ""; }
|
||||
Log 5, "getFhtBuffer: $count $msg";
|
||||
|
||||
return hex(substr($msg, 16, 2)) if($msg && $msg =~ m/^[0-9A-F]+$/i);
|
||||
return 0 if($count++ >= 5);
|
||||
}
|
||||
}
|
||||
|
||||
1;
|
|
@ -1,231 +0,0 @@
|
|||
##############################################
|
||||
package main;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
my %codes = (
|
||||
"0" => "HMS100TF",
|
||||
"1" => "HMS100T",
|
||||
"2" => "HMS100WD",
|
||||
"3" => "RM100-2",
|
||||
"4" => "HMS100TFK", # Depending on the onboard jumper it is 4 or 5
|
||||
"5" => "HMS100TFK",
|
||||
"6" => "HMS100MG",
|
||||
"8" => "HMS100CO",
|
||||
"e" => "HMS100FIT",
|
||||
);
|
||||
|
||||
my %defptr;
|
||||
|
||||
|
||||
#####################################
|
||||
sub
|
||||
HMS_Initialize($)
|
||||
{
|
||||
my ($hash) = @_;
|
||||
|
||||
# 810e047e0510a001473a000000120233 HMS100TF
|
||||
# 810e04b90511a0018e63000001100000 HMS100T
|
||||
# 810e04e80212a001ec46000001000000 HMS100WD
|
||||
# 810e04d70213a001b16d000003000000 RM100-2
|
||||
# 810e047f0214a001a81f000001000000 HMS100TFK
|
||||
# 810e048f0295a0010155000001000000 HMS100TFK (jumper)
|
||||
# 810e04330216a001b4c5000001000000 HMS100MG
|
||||
# 810e04210218a00186e0000000000000 HMS100CO
|
||||
# 810e0448029ea00132d5000000000000 FI-Trenner
|
||||
|
||||
$hash->{Match} = "^810e04....(1|5|9).a001";
|
||||
$hash->{DefFn} = "HMS_Define";
|
||||
$hash->{UndefFn} = "HMS_Undef";
|
||||
$hash->{ParseFn} = "HMS_Parse";
|
||||
$hash->{AttrList} = "IODev do_not_notify:0,1 showtime:0,1 model;hms100-t,hms100-tf,hms100-wd,hms100-mg,hms100-tfk,rm100-2,hms100-co,hms100-fit loglevel:0,1,2,3,4,5,6";
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub
|
||||
HMS_Define($$)
|
||||
{
|
||||
my ($hash, $def) = @_;
|
||||
my @a = split("[ \t][ \t]*", $def);
|
||||
|
||||
return "wrong syntax: define <name> HMS CODE" if(int(@a) != 3);
|
||||
$a[2] = lc($a[2]);
|
||||
return "Define $a[0]: wrong CODE format: specify a 4 digit hex value"
|
||||
if($a[2] !~ m/^[a-f0-9][a-f0-9][a-f0-9][a-f0-9]$/);
|
||||
|
||||
|
||||
$hash->{CODE} = $a[2];
|
||||
$defptr{$a[2]} = $hash;
|
||||
AssignIoPort($hash);
|
||||
return undef;
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub
|
||||
HMS_Undef($$)
|
||||
{
|
||||
my ($hash, $name) = @_;
|
||||
delete($defptr{$hash->{CODE}})
|
||||
if(defined($hash->{CODE}) && defined($defptr{$hash->{CODE}}));
|
||||
return undef;
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub
|
||||
HMS_Parse($$)
|
||||
{
|
||||
my ($hash, $msg) = @_;
|
||||
|
||||
my $dev = substr($msg, 16, 4);
|
||||
my $cde = substr($msg, 11, 1);
|
||||
# 012345678901234567890123456789
|
||||
# 810e047f0214a001a81f000001000000 HMS100TFK
|
||||
my $val = substr($msg, 24, 8) if(length($msg) == 32);
|
||||
|
||||
my $type = "";
|
||||
foreach my $c (keys %codes) {
|
||||
if($cde =~ m/$c/) {
|
||||
$type = $codes{$c};
|
||||
last;
|
||||
}
|
||||
}
|
||||
|
||||
# As the HMS devices change their id on each battery change, we offer
|
||||
# a wildcard too for each type: 100<device-code>,
|
||||
my $odev = $dev;
|
||||
if(!defined($defptr{$dev})) {
|
||||
Log 4, "HMS device $dev not defined, using the wildcard device 100$cde";
|
||||
$dev = "100$cde";
|
||||
}
|
||||
|
||||
if(!defined($defptr{$dev})) {
|
||||
Log 3, "Unknown HMS device $dev/$odev, please define it";
|
||||
$type = "HMS" if(!$type);
|
||||
return "UNDEFINED $type $odev";
|
||||
}
|
||||
|
||||
my $def = $defptr{$dev};
|
||||
return "" if($def->{IODev} && $def->{IODev}{NAME} ne $hash->{NAME});
|
||||
|
||||
my (@v, @txt);
|
||||
|
||||
# Used for HMS100TF & HMS100T
|
||||
my $batstr1 = "ok";
|
||||
my $status1 = hex(substr($val, 0, 1));
|
||||
$batstr1 = "empty" if( $status1 & 2 );
|
||||
$batstr1 = "replaced" if( $status1 & 4 );
|
||||
|
||||
# Used for the other devices
|
||||
my $batstr2 = "ok";
|
||||
my $status = hex(substr($val, 1, 1));
|
||||
my $status2 = hex(substr($msg, 10, 1));
|
||||
$batstr2 = "empty" if( $status2 & 4 );
|
||||
$batstr2 = "replaced" if( $status2 & 8 );
|
||||
|
||||
if($type eq "HMS100TF") {
|
||||
|
||||
@txt = ( "temperature", "humidity", "battery");
|
||||
|
||||
# Codierung <s1><s0><t1><t0><f0><t2><f2><f1>
|
||||
$v[0] = int(substr($val, 5, 1) . substr($val, 2, 2))/10;
|
||||
$v[0] = -$v[0] if($status1 & 8);
|
||||
$v[1] = int(substr($val, 6, 2) . substr($val, 4, 1))/10;
|
||||
$v[2] = $batstr1;
|
||||
|
||||
$val = "T: $v[0] H: $v[1] Bat: $v[2]";
|
||||
$v[0] = "$v[0] (Celsius)";
|
||||
$v[1] = "$v[1] (%)";
|
||||
|
||||
} elsif ($type eq "HMS100T") {
|
||||
|
||||
@txt = ( "temperature", "battery");
|
||||
|
||||
$v[0] = int(substr($val, 5, 1) . substr($val, 2, 2))/10;
|
||||
$v[0] = -$v[0] if($status1 & 8);
|
||||
$v[1] = $batstr1;
|
||||
|
||||
$val = "T: $v[0] Bat: $v[1]";
|
||||
$v[0] = "$v[0] (Celsius)";
|
||||
|
||||
} elsif ($type eq "HMS100WD") {
|
||||
|
||||
@txt = ( "water_detect", "battery");
|
||||
|
||||
$v[0] = ($status ? "on" : "off");
|
||||
$v[1] = $batstr2;
|
||||
|
||||
$val = "Water Detect: $v[0]";
|
||||
|
||||
} elsif ($type eq "HMS100TFK") { # By Peter P.
|
||||
|
||||
@txt = ( "switch_detect", "battery");
|
||||
|
||||
$v[0] = ($status ? "on" : "off");
|
||||
$v[1] = $batstr2;
|
||||
|
||||
$val = "Switch Detect: $v[0]";
|
||||
|
||||
} elsif($type eq "RM100-2") {
|
||||
|
||||
@txt = ( "smoke_detect", "battery");
|
||||
|
||||
$v[0] = ($status ? "on" : "off");
|
||||
$v[1] = $batstr2;
|
||||
|
||||
$val = "smoke_detect: $v[0]";
|
||||
|
||||
} elsif ($type eq "HMS100MG") { # By Peter Stark
|
||||
|
||||
@txt = ( "gas_detect", "battery");
|
||||
|
||||
$v[0] = ($status ? "on" : "off");
|
||||
$v[1] = $batstr2; # Battery conditions not yet verified
|
||||
|
||||
$val = "Gas Detect: $v[0]";
|
||||
|
||||
} elsif ($type eq "HMS100CO") { # By PAN
|
||||
|
||||
@txt = ( "gas_detect", "battery");
|
||||
|
||||
$v[0] = ($status ? "on" : "off");
|
||||
$v[1] = $batstr2; # Battery conditions not yet verified
|
||||
|
||||
$val = "CO Detect: $v[0]";
|
||||
|
||||
} elsif ($type eq "HMS100FIT") { # By PAN
|
||||
|
||||
@txt = ( "fi_triggered", "battery");
|
||||
|
||||
$v[0] = ($status ? "on" : "off");
|
||||
$v[1] = $batstr2; # Battery conditions not yet verified
|
||||
|
||||
$val = "FI triggered: $v[0]";
|
||||
|
||||
} else {
|
||||
|
||||
Log 3, "HMS Device $dev (Unknown type: $type)";
|
||||
return "";
|
||||
|
||||
}
|
||||
|
||||
my $now = TimeNow();
|
||||
Log GetLogLevel($def->{NAME},4), "HMS Device $dev ($type: $val)";
|
||||
|
||||
my $max = int(@txt);
|
||||
for( my $i = 0; $i < $max; $i++) {
|
||||
$def->{READINGS}{$txt[$i]}{TIME} = $now;
|
||||
$def->{READINGS}{$txt[$i]}{VAL} = $v[$i];
|
||||
$def->{CHANGED}[$i] = "$txt[$i]: $v[$i]";
|
||||
}
|
||||
$def->{READINGS}{type}{TIME} = $now;
|
||||
$def->{READINGS}{type}{VAL} = $type;
|
||||
|
||||
$def->{STATE} = $val;
|
||||
$def->{CHANGED}[$max++] = $val;
|
||||
$def->{CHANGED}[$max++] = "ExactId: $odev" if($odev ne $dev);
|
||||
|
||||
return $def->{NAME};
|
||||
}
|
||||
|
||||
1;
|
|
@ -1,332 +0,0 @@
|
|||
##############################################
|
||||
package main;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
my %defptr;
|
||||
|
||||
#####################################
|
||||
sub
|
||||
KS300_Initialize($)
|
||||
{
|
||||
my ($hash) = @_;
|
||||
|
||||
# Message is like
|
||||
# 810d04f94027a00171212730000008
|
||||
# 81 0d 04 f9 4027a00171 212730000008
|
||||
|
||||
$hash->{Match} = "^810d04..4027a001";
|
||||
$hash->{DefFn} = "KS300_Define";
|
||||
$hash->{UndefFn} = "KS300_Undef";
|
||||
$hash->{ParseFn} = "KS300_Parse";
|
||||
$hash->{AttrList} = "IODev do_not_notify:0,1 showtime:0,1 model:ks300 loglevel:0,1 rainadjustment:0,1";
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub
|
||||
KS300_Define($$)
|
||||
{
|
||||
my ($hash, $def) = @_;
|
||||
my @a = split("[ \t][ \t]*", $def);
|
||||
|
||||
return "wrong syntax: define <name> KS300 <code> " .
|
||||
"[ml/raincounter] [wind-factor]" if(int(@a) < 3 || int(@a) > 5);
|
||||
$a[2] = lc($a[2]);
|
||||
return "Define $a[0]: wrong CODE format: specify a 4 digit hex value"
|
||||
if($a[2] !~ m/^[a-f0-9][a-f0-9][a-f0-9][a-f0-9]$/);
|
||||
|
||||
$hash->{CODE} = $a[2];
|
||||
my $rainunit = ((int(@a) > 3) ? $a[3] : 255);
|
||||
my $windunit = ((int(@a) > 4) ? $a[4] : 1.0);
|
||||
$hash->{CODE} = $a[2];
|
||||
$hash->{RAINUNIT} = $rainunit;
|
||||
$hash->{WINDUNIT} = $windunit;
|
||||
$defptr{$a[2]} = $hash;
|
||||
AssignIoPort($hash);
|
||||
|
||||
return undef;
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub
|
||||
KS300_Undef($$)
|
||||
{
|
||||
my ($hash, $name) = @_;
|
||||
delete($defptr{$hash->{CODE}});
|
||||
return undef;
|
||||
}
|
||||
|
||||
|
||||
#####################################
|
||||
sub
|
||||
KS300_Parse($$)
|
||||
{
|
||||
my ($hash,$msg) = @_;
|
||||
|
||||
###############################
|
||||
# 1 2
|
||||
#0123456789012345 67890123456789
|
||||
#
|
||||
#810d04f94027a001 71212730000008
|
||||
###############################
|
||||
my @a = split("", $msg);
|
||||
|
||||
##########################
|
||||
# I've seldom (1 out of 700) seen messages of length 10 and 11 with correct
|
||||
# CRC, they seem to contain partial data (e.g. temp/wind/hum but not rain)
|
||||
# They are suppressed as of now.
|
||||
if(hex($a[3]) != 13) {
|
||||
Log 4, "Strange KS300 message received, won't decode ($msg)";
|
||||
return "";
|
||||
}
|
||||
|
||||
if(int(keys %defptr)) {
|
||||
|
||||
my @arr = keys(%defptr); # No code is known yet
|
||||
my $dev = shift(@arr);
|
||||
my $def = $defptr{$dev};
|
||||
my $haverain = 0;
|
||||
my $name= $def->{NAME};
|
||||
|
||||
return "" if($def->{IODev} && $def->{IODev}{NAME} ne $hash->{NAME});
|
||||
|
||||
my @v;
|
||||
my @txt = ( "rain_raw", "rain", "wind", "humidity", "temperature",
|
||||
"israining", "unknown1", "unknown2", "unknown3");
|
||||
my @sfx = ( "(counter)", "(l/m2)", "(km/h)", "(%)", "(Celsius)",
|
||||
"(yes/no)", "","","");
|
||||
my %repchanged = ("rain"=>1, "wind"=>1, "humidity"=>1, "temperature"=>1,
|
||||
"israining"=>1);
|
||||
|
||||
# counter for the change hash
|
||||
my $n= 1; # 0 is STATE and will b explicitely set
|
||||
|
||||
# time
|
||||
my $tm = TimeNow();
|
||||
my $tsecs= time(); # number of non-leap seconds since January 1, 1970, UTC
|
||||
|
||||
# The next instr wont work for empty hashes, so we init it now
|
||||
$def->{READINGS}{$txt[0]}{VAL} = 0 if(!$def->{READINGS});
|
||||
my $r = $def->{READINGS};
|
||||
|
||||
|
||||
# preset current $rain_raw
|
||||
$v[0] = hex("$a[28]$a[27]$a[26]");
|
||||
my $rain_raw= $v[0];
|
||||
|
||||
# get previous rain_raw
|
||||
my $rain_raw_prev= $rain_raw;
|
||||
if(defined($r->{rain_raw})) {
|
||||
($rain_raw_prev, undef)= split(" ", $r->{rain_raw}{VAL}); # cut off "(counter)"
|
||||
};
|
||||
|
||||
# unadjusted value as default
|
||||
my $rain_raw_adj= $rain_raw;
|
||||
|
||||
# get previous rain_raw_adj
|
||||
my $rain_raw_adj_prev= $rain_raw;
|
||||
if(defined($r->{rain_raw_adj})) {
|
||||
$rain_raw_adj_prev= $r->{rain_raw_adj}{VAL};
|
||||
};
|
||||
|
||||
if(defined($attr{$name}) &&
|
||||
defined($attr{$name}{"rainadjustment"}) &&
|
||||
($attr{$name}{"rainadjustment"}>0)) {
|
||||
|
||||
# The rain values delivered by my KS300 randomly switch between two
|
||||
# different values. The offset between the two values follows no
|
||||
# identifiable principle. It is even unclear whether the problem is
|
||||
# caused by KS300 or by FHZ1300. ELV denies any problem with the KS300.
|
||||
# The problem is known to several people. For instance, see
|
||||
# http://www.ipsymcon.de/forum/showthread.php?t=3303&highlight=ks300+regen&page=3
|
||||
# The following code detects and automatically corrects these offsets.
|
||||
|
||||
my $rain_raw_ofs;
|
||||
my $rain_raw_ofs_prev;
|
||||
my $tsecs_prev;
|
||||
|
||||
# get previous offet
|
||||
if(defined($r->{rain_raw_ofs})) {
|
||||
$rain_raw_ofs_prev= $r->{rain_raw_ofs}{VAL};
|
||||
} else{
|
||||
$rain_raw_ofs_prev= 0;
|
||||
}
|
||||
|
||||
# the current offset is the same, but this may change later
|
||||
$rain_raw_ofs= $rain_raw_ofs_prev;
|
||||
|
||||
# get previous tsecs
|
||||
if(defined($r->{tsecs})) {
|
||||
$tsecs_prev= $r->{tsecs}{VAL};
|
||||
} else{
|
||||
$tsecs_prev= 0; # 1970-01-01
|
||||
}
|
||||
|
||||
# detect error condition
|
||||
# delta is negative or delta is too large
|
||||
# see http://de.wikipedia.org/wiki/Niederschlagsintensität#Niederschlagsintensit.C3.A4t
|
||||
# during a thunderstorm in middle europe, 50l/m^2 rain may fall per hour
|
||||
# 50l/(m^2*h) correspond to 200 ticks/h
|
||||
# Since KS300 sends every 2,5 minutes, a maximum delta of 8 ticks would
|
||||
# be reasonable. The observed deltas are in most cases 1 or 2 orders
|
||||
# of magnitude larger.
|
||||
# The code also handles counter resets after battery replacement
|
||||
|
||||
my $rain_raw_delta= $rain_raw- $rain_raw_prev;
|
||||
if($tsecs!= $tsecs_prev) { # avoids a rare but relevant condition
|
||||
my $thours_delta= ($tsecs- $tsecs_prev)/3600.0; # in hours
|
||||
my $rain_raw_per_hour= $rain_raw_delta/$thours_delta;
|
||||
if(($rain_raw_delta<0) || ($rain_raw_per_hour> 200.0)) {
|
||||
$rain_raw_ofs= $rain_raw_ofs_prev-$rain_raw_delta;
|
||||
# If the switch in the tick count occurs simultaneously with an
|
||||
# increase due to rain, the tick is lost. We therefore assume that
|
||||
# offsets between -5 and 0 are indeed rain.
|
||||
if(($rain_raw_ofs>=-5) && ($rain_raw_ofs<0)) { $rain_raw_ofs= 0; }
|
||||
$r->{rain_raw_ofs}{TIME} = $tm;
|
||||
$r->{rain_raw_ofs}{VAL} = $rain_raw_ofs;
|
||||
$def->{CHANGED}[$n++] = "rain_raw_ofs: $rain_raw_ofs";
|
||||
}
|
||||
}
|
||||
$rain_raw_adj= $rain_raw+ $rain_raw_ofs;
|
||||
|
||||
}
|
||||
|
||||
# remember tsecs
|
||||
$r->{tsecs}{TIME} = $tm;
|
||||
$r->{tsecs}{VAL} = "$tsecs";
|
||||
|
||||
# remember rain_raw_adj
|
||||
$r->{rain_raw_adj}{TIME} = $tm;
|
||||
$r->{rain_raw_adj}{VAL} = $rain_raw_adj;
|
||||
|
||||
|
||||
# KS300 has a sensor which detects any drop of rain and immediately
|
||||
# sends out the israining message. The sensors consists of two parallel
|
||||
# strips of metal separated by a small gap. The rain bridges the gap
|
||||
# and closes the contact. If the KS300 pole is not perfectly vertical the
|
||||
# drop runs along only one side and the contact is not closed. To get the
|
||||
# israining information anyway, the respective flag is also set when the
|
||||
# a positive amount of rain is detected.
|
||||
$haverain = 1 if($rain_raw_adj != $rain_raw_adj_prev);
|
||||
|
||||
$v[1] = sprintf("%0.1f", $rain_raw_adj * $def->{RAINUNIT} / 1000);
|
||||
$v[2] = sprintf("%0.1f", ("$a[25]$a[24].$a[23]"+0) * $def->{WINDUNIT});
|
||||
$v[3] = "$a[22]$a[21]" + 0;
|
||||
$v[4] = "$a[20]$a[19].$a[18]" + 0; $v[4] = "-$v[4]" if($a[17] eq "7");
|
||||
$v[4] = sprintf("%0.1f", $v[4]);
|
||||
|
||||
$v[5] = ((hex($a[17]) & 0x2) || $haverain) ? "yes" : "no";
|
||||
$v[6] = $a[29];
|
||||
$v[7] = $a[16];
|
||||
$v[8] = $a[17];
|
||||
|
||||
# Negative temp
|
||||
$v[4] = -$v[4] if($v[8] & 8);
|
||||
|
||||
Log GetLogLevel($def->{NAME},4), "KS300 $dev: $msg";
|
||||
|
||||
my $max = int(@v);
|
||||
|
||||
# For logging/summary
|
||||
my $val = "T: $v[4] H: $v[3] W: $v[2] R: $v[1] IR: $v[5]";
|
||||
$def->{STATE} = $val;
|
||||
$def->{CHANGED}[0] = $val;
|
||||
|
||||
for(my $i = 0; $i < $max; $i++) {
|
||||
$r->{$txt[$i]}{TIME} = $tm;
|
||||
$val = "$v[$i] $sfx[$i]";
|
||||
$r->{$txt[$i]}{VAL} = $val;
|
||||
$def->{CHANGED}[$n++] = "$txt[$i]: $val"
|
||||
if(defined($repchanged{$txt[$i]}));
|
||||
}
|
||||
|
||||
###################################
|
||||
# AVG computing
|
||||
if(!$r->{cum_day}) {
|
||||
|
||||
$r->{cum_day}{VAL} = "$tm T: 0 H: 0 W: 0 R: $v[1]";
|
||||
$r->{avg_day}{VAL} = "T: $v[4] H: $v[3] W: $v[2] R: $v[1]";
|
||||
|
||||
} else {
|
||||
|
||||
my @cv = split(" ", $r->{cum_day}{VAL});
|
||||
|
||||
my @cd = split("[ :-]", $r->{cum_day}{TIME});
|
||||
my $csec = 3600*$cd[3] + 60*$cd[4] + $cd[5]; # Sec of last reading
|
||||
|
||||
my @d = split("[ :-]", $tm);
|
||||
my $sec = 3600*$d[3] + 60*$d[4] + $d[5]; # Sec now
|
||||
|
||||
my @sd = split("[ :-]", "$cv[0] $cv[1]");
|
||||
my $ssec = 3600*$sd[3] + 60*$sd[4] + $sd[5]; # Sec at start of day
|
||||
|
||||
my $difft = $sec - $csec;
|
||||
$difft += 86400 if($d[2] != $cd[2]); # Sec since last reading
|
||||
|
||||
my $t = $cv[3] + $difft * $v[4];
|
||||
my $h = $cv[5] + $difft * $v[3];
|
||||
my $w = $cv[7] + $difft * $v[2];
|
||||
my $e = $cv[9];
|
||||
|
||||
$r->{cum_day}{VAL} = "$cv[0] $cv[1] T: $t H: $h W: $w R: $e";
|
||||
|
||||
$difft = $sec - $ssec;
|
||||
$difft += 86400 if($d[2] != $sd[2]); # Sec since last reading
|
||||
|
||||
$t /= $difft; $h /= $difft; $w /= $difft; $e = $v[1] - $cv[9];
|
||||
$r->{avg_day}{VAL} =
|
||||
sprintf("T: %.1f H: %d W: %.1f R: %.1f", $t, $h, $w, $e);
|
||||
|
||||
if($d[2] != $sd[2]) { # Day changed, report it
|
||||
|
||||
$def->{CHANGED}[$n++] = "avg_day $r->{avg_day}{VAL}";
|
||||
$r->{cum_day}{VAL} = "$tm T: 0 H: 0 W: 0 R: $v[1]";
|
||||
|
||||
if(!$r->{cum_month}) { # Check the month
|
||||
|
||||
$r->{cum_month}{VAL} = "1 $r->{avg_day}{VAL}";
|
||||
$r->{avg_month}{VAL} = $r->{avg_day}{VAL};
|
||||
|
||||
} else {
|
||||
|
||||
my @cmv = split(" ", $r->{cum_month}{VAL});
|
||||
$t += $cmv[2]; $w += $cmv[4]; $h += $cmv[6];
|
||||
|
||||
$cmv[0]++;
|
||||
$r->{cum_month}{VAL} =
|
||||
sprintf("%d T: %.1f H: %d W: %.1f R: %.1f",
|
||||
$cmv[0], $t, $h, $w, $cmv[8]+$e);
|
||||
$r->{avg_month}{VAL} =
|
||||
sprintf("T: %.1f H: %d W: %.1f R: %.1f",
|
||||
$t/$cmv[0], $h/$cmv[0], $w/$cmv[0], $cmv[8]+$e);
|
||||
|
||||
if($d[1] != $sd[1]) { # Month changed, report it
|
||||
|
||||
$def->{CHANGED}[$n++] = "avg_month $r->{avg_month}{VAL}";
|
||||
$r->{cum_month}{VAL} = "0 T: 0 H: 0 W: 0 R: 0";
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
$r->{cum_month}{TIME} = $r->{avg_month}{TIME} = $tm;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
$r->{cum_day}{TIME} = $r->{avg_day}{TIME} = $tm;
|
||||
# AVG computing
|
||||
###################################
|
||||
|
||||
return $name;
|
||||
|
||||
} else {
|
||||
|
||||
Log 4, "KS300 detected: $msg";
|
||||
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
1;
|
|
@ -1,270 +0,0 @@
|
|||
##############################################
|
||||
package main;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
my %defptr;
|
||||
|
||||
# Supports following devices:
|
||||
# KS300TH (this is redirected to the more sophisticated 14_KS300 by 00_CUL)
|
||||
# S300TH
|
||||
# WS2000/WS7000
|
||||
#
|
||||
|
||||
#####################################
|
||||
sub
|
||||
CUL_WS_Initialize($)
|
||||
{
|
||||
my ($hash) = @_;
|
||||
|
||||
# Message is like
|
||||
# K41350270
|
||||
|
||||
$hash->{Match} = "^K.....";
|
||||
$hash->{DefFn} = "CUL_WS_Define";
|
||||
$hash->{UndefFn} = "CUL_WS_Undef";
|
||||
$hash->{AttrFn} = "CUL_WS_Attr";
|
||||
$hash->{ParseFn} = "CUL_WS_Parse";
|
||||
$hash->{AttrList} = "IODev do_not_notify:0,1 showtime:0,1 model:S300TH,KS300 loglevel";
|
||||
}
|
||||
|
||||
|
||||
#####################################
|
||||
sub
|
||||
CUL_WS_Define($$)
|
||||
{
|
||||
my ($hash, $def) = @_;
|
||||
my @a = split("[ \t][ \t]*", $def);
|
||||
|
||||
return "wrong syntax: define <name> CUL_WS <code> [corr1...corr4]"
|
||||
if(int(@a) < 3 || int(@a) > 7);
|
||||
$a[2] = lc($a[2]);
|
||||
return "Define $a[0]: wrong CODE format: valid is 1-8"
|
||||
if($a[2] !~ m/^[1-8]$/);
|
||||
|
||||
$hash->{CODE} = $a[2];
|
||||
$hash->{corr1} = ((int(@a) > 3) ? $a[3] : 0);
|
||||
$hash->{corr2} = ((int(@a) > 4) ? $a[4] : 0);
|
||||
$hash->{corr3} = ((int(@a) > 5) ? $a[5] : 0);
|
||||
$hash->{corr4} = ((int(@a) > 6) ? $a[6] : 0);
|
||||
$defptr{$a[2]} = $hash;
|
||||
AssignIoPort($hash);
|
||||
return undef;
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub
|
||||
CUL_WS_Undef($$)
|
||||
{
|
||||
my ($hash, $name) = @_;
|
||||
delete($defptr{$hash->{CODE}}) if($hash && $hash->{CODE});
|
||||
return undef;
|
||||
}
|
||||
|
||||
|
||||
#####################################
|
||||
sub
|
||||
CUL_WS_Parse($$)
|
||||
{
|
||||
my ($hash,$msg) = @_;
|
||||
my %tlist = ("0"=>"temp",
|
||||
"1"=>"temp/hum",
|
||||
"2"=>"rain",
|
||||
"3"=>"wind",
|
||||
"4"=>"temp/hum/press",
|
||||
"5"=>"brightness",
|
||||
"6"=>"pyro",
|
||||
"7"=>"temp/hum");
|
||||
|
||||
|
||||
my @a = split("", $msg);
|
||||
|
||||
my $firstbyte = hex($a[1]);
|
||||
my $cde = ($firstbyte&7) + 1;
|
||||
my $type = $tlist{$a[2]} ? $tlist{$a[2]} : "unknown";
|
||||
|
||||
# There are only 8 S300 devices. In order to enable more, we try to look up
|
||||
# the name in connection with the receiver's name ("CUL868.1", "CUL433.1")
|
||||
# See attr <name> IODev XX
|
||||
|
||||
my $def = $defptr{$hash->{NAME} . "." . $cde};
|
||||
$def = $defptr{$cde} if(!$def);
|
||||
if(!$def) {
|
||||
Log 1, "CUL_WS UNDEFINED $type sensor detected, code $cde";
|
||||
return "UNDEFINED CUL_WS: $cde";
|
||||
}
|
||||
|
||||
# It's not our device
|
||||
return "" if($def->{IODev} && $def->{IODev}{NAME} ne $hash->{NAME});
|
||||
|
||||
|
||||
my $tm=TimeNow();
|
||||
$hash = $def;
|
||||
|
||||
my $typbyte = hex($a[2]) & 7;
|
||||
my $sfirstbyte = $firstbyte & 7;
|
||||
my $val = "";
|
||||
my $devtype = "unknown";
|
||||
my $family = "unknown";
|
||||
my ($sgn, $tmp, $rain, $hum, $prs, $wnd);
|
||||
|
||||
if($sfirstbyte == 7) {
|
||||
|
||||
if($typbyte == 0 && int(@a) > 6) { # temp
|
||||
$sgn = ($firstbyte&8) ? -1 : 1;
|
||||
$tmp = $sgn * ($a[6].$a[3].".".$a[4]) + $hash->{corr1};
|
||||
$val = "T: $tmp";
|
||||
$devtype = "Temp";
|
||||
}
|
||||
|
||||
if($typbyte == 1 && int(@a) > 8) { # temp/hum
|
||||
$sgn = ($firstbyte&8) ? -1 : 1;
|
||||
$tmp = $sgn * ($a[6].$a[3].".".$a[4]) + $hash->{corr1};
|
||||
$hum = ($a[7].$a[8].".".$a[5]) + $hash->{corr2};
|
||||
$val = "T: $tmp H: $hum";
|
||||
$devtype = "PS50";
|
||||
$family = "WS300";
|
||||
}
|
||||
|
||||
if($typbyte == 2 && int(@a) > 5) { # rain
|
||||
#my $more = ($firstbyte&8) ? 0 : 1000;
|
||||
my $c = $hash->{corr1} ? $hash->{corr1} : 1;
|
||||
$rain = hex($a[5].$a[3].$a[4]) + $c;
|
||||
$val = "R: $rain";
|
||||
$devtype = "Rain";
|
||||
$family = "WS7000";
|
||||
}
|
||||
|
||||
if($typbyte == 3 && int(@a) > 8) { # wind
|
||||
my $hun = ($firstbyte&8) ? 100 : 0;
|
||||
$wnd = ($a[6].$a[3].".".$a[4])+$hun;
|
||||
my $dir = (($a[7]&3).$a[8].$a[5])+0;
|
||||
my $swing = ($a[7]&6) >> 2;
|
||||
$val = "W: $wnd D: $dir A: $swing";
|
||||
$devtype = "Wind";
|
||||
$family = "WS7000";
|
||||
}
|
||||
|
||||
if($typbyte == 4 && int(@a) > 10) { # temp/hum/press
|
||||
$sgn = ($firstbyte&8) ? -1 : 1;
|
||||
$tmp = $sgn * ($a[6].$a[3].".".$a[4]) + $hash->{corr1};
|
||||
$hum = ($a[7].$a[8].".".$a[5]) + $hash->{corr2};
|
||||
$prs = ($a[9].$a[10])+ 900 + $hash->{corr3};
|
||||
if($prs < 930) {
|
||||
$prs = $prs + 100;
|
||||
}
|
||||
$val = "T: $tmp H: $hum P: $prs";
|
||||
$devtype = "Indoor";
|
||||
$family = "WS7000";
|
||||
}
|
||||
|
||||
if($typbyte == 5 && int(@a) > 5) { # brightness
|
||||
my $fakt = 1;
|
||||
my $rawfakt = ($a[5])+0;
|
||||
if($rawfakt == 1) { $fakt = 10; }
|
||||
if($rawfakt == 2) { $fakt = 100; }
|
||||
if($rawfakt == 3) { $fakt = 1000; }
|
||||
|
||||
my $br = (hex($a[5].$a[4].$a[3])*$fakt) + $hash->{corr1};
|
||||
$val = "B: $br";
|
||||
$devtype = "Brightness";
|
||||
$family = "WS7000";
|
||||
}
|
||||
|
||||
if($typbyte == 6 && int(@a) > 0) { # Pyro: wurde nie gebaut
|
||||
$devtype = "Pyro";
|
||||
$family = "WS7000";
|
||||
}
|
||||
|
||||
if($typbyte == 7 && int(@a) > 8) { # Temp/hum
|
||||
$sgn = ($firstbyte&8) ? -1 : 1;
|
||||
$tmp = $sgn * ($a[6].$a[3].".".$a[4]) + $hash->{corr1};
|
||||
$hum = ($a[7].$a[8].".".$a[5]) + $hash->{corr2};
|
||||
$val = "T: $tmp H: $hum";
|
||||
$devtype = "Temp/Hum";
|
||||
$family = "WS7000";
|
||||
|
||||
}
|
||||
|
||||
} else { # $firstbyte not 7
|
||||
|
||||
if(@a == 9 && int(@a) > 8) { # S300TH
|
||||
$sgn = ($firstbyte&8) ? -1 : 1;
|
||||
$tmp = $sgn * ($a[6].$a[3].".".$a[4]) + $hash->{corr1};
|
||||
$hum = ($a[7].$a[8].".".$a[5]) + $hash->{corr2};
|
||||
$val = "T: $tmp H: $hum";
|
||||
$devtype = "S300TH";
|
||||
$family = "WS300";
|
||||
|
||||
} elsif(@a == 15 && int(@a) > 14) { # KS300/2
|
||||
|
||||
my $c = $hash->{corr4} ? $hash->{corr4} : 255;
|
||||
$rain = sprintf("%0.1f", hex("$a[14]$a[11]$a[12]") * $c / 1000);
|
||||
$wnd = sprintf("%0.1f", "$a[9]$a[10].$a[7]" + $hash->{corr3});
|
||||
$hum = sprintf( "%02d", "$a[8]$a[5]" + $hash->{corr2});
|
||||
$tmp = sprintf("%0.1f", ("$a[6]$a[3].$a[4]"+ $hash->{corr1}),
|
||||
(($a[1] & 0xC) ? -1 : 1));
|
||||
my $ir = ((hex($a[1]) & 2)) ? "yes" : "no";
|
||||
|
||||
$val = "T: $tmp H: $hum W: $wnd R: $rain IR: $ir";
|
||||
$devtype = "KS300/2";
|
||||
$family = "WS300";
|
||||
|
||||
} elsif(int(@a) > 8) { # WS7000 Temp/Hum sensors
|
||||
|
||||
$sgn = ($firstbyte&8) ? -1 : 1;
|
||||
$tmp = $sgn * ($a[6].$a[3].".".$a[4]) + $hash->{corr1};
|
||||
$hum = ($a[7].$a[8].".".$a[5]) + $hash->{corr2};
|
||||
$val = "T: $tmp H: $hum";
|
||||
$devtype = "TH".$sfirstbyte;
|
||||
$family = "WS7000";
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
my $name = $hash->{NAME};
|
||||
if(!$val) {
|
||||
Log GetLogLevel($name,1), "CUL_WS Cannot decode $msg";
|
||||
return "";
|
||||
}
|
||||
Log GetLogLevel($name,4), "CUL_WS $devtype $name: $val";
|
||||
|
||||
|
||||
if(defined($hum) && $hum < 0) {
|
||||
Log 1, "BOGUS: $name reading: $val, skipping it";
|
||||
return $name;
|
||||
}
|
||||
|
||||
$hash->{STATE} = $val; # List overview
|
||||
$hash->{READINGS}{state}{TIME} = TimeNow(); # For list
|
||||
$hash->{READINGS}{state}{VAL} = $val;
|
||||
$hash->{CHANGED}[0] = $val; # For notify
|
||||
|
||||
$hash->{READINGS}{DEVTYPE}{VAL}=$devtype;
|
||||
$hash->{READINGS}{DEVTYPE}{TIME}=$tm;
|
||||
$hash->{READINGS}{DEVFAMILY}{VAL}=$family;
|
||||
$hash->{READINGS}{DEVFAMILY}{TIME}=$tm;
|
||||
|
||||
return $name;
|
||||
}
|
||||
|
||||
sub
|
||||
CUL_WS_Attr(@)
|
||||
{
|
||||
my @a = @_;
|
||||
|
||||
# Make possible to use the same code for different logical devices when they
|
||||
# are received through different physical devices.
|
||||
return if($a[0] ne "set" || $a[2] ne "IODev");
|
||||
my $hash = $defs{$a[1]};
|
||||
my $iohash = $defs{$a[3]};
|
||||
my $cde = $hash->{CODE};
|
||||
delete($defptr{$cde});
|
||||
$defptr{$iohash->{NAME} . "." . $cde} = $hash;
|
||||
return undef;
|
||||
}
|
||||
|
||||
|
||||
1;
|
|
@ -1,246 +0,0 @@
|
|||
##############################################
|
||||
package main;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
my %defptr;
|
||||
|
||||
# Adjust TOTAL to you meter:
|
||||
# {$defs{emwz}{READINGS}{basis}{VAL}=<meter>/<corr2>-<total_cnt> }
|
||||
|
||||
#####################################
|
||||
sub
|
||||
CUL_EM_Initialize($)
|
||||
{
|
||||
my ($hash) = @_;
|
||||
|
||||
# Message is like
|
||||
# K41350270
|
||||
|
||||
$hash->{Match} = "^E0.................\$";
|
||||
$hash->{DefFn} = "CUL_EM_Define";
|
||||
$hash->{UndefFn} = "CUL_EM_Undef";
|
||||
$hash->{ParseFn} = "CUL_EM_Parse";
|
||||
$hash->{AttrList} = "IODev do_not_notify:0,1 showtime:0,1 model:EMEM,EMWZ,EMGZ loglevel";
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub
|
||||
CUL_EM_Define($$)
|
||||
{
|
||||
my ($hash, $def) = @_;
|
||||
my @a = split("[ \t][ \t]*", $def);
|
||||
|
||||
return "wrong syntax: define <name> CUL_EM <code> [corr1 corr2 CostPerUnit BasicFeePerMonth]"
|
||||
if(int(@a) < 3 || int(@a) > 7);
|
||||
return "Define $a[0]: wrong CODE format: valid is 1-12"
|
||||
if($a[2] !~ m/^\d$/ || $a[2] < 1 || $a[2] > 12);
|
||||
|
||||
$hash->{CODE} = $a[2];
|
||||
|
||||
if($a[2] >= 1 && $a[2] <= 4) { # EMWZ: nRotation in 5 minutes
|
||||
my $c = (int(@a) > 3 ? $a[3] : 150);
|
||||
$hash->{corr1} = (12/$c); # peak/current
|
||||
$c = (int(@a) > 4 ? $a[4] : 1800);
|
||||
$hash->{corr2} = (12/$c); # total
|
||||
|
||||
} elsif($a[2] >= 5 && $a[2] <= 8) { # EMEM
|
||||
# corr1 is the correction factor for power
|
||||
$hash->{corr1} = (int(@a) > 3 ? $a[3] : 0.01);
|
||||
# corr2 is the correction factor for energy
|
||||
$hash->{corr2} = (int(@a) > 4 ? $a[4] : 0.001);
|
||||
|
||||
} elsif($a[2] >= 9 && $a[2] <= 12) { # EMGZ: 0.01
|
||||
$hash->{corr1} = (int(@a) > 3 ? $a[3] : 0.01);
|
||||
$hash->{corr2} = (int(@a) > 4 ? $a[4] : 0.01);
|
||||
|
||||
} else {
|
||||
$hash->{corr1} = 1;
|
||||
$hash->{corr2} = 1;
|
||||
}
|
||||
$hash->{CostPerUnit} = (int(@a) > 5 ? $a[5] : 0);
|
||||
$hash->{BasicFeePerMonth} = (int(@a) > 6 ? $a[6] : 0);
|
||||
|
||||
$defptr{$a[2]} = $hash;
|
||||
AssignIoPort($hash);
|
||||
return undef;
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub
|
||||
CUL_EM_Undef($$)
|
||||
{
|
||||
my ($hash, $name) = @_;
|
||||
delete($defptr{$hash->{CODE}});
|
||||
return undef;
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub
|
||||
CUL_EM_Parse($$)
|
||||
{
|
||||
my ($hash,$msg) = @_;
|
||||
|
||||
# 0123456789012345678
|
||||
# E01012471B80100B80B -> Type 01, Code 01, Cnt 10
|
||||
my @a = split("", $msg);
|
||||
my $tpe = ($a[1].$a[2])+0;
|
||||
my $cde = ($a[3].$a[4])+0;
|
||||
|
||||
# seqno = number of received datagram in sequence, runs from 2 to 255
|
||||
# total_cnt= total (cumulated) value in ticks as read from the device
|
||||
# basis_cnt= correction to total (cumulated) value in ticks to account for
|
||||
# counter wraparounds
|
||||
# total = total (cumulated) value in device units
|
||||
# current = current value (average over latest 5 minutes) in device units
|
||||
# peak = maximum value in device units
|
||||
|
||||
my $seqno = hex($a[5].$a[6]);
|
||||
my $total_cnt = hex($a[ 9].$a[10].$a[ 7].$a[ 8]);
|
||||
my $current_cnt = hex($a[13].$a[14].$a[11].$a[12]);
|
||||
my $peak_cnt = hex($a[17].$a[18].$a[15].$a[16]);
|
||||
|
||||
# these are the raw readings from the device
|
||||
my $val = sprintf("CNT: %d CUM: %d 5MIN: %d TOP: %d",
|
||||
$seqno, $total_cnt, $current_cnt, $peak_cnt);
|
||||
|
||||
if($defptr{$cde}) {
|
||||
my $def = $defptr{$cde};
|
||||
return "" if($def->{IODev} && $def->{IODev}{NAME} ne $hash->{NAME});
|
||||
|
||||
$hash = $defptr{$cde};
|
||||
|
||||
my $tn = TimeNow(); # current time
|
||||
my $c= 0; # count changes
|
||||
my %readings;
|
||||
|
||||
my $n = $hash->{NAME};
|
||||
Log GetLogLevel($n,5), "CUL_EM $n: $val";
|
||||
$readings{RAW} = $val;
|
||||
|
||||
#
|
||||
# calculate readings
|
||||
#
|
||||
# initialize total_cnt_last
|
||||
my $total_cnt_last = 0;
|
||||
if(defined($hash->{READINGS}{total_cnt})) {
|
||||
$total_cnt_last= $hash->{READINGS}{total_cnt}{VAL};
|
||||
}
|
||||
|
||||
|
||||
# initialize basis_cnt_last
|
||||
my $basis_cnt = 0;
|
||||
if(defined($hash->{READINGS}{basis})) {
|
||||
$basis_cnt = $hash->{READINGS}{basis}{VAL};
|
||||
}
|
||||
|
||||
# correct counter wraparound
|
||||
if($total_cnt< $total_cnt_last) {
|
||||
$basis_cnt += 65536;
|
||||
$readings{basis} = $basis_cnt;
|
||||
}
|
||||
|
||||
#
|
||||
# translate into device units
|
||||
#
|
||||
my $corr1 = $hash->{corr1}; # EMEM power correction factor
|
||||
my $corr2 = $hash->{corr2}; # EMEM energy correction factor
|
||||
|
||||
my $total = ($basis_cnt+$total_cnt)*$corr2;
|
||||
my $current = $current_cnt*$corr1;
|
||||
my $peak = $peak_cnt*$corr1;
|
||||
|
||||
$val = sprintf("CNT: %d CUM: %0.3f 5MIN: %0.3f TOP: %0.3f",
|
||||
$seqno, $total, $current, $peak);
|
||||
$hash->{STATE} = $val;
|
||||
$hash->{CHANGED}[$c++] = "$val";
|
||||
|
||||
$readings{total_cnt} = $total_cnt;
|
||||
$readings{current_cnt} = $current_cnt;
|
||||
$readings{peak_cnt} = $peak_cnt;
|
||||
$readings{seqno} = $seqno;
|
||||
$readings{total} = $total;
|
||||
$readings{current} = $current;
|
||||
$readings{peak} = $peak;
|
||||
|
||||
|
||||
###################################
|
||||
# Start CUMULATE day and month
|
||||
Log GetLogLevel($n,4), "CUL_EM $n: $val";
|
||||
my $tsecs_prev;
|
||||
|
||||
#----- get previous tsecs
|
||||
if(defined($hash->{READINGS}{tsecs})) {
|
||||
$tsecs_prev= $hash->{READINGS}{tsecs}{VAL};
|
||||
} else {
|
||||
$tsecs_prev= 0; # 1970-01-01
|
||||
}
|
||||
|
||||
#----- save actual tsecs
|
||||
my $tsecs= time(); # number of non-leap seconds since January 1, 1970, UTC
|
||||
$readings{tsecs} = $tsecs;
|
||||
|
||||
#----- get cost parameter
|
||||
my $cost = $hash->{CostPerUnit};
|
||||
my $basicfee = $hash->{BasicFeePerMonth};
|
||||
|
||||
#----- check whether day or month was changed
|
||||
if(!defined($hash->{READINGS}{cum_day})) {
|
||||
#----- init cum_day if it is not set
|
||||
$val = sprintf("CUM_DAY: %0.3f CUM: %0.3f COST: %0.2f", 0,$total,0);
|
||||
$readings{cum_day} = $val;
|
||||
|
||||
} else {
|
||||
|
||||
if( (localtime($tsecs_prev))[3] != (localtime($tsecs))[3] ) {
|
||||
#----- day has changed (#3)
|
||||
my @cmv = split(" ", $hash->{READINGS}{cum_day}{VAL});
|
||||
$val = sprintf("CUM_DAY: %0.3f CUM: %0.3f COST: %0.2f",
|
||||
$total-$cmv[3], $total, ($total-$cmv[3])*$cost);
|
||||
$readings{cum_day} = $val;
|
||||
Log GetLogLevel($n,3), "CUL_EM $n: $val";
|
||||
|
||||
|
||||
if( (localtime($tsecs_prev))[4] != (localtime($tsecs))[4] ) {
|
||||
|
||||
#----- month has changed (#4)
|
||||
if(!defined($hash->{READINGS}{cum_month})) {
|
||||
# init cum_month if not set
|
||||
$val = sprintf("CUM_MONTH: %0.3f CUM: %0.3f COST: %0.2f",
|
||||
0, $total, 0);
|
||||
$readings{cum_month} = $val;
|
||||
|
||||
} else {
|
||||
@cmv = split(" ", $hash->{READINGS}{cum_month}{VAL});
|
||||
$val = sprintf("CUM_MONTH: %0.3f CUM: %0.3f COST: %0.2f",
|
||||
$total-$cmv[3], $total,($total-$cmv[3])*$cost+$basicfee);
|
||||
$readings{cum_month} = $val;
|
||||
Log GetLogLevel($n,3), "CUL_EM $n: $val";
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
# End CUMULATE day and month
|
||||
###################################
|
||||
|
||||
|
||||
foreach my $k (keys %readings) {
|
||||
$hash->{READINGS}{$k}{TIME}= $tn;
|
||||
$hash->{READINGS}{$k}{VAL} = $readings{$k};
|
||||
$hash->{CHANGED}[$c++] = "$k: $readings{$k}";
|
||||
}
|
||||
return $hash->{NAME};
|
||||
|
||||
} else {
|
||||
|
||||
Log 1, "CUL_EM detected, Code $cde $val";
|
||||
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
1;
|
||||
|
|
@ -1,371 +0,0 @@
|
|||
################################################################
|
||||
#
|
||||
# Copyright notice
|
||||
#
|
||||
# (c) 2008 Dr. Boris Neubert (omega@online.de)
|
||||
#
|
||||
# This script 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.
|
||||
#
|
||||
# The GNU General Public License can be found at
|
||||
# http://www.gnu.org/copyleft/gpl.html.
|
||||
# A copy is found in the textfile GPL.txt and important notices to the license
|
||||
# from the author is found in LICENSE.txt distributed with these scripts.
|
||||
#
|
||||
# This script 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.
|
||||
#
|
||||
# This copyright notice MUST APPEAR in all copies of the script!
|
||||
#
|
||||
################################################################
|
||||
|
||||
package main;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
my %functions = ( ALL_UNITS_OFF => "all_units_off",
|
||||
ALL_LIGHTS_ON => "all_lights_on",
|
||||
ON => "on",
|
||||
OFF => "off",
|
||||
DIM => "dimdown",
|
||||
BRIGHT => "dimup",
|
||||
ALL_LIGHTS_OFF => "all_lights_off",
|
||||
EXTENDED_CODE => "",
|
||||
HAIL_REQUEST => "",
|
||||
HAIL_ACK => "",
|
||||
PRESET_DIM1 => "",
|
||||
PRESET_DIM2 => "",
|
||||
EXTENDED_DATA_TRANSFER => "",
|
||||
STATUS_ON => "",
|
||||
STATUS_OFF => "",
|
||||
STATUS_REQUEST => "",
|
||||
);
|
||||
|
||||
my %snoitcnuf; # the reverse of the above
|
||||
|
||||
my %functions_rewrite = ( "all_units_off" => "off",
|
||||
"all_lights_on" => "on",
|
||||
"all_lights_off" => "off",
|
||||
);
|
||||
|
||||
my %functions_snd = qw( ON 0010
|
||||
OFF 0011
|
||||
DIM 0100
|
||||
BRIGHT 0101 );
|
||||
|
||||
my %housecodes_snd = qw(A 0110 B 1110 C 0010 D 1010
|
||||
E 0001 F 1001 G 0101 H 1101
|
||||
I 0111 J 1111 K 0011 K 1011
|
||||
M 0000 N 1000 O 0100 P 1100);
|
||||
|
||||
my %unitcodes_snd = qw( 1 0110 2 1110 3 0010 4 1010
|
||||
5 0001 6 1001 7 0101 8 1101
|
||||
9 0111 10 1111 11 0011 12 1011
|
||||
13 0000 14 1000 15 0100 16 1100);
|
||||
|
||||
|
||||
my %functions_set = ( "on" => 0,
|
||||
"off" => 0,
|
||||
"dimup" => 1,
|
||||
"dimdown" => 1,
|
||||
"on-till" => 1,
|
||||
);
|
||||
|
||||
# devices{HOUSE}{UNIT} -> Pointer to hash for the device for lookups
|
||||
my %devices;
|
||||
|
||||
my %models = (
|
||||
lm12 => 'dimmer',
|
||||
lm15 => 'simple',
|
||||
am12 => 'simple',
|
||||
tm13 => 'simple',
|
||||
);
|
||||
|
||||
my @lampmodules = ('lm12','lm15'); # lamp modules
|
||||
|
||||
|
||||
sub
|
||||
X10_Initialize($)
|
||||
{
|
||||
my ($hash) = @_;
|
||||
|
||||
foreach my $k (keys %functions) {
|
||||
$snoitcnuf{$functions{$k}}= $k;
|
||||
}
|
||||
|
||||
$hash->{Match} = "^X10:[A-P];";
|
||||
$hash->{SetFn} = "X10_Set";
|
||||
$hash->{StateFn} = "X10_SetState";
|
||||
$hash->{DefFn} = "X10_Define";
|
||||
$hash->{UndefFn} = "X10_Undef";
|
||||
$hash->{ParseFn} = "X10_Parse";
|
||||
$hash->{AttrList} = "IODev follow-on-for-timer:1,0 do_not_notify:1,0 dummy:1,0 showtime:1,0 model:lm12,lm15,am12,tm13 loglevel:0,1,2,3,4,5,6";
|
||||
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub
|
||||
X10_SetState($$$$)
|
||||
{
|
||||
my ($hash, $tim, $vt, $val) = @_;
|
||||
return undef;
|
||||
}
|
||||
|
||||
#############################
|
||||
sub
|
||||
X10_Do_On_Till($@)
|
||||
{
|
||||
my ($hash, @a) = @_;
|
||||
return "Timespec (HH:MM[:SS]) needed for the on-till command" if(@a != 3);
|
||||
|
||||
my ($err, $hr, $min, $sec, $fn) = GetTimeSpec($a[2]);
|
||||
return $err if($err);
|
||||
|
||||
my @lt = localtime;
|
||||
my $hms_till = sprintf("%02d:%02d:%02d", $hr, $min, $sec);
|
||||
my $hms_now = sprintf("%02d:%02d:%02d", $lt[2], $lt[1], $lt[0]);
|
||||
if($hms_now ge $hms_till) {
|
||||
Log 4, "on-till: won't switch as now ($hms_now) is later than $hms_till";
|
||||
return "";
|
||||
}
|
||||
|
||||
my @b = ($a[0], "on");
|
||||
X10_Set($hash, @b);
|
||||
CommandDefine(undef, $hash->{NAME} . "_till at $hms_till set $a[0] off");
|
||||
|
||||
}
|
||||
|
||||
###################################
|
||||
|
||||
sub
|
||||
X11_Write($$$)
|
||||
{
|
||||
my ($hash, $function, $dim)= @_;
|
||||
my $name = $hash->{NAME};
|
||||
my $housecode= $hash->{HOUSE};
|
||||
my $unitcode = $hash->{UNIT};
|
||||
my $x10func = $snoitcnuf{$function};
|
||||
undef $function; # do not use after this point
|
||||
my $prefix= "X10 device $name:";
|
||||
|
||||
Log 5, "$prefix sending X10:$housecode;$unitcode;$x10func $dim";
|
||||
|
||||
my ($hc_b, $hu_b, $hf_b);
|
||||
my ($hc, $hu, $hf);
|
||||
|
||||
# Header:Code, Address
|
||||
$hc_b = "00000100"; # 0x04
|
||||
$hc = pack("B8", $hc_b);
|
||||
$hu_b = $housecodes_snd{$housecode} . $unitcodes_snd{$unitcode};
|
||||
$hu = pack("B8", $hu_b);
|
||||
IOWrite($hash, $hc, $hu);
|
||||
|
||||
# Header:Code, Function
|
||||
$hc_b = substr(unpack('B8', pack('C', $dim)), 3) . # dim, 0..22
|
||||
"110"; # always 110
|
||||
$hc = pack("B8", $hc_b);
|
||||
$hf_b = $housecodes_snd{$housecode} . $functions_snd{$x10func};
|
||||
$hf = pack("B8", $hf_b);
|
||||
IOWrite($hash, $hc, $hf);
|
||||
}
|
||||
|
||||
###################################
|
||||
sub
|
||||
X10_Set($@)
|
||||
{
|
||||
my ($hash, @a) = @_;
|
||||
my $ret = undef;
|
||||
my $na = int(@a);
|
||||
|
||||
# initialization and sanity checks
|
||||
return "no set value specified" if($na < 2);
|
||||
|
||||
my $name= $hash->{NAME};
|
||||
my $function= $a[1];
|
||||
my $nrparams= $functions_set{$function};
|
||||
return "Unknown argument $function, choose one of " .
|
||||
join(",", sort keys %functions_set) if(!defined($nrparams));
|
||||
return "Wrong number of parameters" if($na != 2+$nrparams);
|
||||
|
||||
# special for on-till
|
||||
return X10_Do_On_Till($hash, @a) if($function eq "on-till");
|
||||
|
||||
# argument evaluation
|
||||
my $model= $hash->{MODEL};
|
||||
|
||||
my $dim= 0;
|
||||
if($function =~ m/^dim/) {
|
||||
return "Cannot dim $name (model $model)" if($models{$model} ne "dimmer");
|
||||
my $arg= $a[2];
|
||||
return "Wrong argument $arg, use 0..22" if($arg !~ m/^[0-9]{1,2}$/);
|
||||
return "Wrong argument $arg, use 0..22" if($arg>22);
|
||||
$dim= $arg;
|
||||
}
|
||||
|
||||
# send command to CM11
|
||||
X11_Write($hash, $function, $dim) if(!IsDummy($a[0]));
|
||||
|
||||
my $v = join(" ", @a);
|
||||
Log GetLogLevel($a[0],2), "X10 set $v";
|
||||
(undef, $v) = split(" ", $v, 2); # Not interested in the name...
|
||||
|
||||
my $tn = TimeNow();
|
||||
|
||||
$hash->{CHANGED}[0] = $v;
|
||||
$hash->{STATE} = $v;
|
||||
$hash->{READINGS}{state}{TIME} = $tn;
|
||||
$hash->{READINGS}{state}{VAL} = $v;
|
||||
|
||||
return undef;
|
||||
}
|
||||
|
||||
#############################
|
||||
sub
|
||||
X10_Define($$)
|
||||
{
|
||||
my ($hash, $def) = @_;
|
||||
my @a = split("[ \t][ \t]*", $def);
|
||||
|
||||
return "wrong syntax: define <name> X10 model housecode unitcode"
|
||||
if(int(@a)!= 5);
|
||||
|
||||
my $model= $a[2];
|
||||
return "Define $a[0]: wrong model: specify one of " .
|
||||
join ",", sort keys %models
|
||||
if(!grep { $_ eq $model} keys %models);
|
||||
|
||||
my $housecode = $a[3];
|
||||
return "Define $a[0]: wrong housecode format: specify a value ".
|
||||
"from A to P"
|
||||
if($housecode !~ m/^[A-P]$/i);
|
||||
|
||||
my $unitcode = $a[4];
|
||||
return "Define $a[0]: wrong unitcode format: specify a value " .
|
||||
"from 1 to 16"
|
||||
if( ($unitcode<1) || ($unitcode>16) );
|
||||
|
||||
|
||||
$hash->{MODEL} = $model;
|
||||
$hash->{HOUSE} = $housecode;
|
||||
$hash->{UNIT} = $unitcode;
|
||||
|
||||
if(defined($devices{$housecode}{$unitcode})) {
|
||||
return "Error: duplicate X10 device $housecode $unitcode definition " .
|
||||
$hash->{NAME} . " (previous: " .
|
||||
$devices{$housecode}{$unitcode}->{NAME} .")";
|
||||
}
|
||||
|
||||
$devices{$housecode}{$unitcode}= $hash;
|
||||
|
||||
AssignIoPort($hash);
|
||||
}
|
||||
|
||||
#############################
|
||||
sub
|
||||
X10_Undef($$)
|
||||
{
|
||||
my ($hash, $name) = @_;
|
||||
if( defined($hash->{HOUSE}) && defined($hash->{UNIT}) ) {
|
||||
delete($devices{$hash->{HOUSE}}{$hash->{UNIT}});
|
||||
}
|
||||
return undef;
|
||||
}
|
||||
|
||||
#############################
|
||||
sub
|
||||
X10_Parse($$)
|
||||
{
|
||||
my ($hash, $msg) = @_;
|
||||
|
||||
# message example: X10:N;1 12;OFF
|
||||
(undef, $msg)= split /:/, $msg, 2; # strip off "X10"
|
||||
my ($housecode,$unitcodes,$command)= split /;/, $msg, 4;
|
||||
|
||||
my @list; # list of selected devices
|
||||
|
||||
#
|
||||
# command evaluation
|
||||
#
|
||||
my ($x10func,$arg)= split / /, $command, 2;
|
||||
my $function= $functions{$x10func}; # translate, eg BRIGHT -> dimup
|
||||
undef $x10func; # do not use after this point
|
||||
|
||||
# the following code sequence converts an all on/off command into
|
||||
# a sequence of simple on/off commands for all defined devices
|
||||
my $all_lights= ($function=~ m/^all_lights_/);
|
||||
my $all_units= ($function=~ m/^all_units_/);
|
||||
if($all_lights || $all_units) {
|
||||
$function= $functions_rewrite{$function}; # translate, all_lights_on -> on
|
||||
$unitcodes= "";
|
||||
foreach my $unitcode (keys %{ $devices{$housecode} } ) {
|
||||
my $h= $devices{$housecode}{$unitcode};
|
||||
my $islampmodule= grep { $_ eq $h->{MODEL} } @lampmodules;
|
||||
if($all_units || $islampmodule ) {
|
||||
$unitcodes.= " " if($unitcodes ne "");
|
||||
$unitcodes.= $h->{UNIT};
|
||||
}
|
||||
}
|
||||
# no units for that housecode
|
||||
if($unitcodes eq "") {
|
||||
Log 3, "X10 No units with housecode $housecode, command $command, " .
|
||||
"please define one";
|
||||
push(@list,
|
||||
"UNDEFINED X10 device $housecode ?, command $command");
|
||||
return @list;
|
||||
}
|
||||
}
|
||||
|
||||
# apply to each unit in turn
|
||||
my @unitcodes= split / /, $unitcodes;
|
||||
|
||||
if(!int(@unitcodes)) {
|
||||
# command without unitcodes, this happens when a single on/off is sent
|
||||
# but no unit was previously selected
|
||||
Log 3, "X10 No unit selected for housecode $housecode, command $command";
|
||||
push(@list,
|
||||
"UNDEFINED X10 device $housecode ?, command $command");
|
||||
return @list;
|
||||
}
|
||||
|
||||
# function rewriting
|
||||
my $value= $function;
|
||||
return @list if($value eq ""); # function not evaluated
|
||||
|
||||
# function determined, add argument
|
||||
if( defined($arg) ) {
|
||||
# received dims from 0..210
|
||||
my $dim= $arg;
|
||||
$value = "$value $dim" ;
|
||||
}
|
||||
|
||||
|
||||
my $unknown_unitcodes= '';
|
||||
foreach my $unitcode (@unitcodes) {
|
||||
my $h= $devices{$housecode}{$unitcode};
|
||||
if($h) {
|
||||
return "" if($h->{IODev} && $h->{IODev}{NAME} ne $hash->{NAME});
|
||||
my $name= $h->{NAME};
|
||||
$h->{CHANGED}[0] = $value;
|
||||
$h->{STATE} = $value;
|
||||
$h->{READINGS}{state}{TIME} = TimeNow();
|
||||
$h->{READINGS}{state}{VAL} = $value;
|
||||
Log GetLogLevel($name,2), "X10 $name $value";
|
||||
push(@list, $name);
|
||||
} else {
|
||||
Log 3, "X10 Unknown device $housecode $unitcode, command $command, " .
|
||||
"please define it";
|
||||
push(@list,
|
||||
"UNDEFINED X10 device $housecode $unitcode, command $command");
|
||||
}
|
||||
}
|
||||
return @list;
|
||||
|
||||
}
|
||||
|
||||
|
||||
1;
|
|
@ -1,673 +0,0 @@
|
|||
################################################################
|
||||
#
|
||||
# Copyright notice
|
||||
#
|
||||
# (c) 2007 Copyright: Martin Klerx (Martin at klerx dot de)
|
||||
# All rights reserved
|
||||
#
|
||||
# This script 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.
|
||||
#
|
||||
# The GNU General Public License can be found at
|
||||
# http://www.gnu.org/copyleft/gpl.html.
|
||||
# A copy is found in the textfile GPL.txt and important notices to the license
|
||||
# from the author is found in LICENSE.txt distributed with these scripts.
|
||||
#
|
||||
# This script 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.
|
||||
#
|
||||
# This copyright notice MUST APPEAR in all copies of the script!
|
||||
#
|
||||
################################################################
|
||||
# examples:
|
||||
# define WS300Device WS300 /dev/ttyUSB1 (fixed name, must be first)
|
||||
# define ash2200-1 WS300 0
|
||||
# define ash2200-2 WS300 1
|
||||
# ...
|
||||
# define ash2200-8 WS300 7
|
||||
# define ks300 WS300 8 (always 8)
|
||||
# define ws300 WS300 9 (always 9)
|
||||
# set WS300Device <interval(5-60 min.)> <height(0-2000 m)> <rainvalume(ml)>
|
||||
################################################################
|
||||
|
||||
package main;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
my %defptr;
|
||||
my $DeviceName="";
|
||||
my $inbuf="";
|
||||
|
||||
my $config;
|
||||
my $cmd=0x32;
|
||||
my $errcount=0;
|
||||
my $ir="no";
|
||||
my $willi=0;
|
||||
my $oldwind=0.0;
|
||||
my $polling=0;
|
||||
my $acthour=99;
|
||||
my $actday=99;
|
||||
my $actmonth=99;
|
||||
my $oldrain=0;
|
||||
my $rain_hour=0;
|
||||
my $rain_day=0;
|
||||
my $rain_month=0;
|
||||
#####################################
|
||||
sub
|
||||
WS300_Initialize($)
|
||||
{
|
||||
my ($hash) = @_;
|
||||
|
||||
# Provider
|
||||
$hash->{AttrList} = "do_not_notify:0,1 showtime:0,1 model:ws300 loglevel:0,1,2,3,4,5,6";
|
||||
$hash->{DefFn} = "WS300_Define";
|
||||
$hash->{GetFn} = "WS300_Get";
|
||||
$hash->{ParseFn} = "WS300_Parse";
|
||||
$hash->{SetFn} = "WS300_Set";
|
||||
$hash->{UndefFn} = "WS300_Undef";
|
||||
|
||||
$hash->{Clients} = ":WS300:"; # Not needed
|
||||
$hash->{Match} = "^WS300.*"; # Not needed
|
||||
$hash->{ReadFn} = "WS300_Read"; # Not needed
|
||||
$hash->{Type} = "FHZ1000"; # Not needed
|
||||
$hash->{WriteFn} = "WS300_Write"; # Not needed
|
||||
}
|
||||
|
||||
###################################
|
||||
sub
|
||||
WS300_Set($@)
|
||||
{
|
||||
my ($hash, @a) = @_;
|
||||
if($hash->{NAME} eq "WS300Device")
|
||||
{
|
||||
return "wrong syntax: set WS300Device <Interval(5-60 min.)> <height(0-2000 m)> <rainvolume(ml)>" if(int(@a) < 4 || int($a[1]) < 5 || int($a[1]) > 60 || int($a[2]) > 2000);
|
||||
my $bstring = sprintf("%c%c%c%c%c%c%c%c",0xfe,0x30,(int($a[1])&0xff),((int($a[2])>>8)&0xff),(int($a[2])&0xff),((int($a[3])>>8)&0xff),(int($a[3])&0xff),0xfc);
|
||||
$hash->{PortObj}->write($bstring);
|
||||
Log 1,"WS300 synchronization started (".unpack('H*',$bstring).")";
|
||||
return "the ws300pc will now synchronize for 10 minutes";
|
||||
}
|
||||
return "No set function implemented";
|
||||
}
|
||||
|
||||
###################################
|
||||
sub
|
||||
WS300_Get(@)
|
||||
{
|
||||
my ($hash, @a) = @_;
|
||||
if($hash->{NAME} eq "WS300Device")
|
||||
{
|
||||
Log 5,"WS300_Get $a[0] $a[1]";
|
||||
WS300_Poll($hash);
|
||||
return undef;
|
||||
}
|
||||
return "No get function implemented";
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub
|
||||
WS300_Define($$)
|
||||
{
|
||||
my ($hash, $def) = @_;
|
||||
my @a = split("[ \t][ \t]*", $def);
|
||||
my $po;
|
||||
if($a[0] eq "WS300Device")
|
||||
{
|
||||
$defptr{10} = $hash;
|
||||
return "wrong syntax: define WS300Device WS300 <DeviceName>" if(int(@a) < 3);
|
||||
$DeviceName = $a[2];
|
||||
$hash->{STATE} = "Initializing";
|
||||
$hash->{SENSOR} = 10;
|
||||
$hash->{READINGS}{WS300Device}{VAL} = "Initializing";
|
||||
$hash->{READINGS}{WS300Device}{TIME} = TimeNow;
|
||||
|
||||
if ($^O=~/Win/) {
|
||||
eval ("use Win32::SerialPort;");
|
||||
$po = new Win32::SerialPort ($DeviceName);
|
||||
}else{
|
||||
eval ("use Device::SerialPort;");
|
||||
$po = new Device::SerialPort ($DeviceName);
|
||||
}
|
||||
if(!$po)
|
||||
{
|
||||
$hash->{STATE} = "error opening device";
|
||||
$hash->{READINGS}{WS300Device}{VAL} = "error opening device";
|
||||
$hash->{READINGS}{WS300Device}{TIME} = TimeNow;
|
||||
Log 1,"Error opening WS300 Device $a[2]";
|
||||
return "Can't open $a[2]: $!\n";
|
||||
}
|
||||
$po->reset_error();
|
||||
$po->baudrate(19200);
|
||||
$po->databits(8);
|
||||
$po->parity('even');
|
||||
$po->stopbits(1);
|
||||
$po->handshake('none');
|
||||
$po->rts_active(1);
|
||||
$po->dtr_active(1);
|
||||
sleep(1);
|
||||
$po->rts_active(0);
|
||||
$po->write_settings;
|
||||
$hash->{PortObj} = $po;
|
||||
$hash->{DeviceName} = $a[2];
|
||||
$hash->{STATE} = "opened";
|
||||
$hash->{READINGS}{WS300Device}{VAL} = "opened";
|
||||
$hash->{READINGS}{WS300Device}{TIME} = TimeNow;
|
||||
CommandDefine(undef,"WS300Device_timer at +*00:00:05 get WS300Device data");
|
||||
Log 1,"WS300 Device $a[2] opened";
|
||||
$attr{$a[0]}{savefirst} = 1;
|
||||
return undef;
|
||||
}
|
||||
return "wrong syntax: define <name> WS300 <sensor (0-9)>\n0-7=ASH2200\n8=KS300\n9=WS300" if(int(@a) < 3);
|
||||
return "no device: define WS300Device WS300 <DeviceName> first" if($DeviceName eq "");
|
||||
return "Define $a[0]: wrong sensor number." if($a[2] !~ m/^[0-9]$/);
|
||||
$hash->{SENSOR} = $a[2];
|
||||
$defptr{$a[2]} = $hash;
|
||||
|
||||
return undef;
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub
|
||||
WS300_Undef($$)
|
||||
{
|
||||
my ($hash, $name) = @_;
|
||||
return undef if(!defined($hash->{SENSOR}));
|
||||
delete($defptr{$hash->{SENSOR}});
|
||||
return undef;
|
||||
}
|
||||
|
||||
|
||||
#####################################
|
||||
sub
|
||||
WS300_Parse($$)
|
||||
{
|
||||
my ($hash, $msg) = @_;
|
||||
my $ll = GetLogLevel("WS300Device");
|
||||
$ll = 5 if($ll == 2);
|
||||
|
||||
my @c = split("", $config);
|
||||
my @cmsg = split("",unpack('H*',$config));
|
||||
my $dmsg = unpack('H*',$msg);
|
||||
my @a = split("", $dmsg);
|
||||
my $val = "";
|
||||
my $tm;
|
||||
my $h;
|
||||
my $t;
|
||||
my $b;
|
||||
my $l;
|
||||
my $value;
|
||||
my $offs=0;
|
||||
my $ref;
|
||||
my $def;
|
||||
my $zeit;
|
||||
my @txt = ( "temperature", "humidity", "wind", "rain_raw", "israining", "battery", "lost_receives", "pressure", "rain_cum", "rain_hour", "rain_day", "rain_month");
|
||||
my @sfx = ( "(Celsius)", "(%)", "(km/h)", "(counter)", "(yes/no)", " ", "(counter)", "(hPa)", "(mm)", "(mm)", "(mm)", "(mm)");
|
||||
# 1 2 3 4 5 6 7 8
|
||||
# 012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
|
||||
# 3180800001005d4e00000000000000000000000000000000000000000000594a0634001e00f62403f1fc stored
|
||||
# aaaatttthhtttthhtttthhtttthhtttthhtttthhtttthhtttthhtttthhrrrrwwwwtttthhpppp
|
||||
# 3300544a0000000000000000000000000000000000000000000057470634002c00f32303ee32fc current
|
||||
# tttthhtttthhtttthhtttthhtttthhtttthhtttthhtttthhtttthhrrrrwwwwtttthhppppss
|
||||
# 3210000000000000001005003a0127fc config
|
||||
# 001122334455667788iihhhhmmmm
|
||||
$offs = 2 if(hex($a[0].$a[1]) == 0x33);
|
||||
$offs = 10 if(hex($a[0].$a[1]) == 0x31);
|
||||
if($offs == 0)
|
||||
{
|
||||
Log 1,"WS300 illegal data in WS300_Parse";
|
||||
return undef;
|
||||
}
|
||||
$zeit = time;
|
||||
my $wind = hex($a[58+$offs].$a[59+$offs].$a[60+$offs].$a[61+$offs]);
|
||||
$wind /= 10.0;
|
||||
if(hex($a[0].$a[1]) == 0x33)
|
||||
{
|
||||
return undef if(hex($a[74].$a[75]) == $willi && $wind == $oldwind );
|
||||
$willi = hex($a[74].$a[75]);
|
||||
$ir="no";
|
||||
$ir="yes" if(($willi&0x80));
|
||||
}
|
||||
else
|
||||
{
|
||||
$zeit -= (hex($a[6].$a[7].$a[8].$a[9])*60);
|
||||
}
|
||||
my @lt = localtime($zeit);
|
||||
$tm = sprintf("%04d-%02d-%02d %02d:%02d:%02d",$lt[5]+1900, $lt[4]+1, $lt[3], $lt[2], $lt[1], $lt[0]);
|
||||
$oldwind = $wind;
|
||||
my $press = hex($a[68+$offs].$a[69+$offs].$a[70+$offs].$a[71+$offs]);
|
||||
my $hpress = hex($cmsg[22].$cmsg[23].$cmsg[24].$cmsg[25]);
|
||||
$hpress /= 8.5;
|
||||
$press += $hpress;
|
||||
$press = sprintf("%.1f",$press);
|
||||
my $rainc = hex($a[54+$offs].$a[55+$offs].$a[56+$offs].$a[57+$offs]);
|
||||
my $rain = hex($cmsg[26].$cmsg[27].$cmsg[28].$cmsg[29]);
|
||||
$rain *= $rainc;
|
||||
$rain /= 1000;
|
||||
$rain = sprintf("%.1f",$rain);
|
||||
for(my $s=0;$s<9;$s++)
|
||||
{
|
||||
if((ord($c[$s+1])&0x10))
|
||||
{
|
||||
my $p=($s*6)+$offs;
|
||||
Log $ll,"Sensor $s vorhanden";
|
||||
if(!defined($defptr{$s}))
|
||||
{
|
||||
Log(3,"WS300 $s: undefined");
|
||||
}
|
||||
else
|
||||
{
|
||||
$def = $defptr{$s};
|
||||
$def->{READINGS}{$txt[0]}{VAL} = 0 if(!$def->{READINGS});
|
||||
$ref = $def->{READINGS};
|
||||
|
||||
$t = hex($a[$p].$a[$p+1].$a[$p+2].$a[$p+3]);
|
||||
$t -= 65535 if( $t > 32767 );
|
||||
$t /= 10.0;
|
||||
$h = hex($a[$p+4].$a[$p+5]);
|
||||
if((ord($c[$s+1])&0xe0))
|
||||
{
|
||||
$b = "Empty"
|
||||
}
|
||||
else
|
||||
{
|
||||
$b = "Ok"
|
||||
}
|
||||
$l = (ord($c[$s+1])&0x0f);
|
||||
if($s < 8)
|
||||
{
|
||||
# state
|
||||
$val = "T: $t H: $h Bat: $b LR: $l";
|
||||
$def->{STATE} = $val;
|
||||
$def->{CHANGED}[0] = $val;
|
||||
$def->{CHANGETIME}[0] = $tm;
|
||||
# temperatur
|
||||
$ref->{$txt[0]}{TIME} = $tm;
|
||||
$value = "$t $sfx[0]";
|
||||
$ref->{$txt[0]}{VAL} = $value;
|
||||
$def->{CHANGED}[1] = "$txt[0]: $value";
|
||||
$def->{CHANGETIME}[1] = $tm;
|
||||
# humidity
|
||||
$ref->{$txt[1]}{TIME} = $tm;
|
||||
$value = "$h $sfx[1]";
|
||||
$ref->{$txt[1]}{VAL} = $value;
|
||||
$def->{CHANGED}[2] = "$txt[1]: $value";
|
||||
$def->{CHANGETIME}[2] = $tm;
|
||||
# battery
|
||||
$ref->{$txt[5]}{TIME} = $tm;
|
||||
$value = "$b $sfx[5]";
|
||||
$ref->{$txt[5]}{VAL} = $value;
|
||||
$def->{CHANGED}[3] = "$txt[5]: $value";
|
||||
$def->{CHANGETIME}[3] = $tm;
|
||||
# lost receives
|
||||
$ref->{$txt[6]}{TIME} = $tm;
|
||||
$value = "$l $sfx[6]";
|
||||
$ref->{$txt[6]}{VAL} = $value;
|
||||
$def->{CHANGED}[4] = "$txt[6]: $value";
|
||||
$def->{CHANGETIME}[4] = $tm;
|
||||
|
||||
Log $ll,"WS300 $def->{NAME}: $val";
|
||||
DoTrigger($def->{NAME},undef);
|
||||
}
|
||||
else
|
||||
{
|
||||
# state
|
||||
$val = "T: $t H: $h W: $wind R: $rain IR: $ir Bat: $b LR: $l";
|
||||
$def->{STATE} = $val;
|
||||
$def->{CHANGED}[0] = $val;
|
||||
$def->{CHANGETIME}[0] = $tm;
|
||||
# temperature
|
||||
$ref->{$txt[0]}{TIME} = $tm;
|
||||
$value = "$t $sfx[0]";
|
||||
$ref->{$txt[0]}{VAL} = $value;
|
||||
$def->{CHANGED}[1] = "$txt[0]: $value";
|
||||
$def->{CHANGETIME}[1] = $tm;
|
||||
# humidity
|
||||
$ref->{$txt[1]}{TIME} = $tm;
|
||||
$value = "$h $sfx[1]";
|
||||
$ref->{$txt[1]}{VAL} = $value;
|
||||
$def->{CHANGED}[2] = "$txt[1]: $value";
|
||||
$def->{CHANGETIME}[2] = $tm;
|
||||
# wind
|
||||
$ref->{$txt[2]}{TIME} = $tm;
|
||||
$value = "$wind $sfx[2]";
|
||||
$ref->{$txt[2]}{VAL} = $value;
|
||||
$def->{CHANGED}[3] = "$txt[2]: $value";
|
||||
$def->{CHANGETIME}[3] = $tm;
|
||||
#rain counter
|
||||
$ref->{$txt[3]}{TIME} = $tm;
|
||||
$value = "$rainc $sfx[3]";
|
||||
$ref->{$txt[3]}{VAL} = $value;
|
||||
$def->{CHANGED}[4] = "$txt[3]: $value";
|
||||
$def->{CHANGETIME}[4] = $tm;
|
||||
# is raining
|
||||
$ref->{$txt[4]}{TIME} = $tm;
|
||||
$value = "$ir $sfx[4]";
|
||||
$ref->{$txt[4]}{VAL} = $value;
|
||||
$def->{CHANGED}[5] = "$txt[4]: $value";
|
||||
$def->{CHANGETIME}[5] = $tm;
|
||||
# battery
|
||||
$ref->{$txt[5]}{TIME} = $tm;
|
||||
$value = "$b $sfx[5]";
|
||||
$ref->{$txt[5]}{VAL} = $value;
|
||||
$def->{CHANGED}[6] = "$txt[5]: $value";
|
||||
$def->{CHANGETIME}[6] = $tm;
|
||||
# lost receives
|
||||
$ref->{$txt[6]}{TIME} = $tm;
|
||||
$value = "$l $sfx[6]";
|
||||
$ref->{$txt[6]}{VAL} = $value;
|
||||
$def->{CHANGED}[7] = "$txt[6]: $value";
|
||||
$def->{CHANGETIME}[7] = $tm;
|
||||
# rain cumulative
|
||||
$ref->{$txt[8]}{TIME} = $tm;
|
||||
$value = "$rain $sfx[8]";
|
||||
$ref->{$txt[8]}{VAL} = $value;
|
||||
$def->{CHANGED}[8] = "$txt[8]: $value";
|
||||
$def->{CHANGETIME}[8] = $tm;
|
||||
# statistics
|
||||
if($actday == 99)
|
||||
{
|
||||
$oldrain = $rain;
|
||||
$acthour = $ref->{acthour}{VAL} if(defined($ref->{acthour}{VAL}));
|
||||
$actday = $ref->{actday}{VAL} if(defined($ref->{actday}{VAL}));
|
||||
$actmonth = $ref->{actmonth}{VAL} if(defined($ref->{actmonth}{VAL}));
|
||||
$rain_day = $ref->{rain_day}{VAL} if(defined($ref->{rain_day}{VAL}));
|
||||
$rain_month = $ref->{rain_month}{VAL} if(defined($ref->{rain_month}{VAL}));
|
||||
$rain_hour = $ref->{rain_hour}{VAL} if(defined($ref->{rain_hour}{VAL}));
|
||||
}
|
||||
if($acthour != $lt[2])
|
||||
{
|
||||
$acthour = $lt[2];
|
||||
$rain_hour = sprintf("%.1f",$rain_hour);
|
||||
$rain_day = sprintf("%.1f",$rain_day);
|
||||
$rain_month = sprintf("%.1f",$rain_month);
|
||||
$ref->{acthour}{TIME} = $tm;
|
||||
$ref->{acthour}{VAL} = "$acthour";
|
||||
$ref->{$txt[9]}{TIME} = $tm;
|
||||
$ref->{$txt[9]}{VAL} = $rain_hour;
|
||||
$def->{CHANGED}[9] = "$txt[9]: $rain_hour $sfx[9]";
|
||||
$def->{CHANGETIME}[9] = $tm;
|
||||
$ref->{$txt[10]}{TIME} = $tm;
|
||||
$ref->{$txt[10]}{VAL} = $rain_day;
|
||||
$def->{CHANGED}[10] = "$txt[10]: $rain_day $sfx[10]";
|
||||
$def->{CHANGETIME}[10] = $tm;
|
||||
$ref->{$txt[11]}{TIME} = $tm;
|
||||
$ref->{$txt[11]}{VAL} = $rain_month;
|
||||
$def->{CHANGED}[11] = "$txt[11]: $rain_month $sfx[11]";
|
||||
$def->{CHANGETIME}[11] = $tm;
|
||||
$rain_hour=0;
|
||||
}
|
||||
if($actday != $lt[3])
|
||||
{
|
||||
$actday = $lt[3];
|
||||
$ref->{actday}{TIME} = $tm;
|
||||
$ref->{actday}{VAL} = "$actday";
|
||||
$rain_day=0;
|
||||
}
|
||||
if($actmonth != $lt[4]+1)
|
||||
{
|
||||
$actmonth = $lt[4]+1;
|
||||
$ref->{actmonth}{TIME} = $tm;
|
||||
$ref->{actmonth}{VAL} = "$actmonth";
|
||||
$rain_month=0;
|
||||
}
|
||||
if($rain != $oldrain)
|
||||
{
|
||||
$rain_hour += ($rain-$oldrain);
|
||||
$rain_hour = sprintf("%.1f",$rain_hour);
|
||||
$rain_day += ($rain-$oldrain);
|
||||
$rain_day = sprintf("%.1f",$rain_day);
|
||||
$rain_month += ($rain-$oldrain);
|
||||
$rain_month = sprintf("%.1f",$rain_month);
|
||||
$oldrain = $rain;
|
||||
|
||||
$ref->{acthour}{TIME} = $tm;
|
||||
$ref->{acthour}{VAL} = "$acthour";
|
||||
$ref->{$txt[9]}{TIME} = $tm;
|
||||
$ref->{$txt[9]}{VAL} = $rain_hour;
|
||||
$def->{CHANGED}[9] = "$txt[9]: $rain_hour $sfx[9]";
|
||||
$def->{CHANGETIME}[9] = $tm;
|
||||
$ref->{$txt[10]}{TIME} = $tm;
|
||||
$ref->{$txt[10]}{VAL} = $rain_day;
|
||||
$def->{CHANGED}[10] = "$txt[10]: $rain_day $sfx[10]";
|
||||
$def->{CHANGETIME}[10] = $tm;
|
||||
$ref->{$txt[11]}{TIME} = $tm;
|
||||
$ref->{$txt[11]}{VAL} = $rain_month;
|
||||
$def->{CHANGED}[11] = "$txt[11]: $rain_month $sfx[11]";
|
||||
$def->{CHANGETIME}[11] = $tm;
|
||||
}
|
||||
Log $ll,"WS300 $def->{NAME}: $val";
|
||||
DoTrigger($def->{NAME},undef);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if(!defined($defptr{9}))
|
||||
{
|
||||
Log(3,"WS300 9: undefined");
|
||||
}
|
||||
else
|
||||
{
|
||||
$def = $defptr{9};
|
||||
$def->{READINGS}{$txt[0]}{VAL} = 0 if(!$def->{READINGS});
|
||||
$ref = $def->{READINGS};
|
||||
|
||||
$t = hex($a[62+$offs].$a[63+$offs].$a[64+$offs].$a[65+$offs]);
|
||||
$t -= 65535 if( $t > 32767 );
|
||||
$t /= 10.0;
|
||||
$h = hex($a[66+$offs].$a[67+$offs]);
|
||||
# state
|
||||
$val = "T: $t H: $h P: $press Willi: $willi";
|
||||
$def->{STATE} = $val;
|
||||
$def->{CHANGED}[0] = $val;
|
||||
$def->{CHANGETIME}[0] = $tm;
|
||||
# temperature
|
||||
$ref->{$txt[0]}{TIME} = $tm;
|
||||
$value = "$t $sfx[0]";
|
||||
$ref->{$txt[0]}{VAL} = $value;
|
||||
$def->{CHANGED}[1] = "$txt[0]: $value";
|
||||
$def->{CHANGETIME}[1] = $tm;
|
||||
# humidity
|
||||
$ref->{$txt[1]}{TIME} = $tm;
|
||||
$value = "$h $sfx[1]";
|
||||
$ref->{$txt[1]}{VAL} = $value;
|
||||
$def->{CHANGED}[2] = "$txt[1]: $value";
|
||||
$def->{CHANGETIME}[2] = $tm;
|
||||
# pressure
|
||||
$ref->{$txt[7]}{TIME} = $tm;
|
||||
$value = "$press $sfx[7]";
|
||||
$ref->{$txt[7]}{VAL} = $value;
|
||||
$def->{CHANGED}[3] = "$txt[7]: $value";
|
||||
$def->{CHANGETIME}[3] = $tm;
|
||||
# willi
|
||||
$ref->{willi}{TIME} = $tm;
|
||||
$value = "$willi";
|
||||
$ref->{willi}{VAL} = $value;
|
||||
$def->{CHANGED}[4] = "willi: $value";
|
||||
$def->{CHANGETIME}[4] = $tm;
|
||||
|
||||
Log $ll,"WS300 $def->{NAME}: $val";
|
||||
DoTrigger($def->{NAME},undef);
|
||||
}
|
||||
return undef;
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub
|
||||
WS300_Read($)
|
||||
{
|
||||
my ($hash) = @_;
|
||||
}
|
||||
#####################################
|
||||
sub
|
||||
WS300_Write($$$)
|
||||
{
|
||||
my ($hash,$fn,$msg) = @_;
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub
|
||||
WS300_Poll($)
|
||||
{
|
||||
my $hash = shift;
|
||||
my $bstring=" ";
|
||||
my $count;
|
||||
my $po;
|
||||
my $inchar='';
|
||||
my $escape=0;
|
||||
my $ll = GetLogLevel("WS300Device");
|
||||
$ll = 5 if($ll == 2);
|
||||
|
||||
if(!$hash || !defined($hash->{PortObj}))
|
||||
{
|
||||
return;
|
||||
}
|
||||
return if($polling);
|
||||
$polling=1;
|
||||
NEXTPOLL:
|
||||
$inbuf = $hash->{PortObj}->input();
|
||||
$bstring = sprintf("%c%c%c",0xfe,$cmd,0xfc);
|
||||
|
||||
my $ret = $hash->{PortObj}->write($bstring);
|
||||
if($ret <= 0)
|
||||
{
|
||||
|
||||
my $devname = $hash->{DeviceName};
|
||||
Log 1, "USB device $devname disconnected, waiting to reappear";
|
||||
$hash->{PortObj}->close();
|
||||
$hash->{STATE} = "disconnected";
|
||||
$hash->{READINGS}{WS300Device}{VAL} = "disconnected";
|
||||
$hash->{READINGS}{WS300Device}{TIME} = TimeNow;
|
||||
sleep(1);
|
||||
if ($^O=~/Win/) {
|
||||
$po = new Win32::SerialPort ($devname);
|
||||
}else{
|
||||
$po = new Device::SerialPort ($devname);
|
||||
}
|
||||
if($po)
|
||||
{
|
||||
$po->reset_error();
|
||||
$po->baudrate(19200);
|
||||
$po->databits(8);
|
||||
$po->parity('even');
|
||||
$po->stopbits(1);
|
||||
$po->handshake('none');
|
||||
$po->rts_active(1);
|
||||
$po->dtr_active(1);
|
||||
sleep(1);
|
||||
$po->rts_active(0);
|
||||
$po->write_settings;
|
||||
Log 1, "USB device $devname reappeared";
|
||||
$hash->{PortObj} = $po;
|
||||
$hash->{STATE} = "opened";
|
||||
$hash->{READINGS}{WS300Device}{VAL} = "opened";
|
||||
$hash->{READINGS}{WS300Device}{TIME} = TimeNow;
|
||||
$polling=0;
|
||||
return;
|
||||
}
|
||||
}
|
||||
$inbuf = "";
|
||||
my $start=0;
|
||||
my $tout=time();
|
||||
my $rcount=0;
|
||||
my $ic=0;
|
||||
|
||||
for(;;)
|
||||
{
|
||||
($count,$inchar) = $hash->{PortObj}->read(1);
|
||||
if($count == 0)
|
||||
{
|
||||
last if($tout < time());
|
||||
}
|
||||
else
|
||||
{
|
||||
$ic = hex(unpack('H*',$inchar));
|
||||
if(!$start)
|
||||
{
|
||||
if($ic == 0xfe)
|
||||
{
|
||||
$start = 1;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if($ic == 0xf8)
|
||||
{
|
||||
$escape = 1;
|
||||
$count = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
if($escape)
|
||||
{
|
||||
$ic--;
|
||||
$inbuf .= chr($ic);
|
||||
$escape = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
$inbuf .= $inchar;
|
||||
last if($ic == 0xfc);
|
||||
}
|
||||
}
|
||||
}
|
||||
$rcount += $count;
|
||||
$tout=time();
|
||||
}
|
||||
}
|
||||
|
||||
Log($ll,"WS300/RAW: ".$rcount." ".unpack('H*',$inbuf));
|
||||
if($ic != 0xfc)
|
||||
{
|
||||
$errcount++ if($errcount < 10);
|
||||
if($errcount == 10)
|
||||
{
|
||||
$hash->{STATE} = "timeout";
|
||||
$hash->{READINGS}{WS300Device}{VAL} = "timeout";
|
||||
$hash->{READINGS}{WS300Device}{TIME} = TimeNow;
|
||||
$errcount++;
|
||||
}
|
||||
Log 1,"WS300: no data" if($rcount == 0);
|
||||
Log 1,"WS300: wrong data ".unpack('H*',$inbuf) if($rcount > 0);
|
||||
$polling=0;
|
||||
return;
|
||||
}
|
||||
if($hash->{STATE} ne "connected" && $errcount > 10)
|
||||
{
|
||||
$hash->{STATE} = "connected";
|
||||
$hash->{READINGS}{WS300Device}{VAL} = "connected";
|
||||
$hash->{READINGS}{WS300Device}{TIME} = TimeNow;
|
||||
}
|
||||
$errcount = 0;
|
||||
$ic = ord(substr($inbuf,0,1));
|
||||
if($ic == 0x32)
|
||||
{
|
||||
$config = $inbuf if($rcount == 16);
|
||||
$cmd=0x31;
|
||||
goto NEXTPOLL;
|
||||
}
|
||||
if($ic == 0x31)
|
||||
{
|
||||
if($rcount == 42)
|
||||
{
|
||||
WS300_Parse($hash, $inbuf);
|
||||
goto NEXTPOLL;
|
||||
}
|
||||
else
|
||||
{
|
||||
$cmd=0x33;
|
||||
goto NEXTPOLL;
|
||||
}
|
||||
}
|
||||
if($ic == 0x33)
|
||||
{
|
||||
WS300_Parse($hash, $inbuf) if($rcount == 39);
|
||||
$cmd=0x32;
|
||||
}
|
||||
$polling=0;
|
||||
}
|
||||
|
||||
1;
|
|
@ -1,193 +0,0 @@
|
|||
#
|
||||
#
|
||||
# 59_Weather.pm
|
||||
# written by Dr. Boris Neubert 2009-06-01
|
||||
# e-mail: omega at online dot de
|
||||
#
|
||||
##############################################
|
||||
package main;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use Time::HiRes qw(gettimeofday);
|
||||
use Weather::Google;
|
||||
|
||||
#####################################
|
||||
sub Weather_Initialize($) {
|
||||
|
||||
my ($hash) = @_;
|
||||
|
||||
# Provider
|
||||
# $hash->{Clients} = undef;
|
||||
|
||||
# Consumer
|
||||
$hash->{DefFn} = "Weather_Define";
|
||||
$hash->{UndefFn} = "Weather_Undef";
|
||||
$hash->{GetFn} = "Weather_Get";
|
||||
$hash->{AttrList}= "loglevel:0,1,2,3,4,5";
|
||||
|
||||
}
|
||||
|
||||
###################################
|
||||
sub f_to_c($) {
|
||||
|
||||
my ($f)= @_;
|
||||
|
||||
return int(($f-32)*5/9+0.5);
|
||||
}
|
||||
|
||||
###################################
|
||||
sub Weather_UpdateReading($$$$$) {
|
||||
|
||||
my ($hash,$prefix,$key,$tn,$value)= @_;
|
||||
|
||||
return 0 if(!defined($value) || $value eq "");
|
||||
return 0 if($key eq "unit_system");
|
||||
|
||||
if($key eq "temp") {
|
||||
$key= "temp_c";
|
||||
$value= f_to_c($value);
|
||||
} elsif($key eq "low") {
|
||||
$key= "low_c";
|
||||
$value= f_to_c($value);
|
||||
} elsif($key eq "high") {
|
||||
$key= "high_c";
|
||||
$value= f_to_c($value);
|
||||
}
|
||||
|
||||
my $reading= $prefix . $key;
|
||||
my $r= $hash->{READINGS};
|
||||
$r->{$reading}{TIME}= $tn;
|
||||
$r->{$reading}{VAL} = $value;
|
||||
Log 5, "Weather $hash->{NAME}: $reading= $value";
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
###################################
|
||||
sub Weather_GetUpdate($)
|
||||
{
|
||||
my ($hash) = @_;
|
||||
|
||||
if(!$hash->{LOCAL}) {
|
||||
InternalTimer(gettimeofday()+$hash->{INTERVAL}, "Weather_GetUpdate", $hash, 1);
|
||||
}
|
||||
|
||||
my $name = $hash->{NAME};
|
||||
|
||||
|
||||
# time
|
||||
my $tn = TimeNow();
|
||||
|
||||
|
||||
# get weather information from Google weather API
|
||||
# see http://search.cpan.org/~possum/Weather-Google-0.03/lib/Weather/Google.pm
|
||||
|
||||
my $location= $hash->{LOCATION};
|
||||
my $WeatherObj= new Weather::Google($location);
|
||||
|
||||
Log 4, "$name: Updating weather information for $location.";
|
||||
|
||||
my $current = $WeatherObj->current_conditions;
|
||||
foreach my $condition ( keys ( %$current ) ) {
|
||||
my $value= $current->{$condition};
|
||||
Weather_UpdateReading($hash,"",$condition,$tn,$value);
|
||||
}
|
||||
|
||||
my $fci= $WeatherObj->forecast_information;
|
||||
foreach my $i ( keys ( %$fci ) ) {
|
||||
my $reading= $i;
|
||||
my $value= $fci->{$i};
|
||||
Weather_UpdateReading($hash,"",$i,$tn,$value);
|
||||
}
|
||||
|
||||
for(my $t= 0; $t<= 3; $t++) {
|
||||
my $fcc= $WeatherObj->forecast_conditions($t);
|
||||
my $prefix= sprintf("fc%d_", $t);
|
||||
foreach my $condition ( keys ( %$fcc ) ) {
|
||||
my $value= $fcc->{$condition};
|
||||
Weather_UpdateReading($hash,$prefix,$condition,$tn,$value);
|
||||
}
|
||||
}
|
||||
|
||||
if(!$hash->{LOCAL}) {
|
||||
DoTrigger($name, undef) if($init_done);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
# Perl Special: { $defs{Weather}{READINGS}{condition}{VAL} }
|
||||
# conditions: Mostly Cloudy, Overcast, Clear, Chance of Rain
|
||||
|
||||
###################################
|
||||
sub Weather_Get($@) {
|
||||
|
||||
my ($hash, @a) = @_;
|
||||
|
||||
return "argument is missing" if(int(@a) != 2);
|
||||
|
||||
$hash->{LOCAL} = 1;
|
||||
Weather_GetUpdate($hash);
|
||||
delete $hash->{LOCAL};
|
||||
|
||||
my $reading= $a[1];
|
||||
my $value;
|
||||
|
||||
if(defined($hash->{READINGS}{$reading})) {
|
||||
$value= $hash->{READINGS}{$reading}{VAL};
|
||||
} else {
|
||||
return "no such reading: $reading";
|
||||
}
|
||||
|
||||
return "$a[0] $reading => $value";
|
||||
}
|
||||
|
||||
|
||||
#####################################
|
||||
sub Weather_Define($$) {
|
||||
|
||||
my ($hash, $def) = @_;
|
||||
|
||||
# define <name> Weather <location> [interval]
|
||||
# define MyWeather Weather "Maintal,HE" 3600
|
||||
|
||||
my @a = split("[ \t][ \t]*", $def);
|
||||
|
||||
return "syntax: define <name> Weather <location> [interval]"
|
||||
if(int(@a) < 3 && int(@a) > 4);
|
||||
|
||||
$hash->{STATE} = "Initialized";
|
||||
|
||||
my $name = $a[0];
|
||||
my $location = $a[2];
|
||||
my $interval = 3600;
|
||||
if(int(@a)==4) { $interval= $a[3]; }
|
||||
|
||||
$hash->{LOCATION} = $location;
|
||||
$hash->{INTERVAL} = $interval;
|
||||
$hash->{READINGS}{current_date_time}{TIME}= TimeNow();
|
||||
$hash->{READINGS}{current_date_time}{VAL}= "none";
|
||||
|
||||
$hash->{LOCAL} = 1;
|
||||
Weather_GetUpdate($hash);
|
||||
delete $hash->{LOCAL};
|
||||
|
||||
InternalTimer(gettimeofday()+$hash->{INTERVAL}, "Weather_GetUpdate", $hash, 0);
|
||||
|
||||
return undef;
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub Weather_Undef($$) {
|
||||
|
||||
my ($hash, $arg) = @_;
|
||||
|
||||
RemoveInternalTimer($hash);
|
||||
return undef;
|
||||
}
|
||||
|
||||
#####################################
|
||||
|
||||
|
||||
1;
|
|
@ -1,426 +0,0 @@
|
|||
##############################################
|
||||
package main;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
|
||||
sub EM_Write($$);
|
||||
sub EmCrc($$);
|
||||
sub EmCrcCheck($$);
|
||||
sub EmEsc($);
|
||||
sub EmGetData($$);
|
||||
sub EmMakeMsg($);
|
||||
sub EM_Set($@);
|
||||
|
||||
# Following one-byte commands are trange, as they cause a timeout:
|
||||
# 124 127 150 153 155 156
|
||||
|
||||
|
||||
#####################################
|
||||
sub
|
||||
EM_Initialize($)
|
||||
{
|
||||
my ($hash) = @_;
|
||||
|
||||
|
||||
# Provider
|
||||
$hash->{WriteFn} = "EM_Write";
|
||||
$hash->{Clients} = ":EMWZ:EMEM:EMGZ:";
|
||||
|
||||
# Consumer
|
||||
$hash->{DefFn} = "EM_Define";
|
||||
$hash->{UndefFn} = "EM_Undef";
|
||||
$hash->{GetFn} = "EM_Get";
|
||||
$hash->{SetFn} = "EM_Set";
|
||||
$hash->{AttrList}= "model:em1010pc dummy:1,0 loglevel:0,1,2,3,4,5,6";
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub
|
||||
EM_Define($$)
|
||||
{
|
||||
my ($hash, $def) = @_;
|
||||
my @a = split("[ \t][ \t]*", $def);
|
||||
my $po;
|
||||
$hash->{STATE} = "Initialized";
|
||||
|
||||
my $name = $a[0];
|
||||
my $dev = $a[2];
|
||||
|
||||
$attr{$name}{savefirst} = 1;
|
||||
|
||||
if($dev eq "none") {
|
||||
Log 1, "EM device is none, commands will be echoed only";
|
||||
$attr{$name}{dummy} = 1;
|
||||
return undef;
|
||||
}
|
||||
|
||||
Log 3, "EM opening device $dev";
|
||||
if ( $^O =~ /Win/) {
|
||||
eval ("use Win32::SerialPort;");
|
||||
$po = new Win32::SerialPort ($dev);
|
||||
}else{
|
||||
eval ("use Device::SerialPort;");
|
||||
$po = new Device::SerialPort ($dev);
|
||||
}
|
||||
|
||||
return "Can't open $dev: $!" if(!$po);
|
||||
Log 3, "EM opened device $dev";
|
||||
$po->close();
|
||||
|
||||
$hash->{DeviceName} = $dev;
|
||||
return undef;
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub
|
||||
EM_Undef($$)
|
||||
{
|
||||
my ($hash, $arg) = @_;
|
||||
my $name = $hash->{NAME};
|
||||
|
||||
foreach my $d (sort keys %defs) {
|
||||
if(defined($defs{$d}) &&
|
||||
defined($defs{$d}{IODev}) &&
|
||||
$defs{$d}{IODev} == $hash)
|
||||
{
|
||||
my $lev = ($reread_active ? 4 : 2);
|
||||
Log GetLogLevel($name, $lev), "deleting port for $d";
|
||||
delete $defs{$d}{IODev};
|
||||
}
|
||||
}
|
||||
return undef;
|
||||
}
|
||||
|
||||
|
||||
#####################################
|
||||
sub
|
||||
EM_Set($@)
|
||||
{
|
||||
my ($hash, @a) = @_;
|
||||
my $u1 = "Usage: set <name> reset\n" .
|
||||
" set <name> time [YYYY-MM-DD HH:MM:SS]";
|
||||
|
||||
return $u1 if(int(@a) < 2);
|
||||
my $name = $hash->{DeviceName};
|
||||
|
||||
if($a[1] eq "time") {
|
||||
|
||||
if (int(@a) == 2) {
|
||||
my @lt = localtime;
|
||||
$a[2] = sprintf ("%04d-%02d-%02d", $lt[5]+1900, $lt[4]+1, $lt[3]);
|
||||
$a[3] = sprintf ("%02d:%02d:%02d", $lt[2], $lt[1], $lt[0]);
|
||||
} elsif (int(@a) != 4) {
|
||||
return $u1;
|
||||
}
|
||||
my @d = split("-", $a[2]);
|
||||
my @t = split(":", $a[3]);
|
||||
my $msg = sprintf("73%02x%02x%02x00%02x%02x%02x",
|
||||
$d[2],$d[1],$d[0]-2000+0xd0, $t[0],$t[1],$t[2]);
|
||||
my $d = EmGetData($name, $msg);
|
||||
return "Read error" if(!defined($d));
|
||||
return b($d,0);
|
||||
|
||||
} elsif($a[1] eq "reset") {
|
||||
|
||||
my $d = EmGetData($name, "4545"); # Reset
|
||||
return "Read error" if(!defined($d));
|
||||
sleep(10);
|
||||
EM_Set($hash, ($a[0], "time")); # Set the time
|
||||
sleep(1);
|
||||
$d = EmGetData($name, "67"); # "Push the button", we don't want usesr interaction
|
||||
return "Read error" if(!defined($d));
|
||||
|
||||
} else {
|
||||
|
||||
return "Unknown argument $a[1], choose one of reset time"
|
||||
|
||||
}
|
||||
return undef;
|
||||
|
||||
}
|
||||
|
||||
#########################
|
||||
sub
|
||||
b($$)
|
||||
{
|
||||
my ($t,$p) = @_;
|
||||
return -1 if(!defined($t) || length($t) < $p);
|
||||
return ord(substr($t,$p,1));
|
||||
}
|
||||
|
||||
sub
|
||||
w($$)
|
||||
{
|
||||
my ($t,$p) = @_;
|
||||
return b($t,$p+1)*256 + b($t,$p);
|
||||
}
|
||||
|
||||
sub
|
||||
dw($$)
|
||||
{
|
||||
my ($t,$p) = @_;
|
||||
return w($t,$p+2)*65536 + w($t,$p);
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub
|
||||
EM_Get($@)
|
||||
{
|
||||
my ($hash, @a) = @_;
|
||||
|
||||
return "\"get EM\" needs only one parameter" if(@a != 2);
|
||||
|
||||
my $v;
|
||||
if($a[1] eq "time") {
|
||||
|
||||
my $d = EmGetData($hash->{DeviceName}, "74");
|
||||
return "Read error" if(!defined($d));
|
||||
|
||||
$v = sprintf "%4d-%02d-%02d %02d:%02d:%02d",
|
||||
b($d,5)+2006, b($d,4), b($d,3),
|
||||
b($d,0), b($d,1), b($d,2);
|
||||
|
||||
} elsif($a[1] eq "version") {
|
||||
|
||||
my $d = EmGetData($hash->{DeviceName},"76");
|
||||
return "Read error" if(!defined($d));
|
||||
$v = sprintf "%d.%d", b($d,0), b($d,1);
|
||||
|
||||
} else {
|
||||
return "Unknown argument $a[1], choose one of time,version";
|
||||
}
|
||||
|
||||
$hash->{READINGS}{$a[1]}{VAL} = $v;
|
||||
$hash->{READINGS}{$a[1]}{TIME} = TimeNow();
|
||||
|
||||
return "$a[0] $a[1] => $v";
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub
|
||||
EM_Write($$)
|
||||
{
|
||||
my ($hash,$msg) = @_;
|
||||
|
||||
return EmGetData($hash->{DeviceName}, $msg);
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub
|
||||
EmCrc($$)
|
||||
{
|
||||
my ($in, $val) = @_;
|
||||
my ($crc, $bits) = (0, 8);
|
||||
my $k = (($in >> 8) ^ $val) << 8;
|
||||
while($bits--) {
|
||||
if(($crc ^ $k) & 0x8000) {
|
||||
$crc = ($crc << 1) ^ 0x8005;
|
||||
} else {
|
||||
$crc <<= 1;
|
||||
}
|
||||
$k <<= 1;
|
||||
}
|
||||
return (($in << 8) ^ $crc) & 0xffff;
|
||||
}
|
||||
|
||||
#########################
|
||||
sub
|
||||
EmEsc($)
|
||||
{
|
||||
my ($b) = @_;
|
||||
|
||||
my $out = "";
|
||||
$out .= chr(0x10) if($b==0x02 || $b==0x03 || $b==0x10);
|
||||
$out .= chr($b);
|
||||
}
|
||||
|
||||
|
||||
#####################################
|
||||
sub
|
||||
EmCrcCheck($$)
|
||||
{
|
||||
my ($otxt, $len) = @_;
|
||||
my $crc = 0x8c27;
|
||||
for(my $l = 2; $l < $len+4; $l++) {
|
||||
my $b = ord(substr($otxt,$l,1));
|
||||
$crc = EmCrc($crc, 0x10) if($b==0x02 || $b==0x03 || $b==0x10);
|
||||
$crc = EmCrc($crc, $b);
|
||||
}
|
||||
return ($crc == w($otxt, $len+4));
|
||||
}
|
||||
|
||||
#########################
|
||||
sub
|
||||
EmMakeMsg($)
|
||||
{
|
||||
my ($data) = @_;
|
||||
my $len = length($data);
|
||||
$data = chr($len&0xff) . chr(int($len/256)) . $data;
|
||||
|
||||
my $out = pack('H*', "0200");
|
||||
my $crc = 0x8c27;
|
||||
for(my $l = 0; $l < $len+2; $l++) {
|
||||
my $b = ord(substr($data,$l,1));
|
||||
$crc = EmCrc($crc, 0x10) if($b==0x02 || $b==0x03 || $b==0x10);
|
||||
$crc = EmCrc($crc, $b);
|
||||
$out .= EmEsc($b);
|
||||
}
|
||||
$out .= EmEsc($crc&0xff);
|
||||
$out .= EmEsc($crc/256);
|
||||
$out .= chr(0x03);
|
||||
return $out;
|
||||
}
|
||||
|
||||
#####################################
|
||||
# This is the only
|
||||
sub
|
||||
EmGetData($$)
|
||||
{
|
||||
my ($dev, $d) = @_;
|
||||
$d = EmMakeMsg(pack('H*', $d));
|
||||
my $serport;
|
||||
my $rm;
|
||||
return undef if(!$dev);
|
||||
#OS depends
|
||||
if ($^O=~/Win/) {
|
||||
$serport = new Win32::SerialPort ($dev);
|
||||
}else{
|
||||
$serport = new Device::SerialPort ($dev);
|
||||
}
|
||||
|
||||
if(!$serport) {
|
||||
Log 1, "EM: Can't open $dev: $!";
|
||||
return undef;
|
||||
}
|
||||
$serport->reset_error();
|
||||
$serport->baudrate(38400);
|
||||
$serport->databits(8);
|
||||
$serport->parity('none');
|
||||
$serport->stopbits(1);
|
||||
$serport->handshake('none');
|
||||
if ( $^O =~ /Win/ ) {
|
||||
unless ($serport->write_settings) {
|
||||
$rm= "EM:Can't change Device_Control_Block: $^E\n";
|
||||
goto DONE;
|
||||
}
|
||||
}
|
||||
Log 4, "EM: Sending " . unpack('H*', $d);
|
||||
|
||||
$rm = "EM: timeout reading the answer";
|
||||
for(my $rep = 0; $rep < 3; $rep++) {
|
||||
|
||||
$serport->write($d);
|
||||
|
||||
|
||||
my $retval = "";
|
||||
my $esc = 0;
|
||||
my $started = 0;
|
||||
my $complete = 0;
|
||||
my $buf;
|
||||
my $i;
|
||||
my $b;
|
||||
for(;;) {
|
||||
|
||||
if($^O =~ /Win/) {
|
||||
#select will not work on windows, replaced with status
|
||||
my ($BlockingFlags, $InBytes, $OutBytes, $ErrorFlags)=(0,0,0,0);
|
||||
for ($i=0;$i<9; $i++) {
|
||||
sleep(1); #waiiiit
|
||||
($BlockingFlags, $InBytes, $OutBytes, $ErrorFlags)=$serport->status;
|
||||
last if $InBytes>0;
|
||||
}
|
||||
Log 5,"EM: read returned $InBytes Bytes($i trys)";
|
||||
last if ($InBytes<1);
|
||||
$buf = $serport->input();
|
||||
|
||||
} else {
|
||||
my ($rout, $rin) = ('', '');
|
||||
vec($rin, $serport->FILENO, 1) = 1;
|
||||
my $nfound = select($rout=$rin, undef, undef, 1.0);
|
||||
|
||||
if($nfound < 0) {
|
||||
$rm = "EM Select error $nfound / $!";
|
||||
goto DONE;
|
||||
}
|
||||
last if($nfound == 0);
|
||||
$buf = $serport->input();
|
||||
}
|
||||
|
||||
|
||||
if(!defined($buf) || length($buf) == 0) {
|
||||
$rm = "EM EOF on $dev";
|
||||
goto DONE;
|
||||
}
|
||||
|
||||
for($i = 0; $i < length($buf); $i++) {
|
||||
$b = ord(substr($buf,$i,1));
|
||||
|
||||
if(!$started && $b != 0x02) { next; }
|
||||
$started = 1;
|
||||
if($esc) { $retval .= chr($b); $esc = 0; next; }
|
||||
if($b == 0x10) { $esc = 1; next; }
|
||||
$retval .= chr($b);
|
||||
if($b == 0x03) { $complete = 1; last; }
|
||||
}
|
||||
|
||||
if($complete) {
|
||||
my $l = length($retval);
|
||||
if($l < 8) { $rm = "EM Msg too short"; goto DONE; }
|
||||
if(b($retval,1) != 0) { $rm = "EM Bad second byte"; goto DONE; }
|
||||
if(w($retval,2) != $l-7) { $rm = "EM Length mismatch"; goto DONE; }
|
||||
if(!EmCrcCheck($retval,$l-7)) { $rm = "EM Bad CRC"; goto DONE; }
|
||||
$serport->close();
|
||||
my $data=substr($retval, 4, $l-7);
|
||||
Log 5,"EM: returned ".unpack("H*",$data);
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DONE:
|
||||
Log 5,$rm;
|
||||
$serport->close();
|
||||
return undef;
|
||||
}
|
||||
|
||||
|
||||
#########################
|
||||
# Interpretation is left for the "user";
|
||||
sub
|
||||
EmGetDevData($)
|
||||
{
|
||||
my ($hash) = @_;
|
||||
|
||||
my $dnr = $hash->{DEVNR};
|
||||
my $d = IOWrite($hash, sprintf("7a%02x", $dnr-1));
|
||||
|
||||
return("ERROR: No device no. $dnr present")
|
||||
if($d eq ((pack('H*',"00") x 45) . pack('H*',"FF") x 6));
|
||||
|
||||
my $nrreadings = w($d,2);
|
||||
return("ERROR: No data to read (yet?)")
|
||||
if($nrreadings == 0);
|
||||
|
||||
my $step = b($d,6);
|
||||
my $start = b($d,18)+13;
|
||||
my $end = $start + int(($nrreadings-1)/64)*$step;
|
||||
|
||||
my @ret;
|
||||
my $max;
|
||||
my $off;
|
||||
for(my $p = $start; $p <= $end; $p += $step) { # blockwise
|
||||
$d = IOWrite($hash, sprintf("52%02x%02x00000801", $p%256, int($p/256)));
|
||||
$max = (($p == $end) ? ($nrreadings%64)*4+4 : 260);
|
||||
$step = b($d, 6);
|
||||
|
||||
for($off = 8; $off <= $max; $off += 4) { # Samples in each block
|
||||
push(@ret, sprintf("%04x%04x\n", w($d,$off), w($d,$off+2)));
|
||||
}
|
||||
}
|
||||
return @ret;
|
||||
}
|
||||
|
||||
|
||||
1;
|
|
@ -1,184 +0,0 @@
|
|||
##############################################
|
||||
package main;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use Time::HiRes qw(gettimeofday);
|
||||
|
||||
sub EMWZ_Get($@);
|
||||
sub EMWZ_Set($@);
|
||||
sub EMWZ_Define($$);
|
||||
sub EMWZ_GetStatus($);
|
||||
|
||||
###################################
|
||||
sub
|
||||
EMWZ_Initialize($)
|
||||
{
|
||||
my ($hash) = @_;
|
||||
|
||||
$hash->{GetFn} = "EMWZ_Get";
|
||||
$hash->{SetFn} = "EMWZ_Set";
|
||||
$hash->{DefFn} = "EMWZ_Define";
|
||||
|
||||
$hash->{AttrList} = "IODev dummy:1,0 model;EM1000WZ loglevel:0,1,2,3,4,5,6";
|
||||
}
|
||||
|
||||
|
||||
###################################
|
||||
sub
|
||||
EMWZ_GetStatus($)
|
||||
{
|
||||
my ($hash) = @_;
|
||||
|
||||
if(!$hash->{LOCAL}) {
|
||||
InternalTimer(gettimeofday()+300, "EMWZ_GetStatus", $hash, 0);
|
||||
}
|
||||
|
||||
my $dnr = $hash->{DEVNR};
|
||||
my $name = $hash->{NAME};
|
||||
|
||||
return "Empty status: dummy IO device" if(IsIoDummy($name));
|
||||
|
||||
my $d = IOWrite($hash, sprintf("7a%02x", $dnr-1));
|
||||
if(!defined($d)) {
|
||||
my $msg = "EMWZ $name read error (GetStatus 1)";
|
||||
Log GetLogLevel($name,2), $msg;
|
||||
return $msg;
|
||||
}
|
||||
|
||||
|
||||
if($d eq ((pack('H*',"00") x 45) . pack('H*',"FF") x 6)) {
|
||||
my $msg = "EMWZ no device no. $dnr present";
|
||||
Log GetLogLevel($name,2), $msg;
|
||||
return $msg;
|
||||
}
|
||||
|
||||
my $pulses=w($d,13);
|
||||
my $ec=w($d,49) / 10;
|
||||
if($ec <= 0) {
|
||||
my $msg = "EMWZ read error (GetStatus 2)";
|
||||
Log GetLogLevel($name,2), $msg;
|
||||
return $msg;
|
||||
}
|
||||
my $cur_energy = $pulses / $ec; # ec = U/kWh
|
||||
my $cur_power = $cur_energy / 5 * 60; # 5minute interval scaled to 1h
|
||||
|
||||
if($cur_power > 30) { # 20Amp x 3 Phase
|
||||
my $msg = "EMWZ Bogus reading: curr. power is reported to be $cur_power";
|
||||
Log GetLogLevel($name,2), $msg;
|
||||
return $msg;
|
||||
}
|
||||
|
||||
my %vals;
|
||||
$vals{"5min_pulses"} = $pulses;
|
||||
$vals{"energy"} = sprintf("%0.3f", $cur_energy);
|
||||
$vals{"power"} = sprintf("%.3f", $cur_power);
|
||||
$vals{"alarm_PA"} = w($d,45) . " Watt";
|
||||
$vals{"price_CF"} = sprintf("%.3f", w($d,47)/10000);
|
||||
$vals{"RperKW_EC"} = $ec;
|
||||
$hash->{READINGS}{cum_kWh}{VAL} = 0 if(!$hash->{READINGS}{cum_kWh}{VAL});
|
||||
$vals{"cum_kWh"} = sprintf("%0.3f",
|
||||
$hash->{READINGS}{cum_kWh}{VAL} + $vals{"energy"});
|
||||
$vals{summary} = sprintf("Pulses: %s Energy: %s Power: %s Cum: %s",
|
||||
$vals{"5min_pulses"}, $vals{energy},
|
||||
$vals{power}, $vals{cum_kWh});
|
||||
|
||||
|
||||
my $tn = TimeNow();
|
||||
my $idx = 0;
|
||||
foreach my $k (keys %vals) {
|
||||
my $v = $vals{$k};
|
||||
$hash->{CHANGED}[$idx++] = "$k: $v";
|
||||
$hash->{READINGS}{$k}{TIME} = $tn;
|
||||
$hash->{READINGS}{$k}{VAL} = $v
|
||||
}
|
||||
|
||||
if(!$hash->{LOCAL}) {
|
||||
DoTrigger($name, undef) if($init_done);
|
||||
}
|
||||
|
||||
$hash->{STATE} = "$cur_power kW";
|
||||
Log GetLogLevel($name,4), "EMWZ $name: $cur_power kW / $vals{energy}";
|
||||
|
||||
return $hash->{STATE};
|
||||
}
|
||||
|
||||
###################################
|
||||
sub
|
||||
EMWZ_Get($@)
|
||||
{
|
||||
my ($hash, @a) = @_;
|
||||
|
||||
return "argument is missing" if(int(@a) != 2);
|
||||
|
||||
my $d = $hash->{DEVNR};
|
||||
my $msg;
|
||||
|
||||
if($a[1] ne "status") {
|
||||
return "unknown get value, valid is status";
|
||||
}
|
||||
$hash->{LOCAL} = 1;
|
||||
my $v = EMWZ_GetStatus($hash);
|
||||
delete $hash->{LOCAL};
|
||||
|
||||
return "$a[0] $a[1] => $v";
|
||||
}
|
||||
|
||||
sub
|
||||
EMWZ_Set($@)
|
||||
{
|
||||
my ($hash, @a) = @_;
|
||||
|
||||
my $name = $hash->{NAME};
|
||||
|
||||
my $v = $a[2];
|
||||
my $d = $hash->{DEVNR};
|
||||
my $msg;
|
||||
|
||||
if($a[1] eq "price" && int(@a) == 3) {
|
||||
$v *= 10000; # Make display and input the same
|
||||
$msg = sprintf("79%02x2f02%02x%02x", $d-1, $v%256, int($v/256));
|
||||
} elsif($a[1] eq "alarm" && int(@a) == 3) {
|
||||
$msg = sprintf("79%02x2d02%02x%02x", $d-1, $v%256, int($v/256));
|
||||
} elsif($a[1] eq "rperkw" && int(@a) == 3) {
|
||||
$v *= 10; # Make display and input the same
|
||||
$msg = sprintf("79%02x3102%02x%02x", $d-1, $v%256, int($v/256));
|
||||
} else {
|
||||
return "Unknown argument $a[1], choose one of price alarm rperkw";
|
||||
}
|
||||
|
||||
return "" if(IsIoDummy($name));
|
||||
my $ret = IOWrite($hash, $msg);
|
||||
if(!defined($ret)) {
|
||||
my $msg = "EMWZ $name read error (Set)";
|
||||
Log GetLogLevel($name,2), $msg;
|
||||
return $msg;
|
||||
}
|
||||
|
||||
if(ord(substr($ret,0,1)) != 6) {
|
||||
$ret = "EMWZ Error occured: " . unpack('H*', $ret);
|
||||
Log GetLogLevel($name,2), $ret;
|
||||
return $ret;
|
||||
}
|
||||
|
||||
return undef;
|
||||
}
|
||||
|
||||
#############################
|
||||
sub
|
||||
EMWZ_Define($$)
|
||||
{
|
||||
my ($hash, $def) = @_;
|
||||
my @a = split("[ \t][ \t]*", $def);
|
||||
|
||||
return "syntax: define <name> EMWZ devicenumber"
|
||||
if(@a != 3 || $a[2] !~ m,^[1-4]$,);
|
||||
$hash->{DEVNR} = $a[2];
|
||||
AssignIoPort($hash);
|
||||
|
||||
|
||||
EMWZ_GetStatus($hash);
|
||||
return undef;
|
||||
}
|
||||
|
||||
1;
|
|
@ -1,138 +0,0 @@
|
|||
##############################################
|
||||
package main;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use Time::HiRes qw(gettimeofday);
|
||||
|
||||
sub EMEM_Get($@);
|
||||
sub EMEM_Define($$);
|
||||
sub EMEM_GetStatus($);
|
||||
|
||||
###################################
|
||||
sub
|
||||
EMEM_Initialize($)
|
||||
{
|
||||
my ($hash) = @_;
|
||||
|
||||
$hash->{GetFn} = "EMEM_Get";
|
||||
$hash->{DefFn} = "EMEM_Define";
|
||||
|
||||
$hash->{AttrList} = "IODev dummy:1,0 model;EM1000EM loglevel:0,1,2,3,4,5,6";
|
||||
}
|
||||
|
||||
###################################
|
||||
sub
|
||||
EMEM_GetStatus($)
|
||||
{
|
||||
my ($hash) = @_;
|
||||
|
||||
if(!$hash->{LOCAL}) {
|
||||
InternalTimer(gettimeofday()+300, "EMEM_GetStatus", $hash, 0);
|
||||
}
|
||||
|
||||
my $dnr = $hash->{DEVNR};
|
||||
my $name = $hash->{NAME};
|
||||
|
||||
return "Empty status: dummy IO device" if(IsIoDummy($name));
|
||||
|
||||
my $d = IOWrite($hash, sprintf("7a%02x", $dnr-1));
|
||||
if(!defined($d)) {
|
||||
my $msg = "EMEM $name read error (GetStatus 1)";
|
||||
Log GetLogLevel($name,2), $msg;
|
||||
return $msg;
|
||||
}
|
||||
|
||||
if($d eq ((pack('H*',"00") x 45) . pack('H*',"FF") x 6)) {
|
||||
my $msg = "EMEM no device no. $dnr present";
|
||||
Log GetLogLevel($name,2), $msg;
|
||||
return $msg;
|
||||
}
|
||||
|
||||
my $pulses=w($d,13);
|
||||
my $pulses_max= w($d,15);
|
||||
my $iec = 1000;
|
||||
my $cur_power = $pulses / 100;
|
||||
my $cur_power_max = $pulses_max / 100;
|
||||
|
||||
if($cur_power > 30) { # 20Amp x 3 Phase
|
||||
my $msg = "EMEM Bogus reading: curr. power is reported to be $cur_power, setting to -1";
|
||||
Log GetLogLevel($name,2), $msg;
|
||||
#return $msg;
|
||||
$cur_power = -1.0;
|
||||
}
|
||||
if($cur_power_max > 30) { # 20Amp x 3 Phase
|
||||
$cur_power_max = -1.0;
|
||||
}
|
||||
|
||||
my %vals;
|
||||
$vals{"5min_pulses"} = $pulses;
|
||||
$vals{"5min_pulses_max"} = $pulses_max;
|
||||
$vals{"energy_kWh_h"} = sprintf("%0.3f", dw($d,33) / $iec);
|
||||
$vals{"energy_kWh_d"} = sprintf("%0.3f", dw($d,37) / $iec);
|
||||
$vals{"energy_kWh_w"} = sprintf("%0.3f", dw($d,41) / $iec);
|
||||
$vals{"energy_kWh"} = sprintf("%0.3f", dw($d, 7) / $iec);
|
||||
$vals{"power_kW"} = sprintf("%.3f", $cur_power);
|
||||
$vals{"power_kW_max"} = sprintf("%.3f", $cur_power_max);
|
||||
$vals{"alarm_PA_W"} = w($d,45);
|
||||
$vals{"price_CF"} = sprintf("%.3f", w($d,47)/10000);
|
||||
|
||||
|
||||
my $tn = TimeNow();
|
||||
my $idx = 0;
|
||||
foreach my $k (keys %vals) {
|
||||
my $v = $vals{$k};
|
||||
$hash->{CHANGED}[$idx++] = "$k: $v";
|
||||
$hash->{READINGS}{$k}{TIME} = $tn;
|
||||
$hash->{READINGS}{$k}{VAL} = $v
|
||||
}
|
||||
|
||||
if(!$hash->{LOCAL}) {
|
||||
DoTrigger($name, undef) if($init_done);
|
||||
}
|
||||
|
||||
$hash->{STATE} = "$cur_power kW";
|
||||
Log GetLogLevel($name,4), "EMEM $name: $cur_power kW / $vals{energy_kWh} kWh";
|
||||
|
||||
return $hash->{STATE};
|
||||
}
|
||||
|
||||
###################################
|
||||
sub
|
||||
EMEM_Get($@)
|
||||
{
|
||||
my ($hash, @a) = @_;
|
||||
|
||||
return "argument is missing" if(int(@a) != 2);
|
||||
|
||||
my $d = $hash->{DEVNR};
|
||||
my $msg;
|
||||
|
||||
if($a[1] ne "status") {
|
||||
return "unknown get value, valid is status";
|
||||
}
|
||||
$hash->{LOCAL} = 1;
|
||||
my $v = EMEM_GetStatus($hash);
|
||||
delete $hash->{LOCAL};
|
||||
|
||||
return "$a[0] $a[1] => $v";
|
||||
}
|
||||
|
||||
#############################
|
||||
sub
|
||||
EMEM_Define($$)
|
||||
{
|
||||
my ($hash, $def) = @_;
|
||||
my @a = split("[ \t][ \t]*", $def);
|
||||
|
||||
return "syntax: define <name> EMEM devicenumber"
|
||||
if(@a != 3 || $a[2] !~ m,^[5-8]$,);
|
||||
$hash->{DEVNR} = $a[2];
|
||||
AssignIoPort($hash);
|
||||
|
||||
|
||||
EMEM_GetStatus($hash);
|
||||
return undef;
|
||||
}
|
||||
|
||||
1;
|
|
@ -1,171 +0,0 @@
|
|||
##############################################
|
||||
package main;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use Time::HiRes qw(gettimeofday);
|
||||
|
||||
sub EMGZ_Get($@);
|
||||
sub EMGZ_Set($@);
|
||||
sub EMGZ_Define($$);
|
||||
sub EMGZ_GetStatus($);
|
||||
|
||||
###################################
|
||||
sub
|
||||
EMGZ_Initialize($)
|
||||
{
|
||||
my ($hash) = @_;
|
||||
|
||||
$hash->{GetFn} = "EMGZ_Get";
|
||||
$hash->{SetFn} = "EMGZ_Set";
|
||||
$hash->{DefFn} = "EMGZ_Define";
|
||||
|
||||
$hash->{AttrList} = "IODev dummy:1,0 model;EM1000GZ loglevel:0,1,2,3,4,5,6";
|
||||
}
|
||||
|
||||
|
||||
###################################
|
||||
sub
|
||||
EMGZ_GetStatus($)
|
||||
{
|
||||
my ($hash) = @_;
|
||||
|
||||
if(!$hash->{LOCAL}) {
|
||||
InternalTimer(gettimeofday()+300, "EMGZ_GetStatus", $hash, 0);
|
||||
}
|
||||
|
||||
my $dnr = $hash->{DEVNR};
|
||||
my $name = $hash->{NAME};
|
||||
|
||||
return "Empty status: dummy IO device" if(IsIoDummy($name));
|
||||
|
||||
my $d = IOWrite($hash, sprintf("7a%02x", $dnr-1));
|
||||
if(!defined($d)) {
|
||||
my $msg = "EMGZ $name read error (GetStatus 1)";
|
||||
Log GetLogLevel($name,2), $msg;
|
||||
return $msg;
|
||||
}
|
||||
|
||||
|
||||
if($d eq ((pack('H*',"00") x 45) . pack('H*',"FF") x 6)) {
|
||||
my $msg = "EMGZ no device no. $dnr present";
|
||||
Log GetLogLevel($name,2), $msg;
|
||||
return $msg;
|
||||
}
|
||||
|
||||
my $pulses=w($d,13);
|
||||
|
||||
my $ec = 100; # fixed value
|
||||
|
||||
my $cur_energy = $pulses / $ec; # ec = U/m^3
|
||||
my $cur_power = $cur_energy / 5 * 60; # 5minute interval scaled to 1h
|
||||
|
||||
if($cur_power > 30) { # depending on "Anschlussleistung"
|
||||
my $msg = "EMGZ Bogus reading: curr. power is reported to be $cur_power";
|
||||
Log GetLogLevel($name,2), $msg;
|
||||
return $msg;
|
||||
}
|
||||
|
||||
my %vals;
|
||||
$vals{"5min_pulses"} = $pulses;
|
||||
$vals{"act_flow_m3"} = sprintf("%0.3f", $cur_energy);
|
||||
$vals{"m3ph"} = sprintf("%.3f", $cur_power);
|
||||
$vals{"alarm_PA"} = w($d,45) . " Watt"; # nonsens
|
||||
$vals{"price_CF"} = sprintf("%.3f", w($d,47)/10000);
|
||||
$vals{"Rperm3_EC"} = $ec;
|
||||
$hash->{READINGS}{cum_m3}{VAL} = 0 if(!$hash->{READINGS}{cum_m3}{VAL});
|
||||
$vals{"cum_m3"} = sprintf("%0.3f",
|
||||
$hash->{READINGS}{cum_m3}{VAL} + $vals{"act_flow_m3"});
|
||||
|
||||
|
||||
my $tn = TimeNow();
|
||||
my $idx = 0;
|
||||
foreach my $k (keys %vals) {
|
||||
my $v = $vals{$k};
|
||||
$hash->{CHANGED}[$idx++] = "$k: $v";
|
||||
$hash->{READINGS}{$k}{TIME} = $tn;
|
||||
$hash->{READINGS}{$k}{VAL} = $v
|
||||
}
|
||||
|
||||
if(!$hash->{LOCAL}) {
|
||||
DoTrigger($name, undef) if($init_done);
|
||||
}
|
||||
|
||||
$hash->{STATE} = "$cur_power m3ph";
|
||||
Log GetLogLevel($name,4), "EMGZ $name: $cur_power m3ph / $vals{act_flow_m3}";
|
||||
|
||||
return $hash->{STATE};
|
||||
}
|
||||
|
||||
###################################
|
||||
sub
|
||||
EMGZ_Get($@)
|
||||
{
|
||||
my ($hash, @a) = @_;
|
||||
|
||||
my $d = $hash->{DEVNR};
|
||||
my $msg;
|
||||
|
||||
if($a[1] ne "status" && int(@a) != 2) {
|
||||
return "unknown get value, valid is status";
|
||||
}
|
||||
$hash->{LOCAL} = 1;
|
||||
my $v = EMGZ_GetStatus($hash);
|
||||
delete $hash->{LOCAL};
|
||||
|
||||
return "$a[0] $a[1] => $v";
|
||||
}
|
||||
|
||||
sub
|
||||
EMGZ_Set($@)
|
||||
{
|
||||
my ($hash, @a) = @_;
|
||||
my $name = $hash->{NAME};
|
||||
|
||||
my $v = $a[2];
|
||||
my $d = $hash->{DEVNR};
|
||||
my $msg;
|
||||
|
||||
if($a[1] eq "price" && int(@a) != 3) {
|
||||
$v *= 10000; # Make display and input the same
|
||||
$msg = sprintf("79%02x2f02%02x%02x", $d-1, $v%256, int($v/256));
|
||||
} else {
|
||||
return "Unknown argument $a[1], choose one of price";
|
||||
}
|
||||
|
||||
|
||||
return "" if(IsIoDummy($name));
|
||||
my $ret = IOWrite($hash, $msg);
|
||||
if(!defined($ret)) {
|
||||
$msg = "EMWZ $name read error (Set)";
|
||||
Log GetLogLevel($name,2), $msg;
|
||||
return $msg;
|
||||
}
|
||||
|
||||
if(ord(substr($ret,0,1)) != 6) {
|
||||
$ret = "EMGZ Error occured: " . unpack('H*', $ret);
|
||||
Log GetLogLevel($name,2), $ret;
|
||||
return $ret;
|
||||
}
|
||||
|
||||
return undef;
|
||||
}
|
||||
|
||||
#############################
|
||||
sub
|
||||
EMGZ_Define($$)
|
||||
{
|
||||
my ($hash, $def) = @_;
|
||||
my @a = split("[ \t][ \t]*", $def);
|
||||
|
||||
return "syntax: define <name> EMGZ devicenumber"
|
||||
if(@a != 3 || $a[2] !~ m,^[9]$,);
|
||||
$hash->{DEVNR} = $a[2];
|
||||
AssignIoPort($hash);
|
||||
|
||||
|
||||
EMGZ_GetStatus($hash);
|
||||
return undef;
|
||||
}
|
||||
|
||||
1;
|
|
@ -1,273 +0,0 @@
|
|||
##############################################
|
||||
package main;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use Device::SerialPort;
|
||||
|
||||
my %sets = (
|
||||
"cmd" => "",
|
||||
"freq" => "",
|
||||
);
|
||||
|
||||
#####################################
|
||||
sub
|
||||
SCIVT_Initialize($)
|
||||
{
|
||||
my ($hash) = @_;
|
||||
|
||||
# Consumer
|
||||
$hash->{DefFn} = "SCIVT_Define";
|
||||
$hash->{GetFn} = "SCIVT_Get";
|
||||
$hash->{SetFn} = "SCIVT_Set";
|
||||
$hash->{AttrList}= "model:SCD10,SCD20,SCD30 loglevel:0,1,2,3,4,5,6";
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub
|
||||
SCIVT_Define($$)
|
||||
{
|
||||
my ($hash, $def) = @_;
|
||||
my @a = split("[ \t][ \t]*", $def);
|
||||
|
||||
return "Define the serial device as a parameter, use none for a fake device"
|
||||
if(@a != 3);
|
||||
$hash->{STATE} = "Initialized";
|
||||
|
||||
my $dev = $a[2];
|
||||
|
||||
Log 1, "SCIVT device is none, commands will be echoed only"
|
||||
if($dev eq "none");
|
||||
|
||||
if($dev ne "none") {
|
||||
Log 3, "SCIVT opening device $dev";
|
||||
my $po = new Device::SerialPort ($dev);
|
||||
return "SCIVT Can't open $dev: $!" if(!$po);
|
||||
Log 2, "SCIVT opened device $dev";
|
||||
$po->close();
|
||||
}
|
||||
|
||||
$hash->{DeviceName} = $dev;
|
||||
$hash->{Timer} = 900; # call every 15 min
|
||||
$hash->{Cmd} = 'F'; # get all data, min/max unchanged
|
||||
|
||||
my $tn = TimeNow();
|
||||
$hash->{READINGS}{"freq"}{TIME} = $tn;
|
||||
$hash->{READINGS}{"freq"}{VAL} = $hash->{Timer};
|
||||
$hash->{READINGS}{"cmd"}{TIME} = $tn;
|
||||
$hash->{READINGS}{"cmd"}{VAL} = $hash->{Cmd};
|
||||
$hash->{CHANGED}[0] = "freq: $hash->{Timer}";
|
||||
$hash->{CHANGED}[1] = "cmd: $hash->{Cmd}";
|
||||
|
||||
# InternalTimer blocks if init_done is not true
|
||||
my $oid = $init_done;
|
||||
$init_done = 1;
|
||||
SCIVT_GetStatus($hash);
|
||||
$init_done = $oid;
|
||||
return undef;
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub
|
||||
SCIVT_Set($@)
|
||||
{
|
||||
my ($hash, @a) = @_;
|
||||
return "\"set SCIVT\" needs at least two parameter" if(@a < 3);
|
||||
my $name = $hash->{NAME};
|
||||
Log GetLogLevel($name,4), "SCIVT Set request $a[1] $a[2], old: Timer:$hash->{Timer} Cmd: $hash->{Cmd}";
|
||||
|
||||
return "Unknown argument $a[1], choose one of " . join(" ", sort keys %sets)
|
||||
if(!defined($sets{$a[1]}));
|
||||
|
||||
$name = shift @a;
|
||||
my $type = shift @a;
|
||||
my $arg = join("", @a);
|
||||
my $tn = TimeNow();
|
||||
|
||||
if($type eq "freq")
|
||||
{
|
||||
if ($arg > 0)
|
||||
{
|
||||
$hash->{Timer} = $arg * 60;
|
||||
$hash->{READINGS}{$type}{TIME} = $tn;
|
||||
$hash->{READINGS}{$type}{VAL} = $hash->{Timer};
|
||||
$hash->{CHANGED}[0] = "$type: $hash->{Timer}";
|
||||
}
|
||||
}
|
||||
|
||||
if($type eq "cmd")
|
||||
{
|
||||
if ($arg eq "F")
|
||||
{
|
||||
$hash->{Cmd} = 'F'; # F : get all data
|
||||
}
|
||||
if ($arg eq "L") # L : get all data and clear min-/max values
|
||||
{
|
||||
$hash->{Cmd} = 'L';
|
||||
}
|
||||
$hash->{READINGS}{$type}{TIME} = $tn;
|
||||
$hash->{READINGS}{$type}{VAL} = $hash->{Cmd};
|
||||
$hash->{CHANGED}[0] = "$type: $hash->{Cmd}";
|
||||
}
|
||||
|
||||
DoTrigger($name, undef) if($init_done);
|
||||
|
||||
Log GetLogLevel($name,3), "SCIVT Set result Timer:$hash->{Timer} sec Cmd:$hash->{Cmd}";
|
||||
return "SCIVT => Timer:$hash->{Timer} Cmd:$hash->{Cmd}";
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub
|
||||
SCIVT_Get($@)
|
||||
{
|
||||
my ($hash, @a) = @_;
|
||||
return "get for an SCIVT device needs exactly one parameter" if(@a != 2);
|
||||
my $name = $hash->{NAME};
|
||||
|
||||
my $v;
|
||||
if($a[1] eq "data")
|
||||
{
|
||||
$v = SCIVT_GetLine($hash->{DeviceName}, $hash->{Cmd});
|
||||
if(!defined($v))
|
||||
{
|
||||
Log GetLogLevel($name,2), "SCIVT Get $a[1] error";
|
||||
return "$a[0] $a[1] => Error";
|
||||
}
|
||||
$v =~ s/[\r\n]//g; # Delete the NewLine
|
||||
$hash->{READINGS}{$a[1]}{VAL} = $v;
|
||||
$hash->{READINGS}{$a[1]}{TIME} = TimeNow();
|
||||
}
|
||||
else
|
||||
{
|
||||
if($a[1] eq "param")
|
||||
{
|
||||
$v = "$hash->{DeviceName} $hash->{Timer} $hash->{Cmd}";
|
||||
}
|
||||
else
|
||||
{
|
||||
return "Unknown argument $a[1], must be data or param";
|
||||
}
|
||||
}
|
||||
|
||||
Log GetLogLevel($name,3), "SCIVT Get $a[1] $v";
|
||||
return "$a[0] $a[1] => $v";
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub
|
||||
SCIVT_GetStatus($)
|
||||
{
|
||||
my ($hash) = @_;
|
||||
my $dnr = $hash->{DEVNR};
|
||||
my $name = $hash->{NAME};
|
||||
|
||||
# Call us in n minutes again.
|
||||
InternalTimer(gettimeofday()+ $hash->{Timer}, "SCIVT_GetStatus", $hash,1);
|
||||
|
||||
my %vals;
|
||||
my $result = SCIVT_GetLine($hash->{DeviceName}, $hash->{Cmd});
|
||||
|
||||
if(!defined($result))
|
||||
{
|
||||
Log GetLogLevel($name,4), "SCIVT read error, retry $hash->{DeviceName}, $hash->{Cmd}";
|
||||
$result = SCIVT_GetLine($hash->{DeviceName}, $hash->{Cmd});
|
||||
}
|
||||
|
||||
if(!defined($result))
|
||||
{
|
||||
Log GetLogLevel($name,2), "SCIVT read error, abort $hash->{DeviceName}, $hash->{Cmd}";
|
||||
$hash->{STATE} = "timeout";
|
||||
return $hash->{STATE};
|
||||
}
|
||||
if (length($result) < 10)
|
||||
{
|
||||
Log GetLogLevel($name,2), "SCIVT incomplete line ($result)";
|
||||
$hash->{STATE} = "incomplete";
|
||||
}
|
||||
else
|
||||
{
|
||||
$result =~ s/^.*R://;
|
||||
$result =~ s/[\r\n ]//g;
|
||||
Log GetLogLevel($name,3), "SCIVT $result (raw)";
|
||||
$result=~ s/,/./g;
|
||||
my @data = split(";", $result);
|
||||
|
||||
my @names = ("Vs", "Is", "Temp", "minV", "maxV", "minI", "maxI");
|
||||
my $tn = TimeNow();
|
||||
for(my $i = 0; $i < int(@names); $i++)
|
||||
{
|
||||
$hash->{CHANGED}[$i] = "$names[$i]: $data[$i]";
|
||||
$hash->{READINGS}{$names[$i]}{TIME} = $tn;
|
||||
$hash->{READINGS}{$names[$i]}{VAL} = $data[$i];
|
||||
}
|
||||
|
||||
DoTrigger($name, undef) if($init_done);
|
||||
|
||||
$result =~ s/;/ /g;
|
||||
$hash->{STATE} = "$result";
|
||||
}
|
||||
|
||||
return $hash->{STATE};
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub
|
||||
SCIVT_GetLine($$)
|
||||
{
|
||||
my $retry = 0;
|
||||
my ($dev,$cmd) = @_;
|
||||
|
||||
return "R:13,66; 0,0;30;13,62;15,09;- 0,2; 2,8;\n"
|
||||
if($dev eq "none"); # Fake-mode
|
||||
|
||||
my $serport = new Device::SerialPort ($dev);
|
||||
if(!$serport) {
|
||||
Log 1, "SCIVT: Can't open $dev: $!";
|
||||
return undef;
|
||||
}
|
||||
$serport->reset_error();
|
||||
$serport->baudrate(1200);
|
||||
$serport->databits(8);
|
||||
$serport->parity('none');
|
||||
$serport->stopbits(1);
|
||||
$serport->handshake('none');
|
||||
|
||||
my $rm = "SCIVT timeout reading the answer";
|
||||
my $data="";
|
||||
|
||||
$serport->write($cmd);
|
||||
sleep(1);
|
||||
|
||||
for(;;)
|
||||
{
|
||||
my ($rout, $rin) = ('', '');
|
||||
vec($rin, $serport->FILENO, 1) = 1;
|
||||
my $nfound = select($rout=$rin, undef, undef, 3.0);
|
||||
|
||||
if($nfound < 0) {
|
||||
$rm = "SCIVT Select error $nfound / $!";
|
||||
goto DONE;
|
||||
}
|
||||
last if($nfound == 0);
|
||||
|
||||
my $buf = $serport->input();
|
||||
if(!defined($buf) || length($buf) == 0) {
|
||||
$rm = "SCIVT EOF on $dev";
|
||||
goto DONE;
|
||||
}
|
||||
|
||||
|
||||
$data .= $buf;
|
||||
if($data =~ m/[\r\n]/) { # Newline received
|
||||
$serport->close();
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
|
||||
DONE:
|
||||
$serport->close();
|
||||
Log 3, "SCIVT $rm";
|
||||
return undef;
|
||||
}
|
||||
|
||||
1;
|
|
@ -1,348 +0,0 @@
|
|||
#
|
||||
#
|
||||
# 80_M232.pm
|
||||
# written by Dr. Boris Neubert 2007-11-26
|
||||
# e-mail: omega at online dot de
|
||||
#
|
||||
##############################################
|
||||
package main;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
|
||||
sub M232Write($$);
|
||||
sub M232GetData($$);
|
||||
sub Log($$);
|
||||
use vars qw {%attr %defs};
|
||||
|
||||
#####################################
|
||||
sub
|
||||
M232_Initialize($)
|
||||
{
|
||||
my ($hash) = @_;
|
||||
|
||||
# Provider
|
||||
$hash->{WriteFn} = "M232_Write";
|
||||
$hash->{Clients} = ":M232Counter:M232Voltage:";
|
||||
|
||||
# Consumer
|
||||
$hash->{DefFn} = "M232_Define";
|
||||
$hash->{UndefFn} = "M232_Undef";
|
||||
$hash->{GetFn} = "M232_Get";
|
||||
$hash->{SetFn} = "M232_Set";
|
||||
$hash->{AttrList}= "model:m232 loglevel:0,1,2,3,4,5";
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub
|
||||
M232_Define($$)
|
||||
{
|
||||
my ($hash, $def) = @_;
|
||||
my @a = split("[ \t][ \t]*", $def);
|
||||
|
||||
$hash->{STATE} = "Initialized";
|
||||
|
||||
my $dev = $a[2];
|
||||
$attr{$a[0]}{savefirst} = 1;
|
||||
|
||||
if($dev eq "none") {
|
||||
Log 1, "M232 device is none, commands will be echoed only";
|
||||
return undef;
|
||||
}
|
||||
|
||||
Log 3, "M232 opening device $dev";
|
||||
my $po;
|
||||
if ($^O eq 'MSWin32') {
|
||||
eval ("use Win32::SerialPort;");
|
||||
if ($@) {
|
||||
$hash->{STATE} = "error using Modul Win32::SerialPort";
|
||||
Log 1,"Error using Device::SerialPort";
|
||||
return "Can't use Win32::SerialPort $@\n";
|
||||
}
|
||||
$po = new Win32::SerialPort ($dev, 1);
|
||||
|
||||
} else {
|
||||
eval ("use Device::SerialPort;");
|
||||
if ($@) {
|
||||
$hash->{STATE} = "error using Modul Device::SerialPort";
|
||||
Log 1,"Error using Device::SerialPort";
|
||||
return "Can't Device::SerialPort $@\n";
|
||||
}
|
||||
$po = new Device::SerialPort ($dev, 1);
|
||||
}
|
||||
if (!$po) {
|
||||
$hash->{STATE} = "error opening device";
|
||||
Log 1,"Error opening Serial Device $dev";
|
||||
return "Can't open Device $dev: $^E\n";
|
||||
}
|
||||
|
||||
Log 3, "M232 opened device $dev";
|
||||
$po->close();
|
||||
|
||||
$hash->{DeviceName} = $dev;
|
||||
return undef;
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub
|
||||
M232_Undef($$)
|
||||
{
|
||||
my ($hash, $arg) = @_;
|
||||
my $name = $hash->{NAME};
|
||||
|
||||
foreach my $d (sort keys %defs) {
|
||||
if(defined($defs{$d}) &&
|
||||
defined($defs{$d}{IODev}) &&
|
||||
$defs{$d}{IODev} == $hash)
|
||||
{
|
||||
my $lev = ($reread_active ? 4 : 2);
|
||||
Log GetLogLevel($name,$lev), "deleting port for $d";
|
||||
delete $defs{$d}{IODev};
|
||||
}
|
||||
}
|
||||
return undef;
|
||||
}
|
||||
#####################################
|
||||
# M232_Ready
|
||||
# implement ReadyFn
|
||||
# only used for Win32
|
||||
#
|
||||
sub
|
||||
M232_Ready($$)
|
||||
{
|
||||
my ($hash, $dev) = @_;
|
||||
my $po=$dev||$hash->{po};
|
||||
return 0 if !$po;
|
||||
my ($BlockingFlags, $InBytes, $OutBytes, $ErrorFlags)=$po->status;
|
||||
return ($InBytes>0);
|
||||
}
|
||||
|
||||
|
||||
#####################################
|
||||
|
||||
sub
|
||||
M232_Set($@)
|
||||
{
|
||||
my ($hash, @a) = @_;
|
||||
my $u1 = "Usage: set <name> auto <value>\n" .
|
||||
"set <name> stop\n" .
|
||||
"set <name> start\n" .
|
||||
"set <name> octet <value>\n" .
|
||||
"set <name> [io0..io7] 0|1\n";
|
||||
|
||||
return $u1 if(int(@a) < 2);
|
||||
my $msg;
|
||||
my $reading= $a[1];
|
||||
my $value;
|
||||
my @legal;
|
||||
|
||||
if($reading eq "auto") {
|
||||
return $u1 if(int(@a) !=3);
|
||||
$value= $a[2];
|
||||
@legal= (0..5,"none");
|
||||
if(!grep($value eq $_, @legal)) {
|
||||
return "Illegal value $value, possible values: @legal";
|
||||
}
|
||||
if($value eq "none") { $value= 0; } else { $value+=1; }
|
||||
$msg= "M" . $value;
|
||||
}
|
||||
|
||||
elsif($reading eq "start") {
|
||||
return $u1 if(int(@a) !=2);
|
||||
$msg= "Z1";
|
||||
}
|
||||
|
||||
elsif($reading eq "stop") {
|
||||
return $u1 if(int(@a) !=2);
|
||||
$msg= "Z0";
|
||||
}
|
||||
|
||||
elsif($reading eq "octet") {
|
||||
return $u1 if(int(@a) !=3);
|
||||
$value= $a[2];
|
||||
@legal= (0..255);
|
||||
if(!grep($value eq $_, @legal)) {
|
||||
return "Illegal value $value, possible values: 0..255";
|
||||
}
|
||||
$msg= sprintf("W%02X", $value);
|
||||
}
|
||||
|
||||
elsif($reading =~ /^io[0-7]$/) {
|
||||
return $u1 if(int(@a) !=3);
|
||||
$value= $a[2];
|
||||
return $u1 unless($value eq "0" || $value eq "1");
|
||||
$msg= "D" . substr($reading,2,1) . $value;
|
||||
}
|
||||
|
||||
else { return $u1; }
|
||||
|
||||
my $d = M232GetData($hash, $msg);
|
||||
return "Read error" if(!defined($d));
|
||||
return $d;
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub
|
||||
M232_Get($@)
|
||||
{
|
||||
|
||||
my ($hash, @a) = @_;
|
||||
my $u1 = "Usage: get <name> [an0..an5]\n" .
|
||||
"get <name> [io0..io7]\n" .
|
||||
"get <name> octet\n" .
|
||||
"get <name> counter";
|
||||
|
||||
return $u1 if(int(@a) != 2);
|
||||
|
||||
my $name= $a[0];
|
||||
my $reading= $a[1];
|
||||
my $msg;
|
||||
my $retval;
|
||||
my ($count,$d,$state,$iscurrent,$voltage);
|
||||
|
||||
|
||||
if($reading eq "counter") {
|
||||
$msg= "z";
|
||||
$d = M232GetData($hash, $msg);
|
||||
return "Read error" if(!defined($d));
|
||||
$count= hex $d;
|
||||
$retval= $count;
|
||||
}
|
||||
|
||||
elsif($reading =~ /^an[0-5]$/) {
|
||||
$msg= "a" . substr($reading,2,1);
|
||||
$d = M232GetData($hash, $msg);
|
||||
return "Read error" if(!defined($d));
|
||||
$voltage= (hex substr($d,0,3))*5.00/1024.0;
|
||||
$iscurrent= substr($d,3,1);
|
||||
$retval= $voltage; # . " " . $iscurrent;
|
||||
}
|
||||
|
||||
elsif($reading =~ /^io[0-7]$/) {
|
||||
$msg= "d" . substr($reading,2,1);
|
||||
$d = M232GetData($hash, $msg);
|
||||
return "Read error" if(!defined($d));
|
||||
$state= hex $d;
|
||||
$retval= $state;
|
||||
}
|
||||
|
||||
elsif($reading eq "octet") {
|
||||
$msg= "w";
|
||||
$d = M232GetData($hash, $msg);
|
||||
return "Read error" if(!defined($d));
|
||||
$state= hex $d;
|
||||
$retval= $state;
|
||||
}
|
||||
|
||||
else { return $u1; }
|
||||
|
||||
$hash->{READINGS}{$reading}{VAL}= $retval;
|
||||
$hash->{READINGS}{$reading}{TIME}= TimeNow();
|
||||
|
||||
return "$name $reading => $retval";
|
||||
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub
|
||||
M232_Write($$)
|
||||
{
|
||||
my ($hash,$msg) = @_;
|
||||
|
||||
return M232GetData($hash, $msg);
|
||||
}
|
||||
|
||||
|
||||
#####################################
|
||||
sub
|
||||
M232GetData($$)
|
||||
{
|
||||
my ($hash, $data) = @_;
|
||||
my $dev=$hash->{DeviceName};
|
||||
my $MSGSTART= chr 1;
|
||||
my $MSGEND= chr 13;
|
||||
my $MSGACK= chr 6;
|
||||
my $MSGNACK= chr 21;
|
||||
my $serport;
|
||||
my $d = $MSGSTART . $data . $MSGEND;
|
||||
|
||||
if ($^O eq 'MSWin32') {
|
||||
$serport=new Win32::SerialPort ($dev, 1);
|
||||
}else{
|
||||
$serport=new Device::SerialPort ($dev, 1);
|
||||
}
|
||||
if(!$serport) {
|
||||
Log 3, "M232: Can't open $dev: $!";
|
||||
return undef;
|
||||
}
|
||||
$serport->reset_error();
|
||||
$serport->baudrate(2400);
|
||||
$serport->databits(8);
|
||||
$serport->parity('none');
|
||||
$serport->stopbits(1);
|
||||
$serport->handshake('none');
|
||||
$serport->write_settings;
|
||||
$hash->{po}=$serport;
|
||||
Log 4, "M232: Sending $d";
|
||||
|
||||
my $rm = "M232: ?";
|
||||
|
||||
$serport->lookclear;
|
||||
$serport->write($d);
|
||||
|
||||
my $retval = "";
|
||||
my $status = "";
|
||||
my $nfound=0;
|
||||
my $ret=undef;
|
||||
sleep(1);
|
||||
for(;;) {
|
||||
if ($^O eq 'MSWin32') {
|
||||
$nfound=M232_Ready($hash,undef);
|
||||
}else{
|
||||
my ($rout, $rin) = ('', '');
|
||||
vec($rin, $serport->FILENO, 1) = 1;
|
||||
$nfound = select($rin, undef, undef, 1.0); # 3 seconds timeout
|
||||
if($nfound < 0) {
|
||||
next if ($! == EAGAIN() || $! == EINTR() || $! == 0);
|
||||
$rm="M232:Select error $nfound / $!";
|
||||
last;
|
||||
}
|
||||
}
|
||||
|
||||
last if($nfound == 0);
|
||||
|
||||
my $out = $serport->read(1);
|
||||
if(!defined($out) || length($out) == 0) {
|
||||
$rm = "M232 EOF on $dev";
|
||||
last;
|
||||
}
|
||||
|
||||
if($out eq $MSGACK) {
|
||||
$rm= "M232: acknowledged";
|
||||
Log 4, "M232: return value \'" . $retval . "\'";
|
||||
$status= "ACK";
|
||||
} elsif($out eq $MSGNACK) {
|
||||
$rm= "M232: not acknowledged";
|
||||
$status= "NACK";
|
||||
$retval= undef;
|
||||
} else {
|
||||
$retval .= $out;
|
||||
}
|
||||
|
||||
if($status) {
|
||||
$ret=$retval;
|
||||
last;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
DONE:
|
||||
$serport->close();
|
||||
undef $serport;
|
||||
delete $hash->{po} if exists($hash->{po});
|
||||
Log 4, $rm;
|
||||
return $ret;
|
||||
}
|
||||
|
||||
1;
|
|
@ -1,253 +0,0 @@
|
|||
#
|
||||
#
|
||||
# 81_M232Counter.pm
|
||||
# written by Dr. Boris Neubert 2007-11-26
|
||||
# e-mail: omega at online dot de
|
||||
#
|
||||
##############################################
|
||||
package main;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use Time::HiRes qw(gettimeofday);
|
||||
|
||||
sub M232Counter_Get($@);
|
||||
sub M232Counter_Set($@);
|
||||
sub M232Counter_SetBasis($@);
|
||||
sub M232Counter_Define($$);
|
||||
sub M232Counter_GetStatus($);
|
||||
|
||||
###################################
|
||||
sub
|
||||
M232Counter_Initialize($)
|
||||
{
|
||||
my ($hash) = @_;
|
||||
|
||||
$hash->{GetFn} = "M232Counter_Get";
|
||||
$hash->{SetFn} = "M232Counter_Set";
|
||||
$hash->{DefFn} = "M232Counter_Define";
|
||||
|
||||
$hash->{AttrList} = "dummy:1,0 model;M232Counter loglevel:0,1,2,3,4,5";
|
||||
}
|
||||
|
||||
###################################
|
||||
sub
|
||||
M232Counter_GetStatus($)
|
||||
{
|
||||
my ($hash) = @_;
|
||||
|
||||
if(!$hash->{LOCAL}) {
|
||||
InternalTimer(gettimeofday()+$hash->{INTERVAL}, "M232Counter_GetStatus", $hash, 1);
|
||||
}
|
||||
|
||||
my $name = $hash->{NAME};
|
||||
my $r= $hash->{READINGS};
|
||||
|
||||
my $d = IOWrite($hash, "z");
|
||||
if(!defined($d)) {
|
||||
my $msg = "M232Counter $name tick count read error";
|
||||
Log GetLogLevel($name,2), $msg;
|
||||
return $msg;
|
||||
}
|
||||
|
||||
# time
|
||||
my $tn = TimeNow();
|
||||
|
||||
#tsecs
|
||||
my $tsecs= time(); # number of non-leap seconds since January 1, 1970, UTC
|
||||
|
||||
# previous tsecs
|
||||
my $tsecs_prev;
|
||||
if(defined($r->{tsecs})) {
|
||||
$tsecs_prev= $r->{tsecs}{VAL};
|
||||
} else{
|
||||
$tsecs_prev= $tsecs; # 1970-01-01
|
||||
}
|
||||
|
||||
# basis
|
||||
my $basis;
|
||||
if(defined($r->{basis})) {
|
||||
$basis= $r->{basis}{VAL};
|
||||
} else {
|
||||
$basis= 0;
|
||||
}
|
||||
my $basis_prev= $basis;
|
||||
|
||||
|
||||
# previous count (this variable is currently unused)
|
||||
my $count_prev;
|
||||
if(defined($r->{count})) {
|
||||
$count_prev= $r->{count}{VAL};
|
||||
} else {
|
||||
$count_prev= 0;
|
||||
}
|
||||
|
||||
# current count
|
||||
my $count= hex $d;
|
||||
# If the counter reaches 65536, the counter does not wrap around but
|
||||
# stops at 0. We therefore purposefully reset the counter to 0 before
|
||||
# it reaches its final tick count.
|
||||
if($count > 64000) {
|
||||
$basis+= $count;
|
||||
$count= 0;
|
||||
$r->{basis}{VAL} = $basis;
|
||||
$r->{basis}{TIME}= $tn;
|
||||
my $ret = IOWrite($hash, "Z1");
|
||||
if(!defined($ret)) {
|
||||
my $msg = "M232Counter $name reset error";
|
||||
Log GetLogLevel($name,2), $msg;
|
||||
return $msg;
|
||||
}
|
||||
}
|
||||
|
||||
# previous value
|
||||
my $value_prev;
|
||||
if(defined($r->{value})) {
|
||||
$value_prev= $r->{value}{VAL};
|
||||
} else {
|
||||
$value_prev= 0;
|
||||
}
|
||||
|
||||
# current value
|
||||
my $value= ($basis+$count) * $hash->{FACTOR};
|
||||
# round to 3 digits
|
||||
$value= int($value*1000.0+0.5)/1000.0;
|
||||
|
||||
# set new values
|
||||
$r->{count}{TIME} = $tn;
|
||||
$r->{count}{VAL} = $count;
|
||||
$r->{value}{TIME} = $tn;
|
||||
$r->{value}{VAL} = $value;
|
||||
$r->{tsecs}{TIME} = $tn;
|
||||
$r->{tsecs}{VAL} = $tsecs;
|
||||
|
||||
$hash->{CHANGED}[0]= "count: $count";
|
||||
$hash->{CHANGED}[1]= "value: $value";
|
||||
|
||||
# delta
|
||||
my $tsecs_delta= $tsecs-$tsecs_prev;
|
||||
my $count_delta= ($count+$basis)-($count_prev+$basis_prev);
|
||||
if($tsecs_delta>0) {
|
||||
my $delta= ($count_delta/$tsecs_delta)*$hash->{DELTAFACTOR};
|
||||
# round to 3 digits
|
||||
$delta= int($delta*1000.0+0.5)/1000.0;
|
||||
$r->{delta}{TIME} = $tn;
|
||||
$r->{delta}{VAL} = $delta;
|
||||
$hash->{CHANGED}[2]= "delta: $delta";
|
||||
}
|
||||
|
||||
|
||||
if(!$hash->{LOCAL}) {
|
||||
DoTrigger($name, undef) if($init_done);
|
||||
}
|
||||
|
||||
$hash->{STATE} = $value;
|
||||
Log GetLogLevel($name,4), "M232Counter $name: $value $hash->{UNIT}";
|
||||
|
||||
return $hash->{STATE};
|
||||
}
|
||||
|
||||
###################################
|
||||
sub
|
||||
M232Counter_Get($@)
|
||||
{
|
||||
my ($hash, @a) = @_;
|
||||
|
||||
return "argument is missing" if(int(@a) != 2);
|
||||
|
||||
my $msg;
|
||||
|
||||
if($a[1] ne "status") {
|
||||
return "unknown get value, valid is status";
|
||||
}
|
||||
$hash->{LOCAL} = 1;
|
||||
my $v = M232Counter_GetStatus($hash);
|
||||
delete $hash->{LOCAL};
|
||||
|
||||
return "$a[0] $a[1] => $v";
|
||||
}
|
||||
|
||||
#############################
|
||||
sub
|
||||
M232Counter_Calibrate($@)
|
||||
{
|
||||
my ($hash, $value) = @_;
|
||||
my $rm= undef;
|
||||
my $name = $hash->{NAME};
|
||||
|
||||
|
||||
# adjust basis
|
||||
my $tn = TimeNow();
|
||||
$hash->{READINGS}{basis}{VAL}= $value / $hash->{FACTOR};
|
||||
$hash->{READINGS}{basis}{TIME}= $tn;
|
||||
$hash->{READINGS}{count}{VAL}= 0;
|
||||
$hash->{READINGS}{count}{TIME}= $tn;
|
||||
|
||||
# recalculate value
|
||||
$hash->{READINGS}{value}{VAL} = $value;
|
||||
$hash->{READINGS}{value}{TIME} = $tn;
|
||||
|
||||
# reset counter
|
||||
my $ret = IOWrite($hash, "Z1");
|
||||
if(!defined($ret)) {
|
||||
my $rm = "M232Counter $name read error";
|
||||
Log GetLogLevel($name,2), $rm;
|
||||
}
|
||||
|
||||
return $rm;
|
||||
}
|
||||
|
||||
#############################
|
||||
sub
|
||||
M232Counter_Set($@)
|
||||
{
|
||||
my ($hash, @a) = @_;
|
||||
my $u = "Usage: set <name> value <value>\n" .
|
||||
"set <name> interval <seconds>\n" ;
|
||||
|
||||
return $u if(int(@a) != 3);
|
||||
my $reading= $a[1];
|
||||
|
||||
if($a[1] eq "value") {
|
||||
my $value= $a[2];
|
||||
my $rm= M232Counter_Calibrate($hash, $value);
|
||||
} elsif($a[1] eq "interval") {
|
||||
my $interval= $a[2];
|
||||
$hash->{INTERVAL}= $interval;
|
||||
} else {
|
||||
return $u;
|
||||
}
|
||||
|
||||
return undef;
|
||||
}
|
||||
|
||||
|
||||
#############################
|
||||
sub
|
||||
M232Counter_Define($$)
|
||||
{
|
||||
my ($hash, $def) = @_;
|
||||
my @a = split("[ \t][ \t]*", $def);
|
||||
|
||||
return "syntax: define <name> M232Counter [unit] [factor] [deltaunit] [deltafactor]"
|
||||
if(int(@a) < 2 && int(@a) > 6);
|
||||
|
||||
my $unit= ((int(@a) > 2) ? $a[2] : "ticks");
|
||||
my $factor= ((int(@a) > 3) ? $a[3] : 1.0);
|
||||
my $deltaunit= ((int(@a) > 4) ? $a[4] : "ticks per second");
|
||||
my $deltafactor= ((int(@a) > 5) ? $a[5] : 1.0);
|
||||
$hash->{UNIT}= $unit;
|
||||
$hash->{FACTOR}= $factor;
|
||||
$hash->{DELTAUNIT}= $deltaunit;
|
||||
$hash->{DELTAFACTOR}= $deltafactor;
|
||||
$hash->{INTERVAL}= 60; # poll every minute per default
|
||||
|
||||
AssignIoPort($hash);
|
||||
|
||||
if(!$hash->{LOCAL}) {
|
||||
InternalTimer(gettimeofday()+60, "M232Counter_GetStatus", $hash, 0);
|
||||
}
|
||||
return undef;
|
||||
}
|
||||
|
||||
1;
|
|
@ -1,116 +0,0 @@
|
|||
#
|
||||
#
|
||||
# 82_M232Voltage.pm
|
||||
# written by Dr. Boris Neubert 2007-12-24
|
||||
# e-mail: omega at online dot de
|
||||
#
|
||||
##############################################
|
||||
package main;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use Time::HiRes qw(gettimeofday);
|
||||
|
||||
sub M232Voltage_Get($@);
|
||||
sub M232Voltage_Define($$);
|
||||
sub M232Voltage_GetStatus($);
|
||||
|
||||
###################################
|
||||
sub
|
||||
M232Voltage_Initialize($)
|
||||
{
|
||||
my ($hash) = @_;
|
||||
|
||||
$hash->{GetFn} = "M232Voltage_Get";
|
||||
$hash->{DefFn} = "M232Voltage_Define";
|
||||
|
||||
$hash->{AttrList} = "dummy:1,0 model;M232Voltage loglevel:0,1,2,3,4,5";
|
||||
}
|
||||
|
||||
###################################
|
||||
sub
|
||||
M232Voltage_GetStatus($)
|
||||
{
|
||||
my ($hash) = @_;
|
||||
|
||||
if(!$hash->{LOCAL}) {
|
||||
InternalTimer(gettimeofday()+60, "M232Voltage_GetStatus", $hash, 1);
|
||||
}
|
||||
|
||||
my $name = $hash->{NAME};
|
||||
|
||||
my $d = IOWrite($hash, "a" . $hash->{INPUT});
|
||||
if(!defined($d)) {
|
||||
my $msg = "M232Voltage $name read error";
|
||||
Log GetLogLevel($name,2), $msg;
|
||||
return $msg;
|
||||
}
|
||||
|
||||
my $tn = TimeNow();
|
||||
my $value= (hex substr($d,0,3))*5.00/1024.0 * $hash->{FACTOR};
|
||||
|
||||
$hash->{READINGS}{value}{TIME} = $tn;
|
||||
$hash->{READINGS}{value}{VAL} = $value;
|
||||
|
||||
$hash->{CHANGED}[0]= "value: $value";
|
||||
|
||||
if(!$hash->{LOCAL}) {
|
||||
DoTrigger($name, undef) if($init_done);
|
||||
}
|
||||
|
||||
$hash->{STATE} = $value;
|
||||
Log GetLogLevel($name,4), "M232Voltage $name: $value $hash->{UNIT}";
|
||||
|
||||
return $hash->{STATE};
|
||||
}
|
||||
|
||||
###################################
|
||||
sub
|
||||
M232Voltage_Get($@)
|
||||
{
|
||||
my ($hash, @a) = @_;
|
||||
|
||||
return "argument is missing" if(int(@a) != 2);
|
||||
|
||||
my $msg;
|
||||
|
||||
if($a[1] ne "status") {
|
||||
return "unknown get value, valid is status";
|
||||
}
|
||||
$hash->{LOCAL} = 1;
|
||||
my $v = M232Voltage_GetStatus($hash);
|
||||
delete $hash->{LOCAL};
|
||||
|
||||
return "$a[0] $a[1] => $v";
|
||||
}
|
||||
|
||||
#############################
|
||||
sub
|
||||
M232Voltage_Define($$)
|
||||
{
|
||||
my ($hash, $def) = @_;
|
||||
my @a = split("[ \t][ \t]*", $def);
|
||||
|
||||
return "syntax: define <name> M232Voltage an0..an5 [unit [factor]]"
|
||||
if(int(@a) < 3 && int(@a) > 5);
|
||||
|
||||
my $reading= $a[2];
|
||||
return "$reading is not an analog input, valid: an0..an5"
|
||||
if($reading !~ /^an[0-5]$/) ;
|
||||
|
||||
my $unit= ((int(@a) > 3) ? $a[3] : "volts");
|
||||
my $factor= ((int(@a) > 4) ? $a[4] : 1.0);
|
||||
|
||||
$hash->{INPUT}= substr($reading,2);
|
||||
$hash->{UNIT}= $unit;
|
||||
$hash->{FACTOR}= $factor;
|
||||
|
||||
AssignIoPort($hash);
|
||||
|
||||
if(!$hash->{LOCAL}) {
|
||||
InternalTimer(gettimeofday()+60, "M232Voltage_GetStatus", $hash, 0);
|
||||
}
|
||||
return undef;
|
||||
}
|
||||
|
||||
1;
|
|
@ -1,542 +0,0 @@
|
|||
package main;
|
||||
###########################
|
||||
# 87_ws2000.pm
|
||||
# Modul for FHEM
|
||||
#
|
||||
# contributed by thomas dressler 2008
|
||||
# $Id: 87_WS2000.pm,v 1.6 2009-04-10 09:54:37 rudolfkoenig Exp $
|
||||
# corr. negativ temps / peterp
|
||||
###########################
|
||||
use strict;
|
||||
use Switch;
|
||||
use warnings;
|
||||
|
||||
#prototypes to make komodo happy
|
||||
use vars qw{%attr %defs};
|
||||
sub Log($$);
|
||||
our $FH;
|
||||
####################################
|
||||
# WS2000_Initialize
|
||||
# Implements Initialize function
|
||||
#
|
||||
sub WS2000_Initialize($)
|
||||
{
|
||||
my ($hash) = @_;
|
||||
|
||||
# Provider
|
||||
#$hash->{WriteFn} = "ws2000_Write";
|
||||
# $hash->{Clients} = ":WS2000Rain:WS2000Wind:WS2000Indoor:WS2000Lux:WS2000Pyro:WS2000Temp:WS2000TempHum";
|
||||
|
||||
# Consumer
|
||||
$hash->{DefFn} = "WS2000_Define";
|
||||
$hash->{UndefFn} = "WS2000_Undef";
|
||||
$hash->{GetFn} = "WS2000_Get";
|
||||
$hash->{SetFn} = "WS2000_Set";
|
||||
$hash->{ReadyFn} = "WS2000_Ready";
|
||||
$hash->{ReadFn} ="WS2000_Read";
|
||||
$hash->{ListFn} ="WS2000_List";
|
||||
$hash->{AttrList}= "model:WS2000 rain altitude loglevel:0,1,2,3,4,5";
|
||||
}
|
||||
|
||||
#####################################
|
||||
# WS2000_Define
|
||||
# Implements DefFn function
|
||||
#
|
||||
sub
|
||||
WS2000_Define($$)
|
||||
{
|
||||
my ($hash, $def) = @_;
|
||||
my @a = split("[ \t][ \t]*", $def);
|
||||
delete $hash->{po};
|
||||
delete $hash->{socket};
|
||||
delete $hash->{FD};
|
||||
my $ws2000_cfg='ws2000.cfg';
|
||||
my $quiet=1;
|
||||
my $name=$hash->{NAME};
|
||||
my $PortName = $a[2];
|
||||
my $PortObj;
|
||||
$attr{$a[0]}{savefirst} = 1;
|
||||
|
||||
if($PortName eq "none") {
|
||||
Log 1, "WS2000 device is none, commands will be echoed only";
|
||||
return undef;
|
||||
}
|
||||
|
||||
Log 4, "WS2000 opening device $PortName";
|
||||
|
||||
#switch serial/socket device
|
||||
if ($PortName=~/^\/dev|^COM/) {
|
||||
#normal devices (/dev), on windows COMx
|
||||
my $OS=$^O;
|
||||
if ($OS eq 'MSWin32') {
|
||||
eval ("use Win32::SerialPort;");
|
||||
if ($@) {
|
||||
$hash->{STATE} = "error using Modul Win32::SerialPort";
|
||||
Log 1,"Error using Device::SerialPort";
|
||||
return "Can't use Win32::SerialPort $@\n";
|
||||
}
|
||||
$PortObj = new Win32::SerialPort ($PortName, $quiet);
|
||||
if (!$PortObj) {
|
||||
$hash->{STATE} = "error opening device";
|
||||
Log 1,"Error opening Serial Device $PortName";
|
||||
return "Can't open Device $PortName: $^E\n";
|
||||
}
|
||||
#$hash->{FD}=$PortObj->{_HANDLE};
|
||||
$readyfnlist{"$a[0].$a[2]"} = $hash;
|
||||
} else {
|
||||
eval ("use Device::SerialPort;");
|
||||
if ($@) {
|
||||
$hash->{STATE} = "error using Modul Device::SerialPort";
|
||||
Log 1,"Error using Device::SerialPort";
|
||||
return "Can't Device::SerialPort $@\n";
|
||||
}
|
||||
$PortObj = new Device::SerialPort ($PortName, $quiet);
|
||||
if (!$PortObj) {
|
||||
$hash->{STATE} = "error opening device";
|
||||
Log 1,"Error opening Serial Device $PortName";
|
||||
return "Can't open Device $PortName: $^E\n";
|
||||
}
|
||||
$hash->{FD}=$PortObj->FILENO;
|
||||
$selectlist{"$a[0].$a[2]"} = $hash;
|
||||
}
|
||||
#Parameter 19200,8,2,Odd,None
|
||||
$PortObj->baudrate(19200);
|
||||
$PortObj->databits(8);
|
||||
$PortObj->parity("odd");
|
||||
$PortObj->stopbits(2);
|
||||
$PortObj->handshake("none");
|
||||
if (! $PortObj->write_settings) {
|
||||
undef $PortObj;
|
||||
return "Serial write Settings failed!\n";
|
||||
}
|
||||
$hash->{po}=$PortObj;
|
||||
$hash->{socket}=0;
|
||||
|
||||
}elsif($PortName=~/([\w.]+):(\d{1,5})/){
|
||||
#Sockets(hostname:port)
|
||||
my $host=$1;
|
||||
my $port=$2;
|
||||
my $xport=IO::Socket::INET->new(PeerAddr=>$host,
|
||||
PeerPort=>$port,
|
||||
timeout=>1,
|
||||
blocking=>0
|
||||
);
|
||||
if (!$xport) {
|
||||
$hash->{STATE} = "error opening device";
|
||||
Log 1,"Error opening Connection to $PortName";
|
||||
return "Can't Connect to $PortName -> $@ ( $!)\n";
|
||||
}
|
||||
$xport->autoflush(1);
|
||||
$hash->{FD}=$xport->fileno;
|
||||
$selectlist{"$a[0].$a[2]"} = $hash;
|
||||
$hash->{socket}=$xport;
|
||||
|
||||
|
||||
}else{
|
||||
$hash->{STATE} = "$PortName is no device and not implemented";
|
||||
Log 1,"$PortName is no device and not implemented";
|
||||
return "$PortName is no device and not implemented\n";
|
||||
}
|
||||
Log 4, "$name connected to device $PortName";
|
||||
$hash->{STATE} = "open";
|
||||
$hash->{DeviceName}=$PortName;
|
||||
return undef;
|
||||
}
|
||||
|
||||
#####################################
|
||||
# WS2000_Undef
|
||||
# implements UnDef-Function
|
||||
#
|
||||
sub
|
||||
WS2000_Undef($$)
|
||||
{
|
||||
my ($hash, $arg) = @_;
|
||||
my $name = $hash->{NAME};
|
||||
delete $hash->{FD};
|
||||
$hash->{STATE}='close';
|
||||
if ($hash->{socket}) {
|
||||
$hash->{socket}->shutdown(2);
|
||||
$hash->{socket}->close();
|
||||
}elsif ($hash->{po}) {
|
||||
$hash->{po}->close();
|
||||
}
|
||||
Log 5, "$name shutdown complete";
|
||||
return undef;
|
||||
}
|
||||
|
||||
#####################################
|
||||
# WS2000_Set
|
||||
# implement SetFn
|
||||
# currently nothing to set
|
||||
#
|
||||
sub
|
||||
WS2000_Ready($$)
|
||||
{
|
||||
my ($hash, $dev) = @_;
|
||||
my $po=$hash->{po};
|
||||
return undef if !$po;
|
||||
my ($BlockingFlags, $InBytes, $OutBytes, $ErrorFlags)=$po->status;
|
||||
return ($InBytes>0);
|
||||
}
|
||||
|
||||
#####################################
|
||||
# WS2000_Set
|
||||
# implement SetFn
|
||||
# currently nothing to set
|
||||
#
|
||||
sub
|
||||
WS2000_Set($@)
|
||||
{
|
||||
my ($hash, @a) = @_;
|
||||
|
||||
my $msg;
|
||||
my $name=$a[0];
|
||||
my $reading= $a[1];
|
||||
$msg="$name => No Set function ($reading) implemented";
|
||||
Log 1,$msg;
|
||||
return $msg;
|
||||
|
||||
}
|
||||
|
||||
#####################################
|
||||
# WS2000_Get
|
||||
# implement GetFn
|
||||
#
|
||||
sub
|
||||
WS2000_Get($@)
|
||||
{
|
||||
|
||||
my ($hash, @a) = @_;
|
||||
my $u1 = "Usage: get <name> [TH0..TH7, T0..T7, I0..I7, R0..R7, W0..W7, L0..L7, P0..P7, LAST, RAW]\n" .
|
||||
"get <name> list\n";
|
||||
|
||||
|
||||
return $u1 if(int(@a) != 2);
|
||||
|
||||
my $name= $a[0];
|
||||
my $reading= $a[1];
|
||||
my $msg;
|
||||
my $retval;
|
||||
my $time;
|
||||
my $sensor=$hash->{READINGS};
|
||||
if ($reading =~/list/i) {
|
||||
$msg='';
|
||||
foreach my $s (keys %$sensor) {
|
||||
next if !$s;
|
||||
$msg.="ID:$s, Last Update ".$sensor->{$s}{TIME}."\n";
|
||||
}
|
||||
}else {
|
||||
if(!defined($sensor->{$reading})) {
|
||||
$msg="Sensor ($reading)not defined, try 'get <n<me> list'";
|
||||
}else {
|
||||
$retval=$sensor->{$reading}{VAL};
|
||||
$time=$sensor->{$reading}{TIME};
|
||||
$retval=unpack("H*",$retval) if ($reading eq 'RAW');
|
||||
$msg= "$name $reading ($time) => $retval";
|
||||
}
|
||||
}
|
||||
return $msg;
|
||||
}
|
||||
|
||||
#####################################
|
||||
# WS2000_Write
|
||||
# currently dummy
|
||||
#
|
||||
sub
|
||||
WS2000_Write($$)
|
||||
{
|
||||
my ($hash,$msg) = @_;
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#####################################
|
||||
# WS2000_Read
|
||||
# Implements ReadFn, called from global select
|
||||
#
|
||||
sub
|
||||
WS2000_Read($$) {
|
||||
|
||||
my ($hash) = @_;
|
||||
|
||||
my $STX=2;
|
||||
my $ETX=3;
|
||||
my $retval='';
|
||||
my $out=undef;
|
||||
my $byte;
|
||||
my $name=$hash->{NAME};
|
||||
my $xport=$hash->{socket};
|
||||
my $PortObj=$hash->{po};
|
||||
my $message=$hash->{PARTIAL}||'';
|
||||
my $status=$hash->{STEP};
|
||||
#read data(1 byte), because fhem select flagged data available
|
||||
if ($xport) {
|
||||
$xport->read($out,1);
|
||||
}elsif($PortObj) {
|
||||
$out = $PortObj->read(1);
|
||||
}
|
||||
return if(!defined($out) || length($out) == 0) ;
|
||||
Log 5, "$name => WS2000/RAW: " . unpack('H*',$out);
|
||||
|
||||
#check for frame: STX TYP W1 W2 W3 W4 W5 ETX
|
||||
$byte=ord($out);
|
||||
if($byte eq $STX) {
|
||||
#Log 4, "M232: return value \'" . $retval . "\'";
|
||||
$status= "STX";
|
||||
$message=$out;
|
||||
Log 5, "WS2000 STX received";
|
||||
} elsif($byte eq $ETX) {
|
||||
$status= "ETX";
|
||||
$message .=$out;
|
||||
Log 5, "WS2000 ETX received";
|
||||
} elsif ($status eq "STX"){
|
||||
$message .=$out;
|
||||
}
|
||||
$hash->{STEP}=$status;
|
||||
$hash->{PARTIAL}=$message;
|
||||
if($status eq "ETX") {
|
||||
WS2000_Parse($hash,$message);
|
||||
}
|
||||
}
|
||||
#####################################
|
||||
# WS2000_Parse
|
||||
# decodes complete frame
|
||||
# called directly from WS2000_Read
|
||||
sub
|
||||
WS2000_Parse($$) {
|
||||
my ($hash,$msg) = @_;
|
||||
my ($stx,$typ,$w1,$w2,$w3,$w4,$w5,$etx)=map {$_ & 0x7F} unpack("U*",$msg);
|
||||
my $tm=TimeNow();
|
||||
my $name=$hash->{NAME};
|
||||
my $factor=$attr{$name}{rain}||366;
|
||||
my $altitude=$attr{$name}{altitude}||0;
|
||||
if ($etx != 3) {
|
||||
Log 4, "$name:Frame Error!";
|
||||
return undef;
|
||||
}
|
||||
my ($sensor,$daten1,$einheit1,$daten2,$einheit2,$daten3,$einheit3,$result,$shortname,$val, $unit);
|
||||
my $group = ($typ & 0x70)/16 ;#/slash for komodo syntax checker!
|
||||
my $snr = $typ % 16;
|
||||
|
||||
|
||||
#duplicate check (repeater?)
|
||||
my $prevmsg=$hash->{READINGS}{RAW}{VAL}||'';
|
||||
my $prevtime=$hash->{READINGS}{RAW}{TIME}||0;
|
||||
if (($prevmsg eq $msg) && ((time() - $prevtime) <10)) {
|
||||
Log 4,"$name check: Duplicate detected";
|
||||
return undef;
|
||||
}
|
||||
my $rawtext="Typ:$typ,W1:$w1,W2:$w2,W3:$w3,W4:$w4,W5:$w5";
|
||||
Log 4, "$name parsing: $rawtext";
|
||||
|
||||
#break into sensor specs
|
||||
switch ( $group ){
|
||||
case 7 {
|
||||
$sensor = "Fernbedienung";
|
||||
$shortname='FB';
|
||||
$einheit1='(CODE)';
|
||||
$daten1 = $w1 * 10000 + $w2 * 1000 + $w3 * 100 + $w4 * 10 + $w5;
|
||||
$result = $shortname . " => D=" . $daten1 . $einheit1;
|
||||
}
|
||||
case 0 {
|
||||
if ($snr < 8) {
|
||||
$sensor = "Temperatursensor V1.1(" . $snr . ")";
|
||||
}else{
|
||||
$snr -= 8;
|
||||
$sensor = "Temperatursensor V1.2(" .$snr. ")";
|
||||
}
|
||||
$daten1 = (($w1 * 128 + $w2) );
|
||||
if ($daten1 >= 16085)
|
||||
{
|
||||
$daten1 = $daten1 - 16384;
|
||||
}
|
||||
$daten1 = $daten1 / 10;
|
||||
$shortname='TX'.$snr;
|
||||
$einheit1 = " C";
|
||||
$result = $shortname . " => T:" . $daten1 . $einheit1;
|
||||
|
||||
}
|
||||
case 1 {
|
||||
if ($snr <8) {
|
||||
$sensor = "Temperatursensor mit Feuchte V1.1(" . $snr . ")";
|
||||
}else{
|
||||
$snr -= 8;
|
||||
$sensor = "Temperatursensor mit Feuchte V1.2(" . $snr . ")";
|
||||
}
|
||||
$daten1 = (($w1 * 128 + $w2) );
|
||||
if ($daten1 >= 16085)
|
||||
{
|
||||
$daten1 = $daten1 - 16384;
|
||||
}
|
||||
$daten1 = $daten1 / 10;
|
||||
$shortname='TH'.$snr;
|
||||
$einheit1 = " C";
|
||||
$daten2 = $w3;
|
||||
$daten3 = 0;
|
||||
$einheit2 = " %";
|
||||
|
||||
$result = $shortname . " => T:" . $daten1 . $einheit1 . ", H:" . $daten2 .$einheit2;
|
||||
|
||||
|
||||
}
|
||||
case 2 {
|
||||
if ( $snr < 8 ) {
|
||||
$sensor = "Regensensor V1.1(" . $snr . ")";
|
||||
}else{
|
||||
$snr -= 8;
|
||||
$sensor = "Regensensor V1.2(" . $snr . ")"
|
||||
}
|
||||
$shortname='R'.$snr;
|
||||
$daten1 = ($w1 * 128 + $w2);
|
||||
$einheit1= ' Imp';
|
||||
my $prev=$hash->{READINGS}{$shortname}{VAL};
|
||||
if ($prev && $prev=~/C=(\d+)/i) {
|
||||
$prev=$1;
|
||||
}else {
|
||||
$prev=0;
|
||||
}
|
||||
my $diff=$daten1-$prev;
|
||||
$daten2= $diff * $factor/1000;
|
||||
$einheit2 = " l/m2";
|
||||
$result = $shortname
|
||||
. " => M:".$daten2. $einheit2."(". $diff . $einheit1 ." x Faktor $factor)"
|
||||
. ", C:$daten1, P:$prev" ;
|
||||
|
||||
}
|
||||
case 3 {
|
||||
if ($snr < 8) {
|
||||
$sensor = "Windsensor V1.1(" . $snr . ")";
|
||||
}else{
|
||||
$snr -= 8;
|
||||
$sensor = "Windsensor V1.2(" . $snr . ")";
|
||||
}
|
||||
switch( $w3) {
|
||||
case 0 { $daten3 = 0;}
|
||||
case 1 { $daten3 = 22.5;}
|
||||
case 2 { $daten3 = 45;}
|
||||
case 3 { $daten3 = 67.5;}
|
||||
}
|
||||
$einheit3 = " +/-";
|
||||
$daten1 = ($w1 * 128 + $w2) / 10;
|
||||
$daten2 = $w4 * 128 + $w5;
|
||||
$einheit1 = " km/h";
|
||||
$einheit2 = " Grad";
|
||||
$shortname='W'.$snr;
|
||||
my @wr=("N","NNO","NO","ONO","O","OSO","SO","SSO","S","SSW","SW","WSW","W","WNW","NW","NNW");
|
||||
my @bf=(0,0.7,5.4,11.9,19.4,38.7,49.8,61.7,74.6,88.9,102.4,117.4);
|
||||
my @bfn=("Windstille","leiser Zug","leichte Brise","schwache Brise","maessige Brise","frische Brise",
|
||||
"starker Wind","steifer Wind","stuermischer Wind","Sturm","schwerer Sturm","orkanartiger Sturm","Orkan");
|
||||
my $i=1;
|
||||
foreach (1..$#bf) {
|
||||
if ($daten1<$bf[$i]) {
|
||||
last;
|
||||
}
|
||||
$i++;
|
||||
}
|
||||
$i--;
|
||||
#windrichtung
|
||||
my $w=int($daten2/22.5+0.5);
|
||||
if ($w ==16) {$w=0;}
|
||||
$result = $shortname
|
||||
. " => S:" . $daten1 . $einheit1
|
||||
. ", BF:$i($bfn[$i])"
|
||||
. " ,R:" . $daten2 . $einheit2
|
||||
. "($wr[$w])".$einheit3. $daten3;
|
||||
|
||||
}
|
||||
case 4 {
|
||||
if ($snr < 8) {
|
||||
$sensor = "Innensensor V1.1(" . $snr . ")";
|
||||
}else{
|
||||
$snr -= 8;
|
||||
$sensor = "Innensensor V1.2(" . $snr . ")";
|
||||
}
|
||||
$daten1 = (($w1 * 128 + $w2) );
|
||||
if ($daten1 >= 16085)
|
||||
{
|
||||
$daten1 = $daten1 - 16384;
|
||||
}
|
||||
$daten1 = $daten1 / 10;
|
||||
$shortname='I'.$snr;
|
||||
$daten2 = $w3;
|
||||
$daten3 = $w4 * 128 + $w5;
|
||||
$einheit1 = " C";
|
||||
$einheit2 = " %";
|
||||
$einheit3 = " hPa";
|
||||
$result = $shortname
|
||||
. " => T:" . $daten1 . $einheit1
|
||||
. ", H:" . $daten2 . $einheit2
|
||||
. ", D:" . $daten3 . $einheit3;
|
||||
|
||||
}
|
||||
case 5 {
|
||||
$snr -= 8 if $snr>7;; #only V1.2 sensors exists
|
||||
$sensor = "Helligkeitssensor V1.2(" . $snr . ")";
|
||||
$shortname='L'.$snr;
|
||||
switch ($w3) {
|
||||
case 0 {$daten1 = 1;}
|
||||
case 1 {$daten1 = 10;}
|
||||
case 2 {$daten1 = 100;}
|
||||
case 3 {$daten1 = 1000;}
|
||||
}
|
||||
$daten1 = $daten1 * ($w1 * 128 + $w2);
|
||||
$einheit1 = "Lux";
|
||||
$result = $shortname . " => L:" . $daten1 . $einheit1;
|
||||
|
||||
}
|
||||
case 6 {
|
||||
#Sensor has been never produced, but maybe there are personal implementations
|
||||
$snr -= 8 if $snr>7;
|
||||
$sensor = "Pyranometer V1.2(" . $snr . ")";
|
||||
$shortname='P'.$snr;
|
||||
switch ($w3) {
|
||||
case 0 {$daten1 = 1;}
|
||||
case 1 {$daten1 = 10;}
|
||||
case 2 {$daten1 = 100;}
|
||||
case 3 {$daten1 = 1000;}
|
||||
}
|
||||
$daten1 = $daten1 * ($w1 * 128 + $w2);
|
||||
$einheit1 = " W/m2";
|
||||
$result = $shortname . " => P:" . $daten1 . $einheit1;
|
||||
|
||||
}
|
||||
else {
|
||||
$shortname="U";
|
||||
$sensor = "unknown";
|
||||
$daten1 = $typ;
|
||||
$result = "(Group:" . $group . "/Typ:" . $typ . ")";
|
||||
Log 1, "$name => Unknown sensor detected". $result
|
||||
}#switch else
|
||||
|
||||
}#switch
|
||||
|
||||
#store result
|
||||
Log 4, $name." result:".$result;
|
||||
$rawtext='RAW => '.$rawtext;
|
||||
$hash->{READINGS}{LAST}{VAL}=$result;
|
||||
$hash->{READINGS}{LAST}{TIME}=$tm;
|
||||
$hash->{READINGS}{RAW}{TIME}=time();
|
||||
$hash->{READINGS}{RAW}{VAL}=$msg;
|
||||
$hash->{READINGS}{$shortname}{VAL}=$result;
|
||||
$hash->{READINGS}{$shortname}{TIME}=$tm;
|
||||
$hash->{STATE}=$result;
|
||||
$hash->{CHANGED}[0] = $result;
|
||||
$hash->{CHANGETIME}[0]=$tm;
|
||||
$hash->{CHANGED}[1] = $rawtext;
|
||||
$hash->{CHANGETIME}[1]=$tm;
|
||||
#notify system
|
||||
DoTrigger($name, undef);
|
||||
return $result;
|
||||
}
|
||||
#####################################
|
||||
sub
|
||||
WS2000_List($$)
|
||||
{
|
||||
my ($hash,$msg) = @_;
|
||||
$msg=WS2000_Get($hash,$hash->{NAME},'list');
|
||||
return $msg;
|
||||
}
|
||||
|
||||
|
||||
1;
|
|
@ -1,150 +0,0 @@
|
|||
|
||||
package main;
|
||||
##############################################
|
||||
# 88_IPWE.pm
|
||||
# Modul for FHEM
|
||||
#
|
||||
# contributed by thomas dressler 2008
|
||||
# $Id: 88_IPWE.pm,v 1.1 2008-05-18 12:05:24 tdressler Exp $
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use IO::Socket::INET;
|
||||
|
||||
use vars qw {%attr $init_done}; #make komodo happy
|
||||
sub Log($$);
|
||||
#####################################
|
||||
|
||||
sub
|
||||
IPWE_Initialize($)
|
||||
{
|
||||
my ($hash) = @_;
|
||||
# Consumer
|
||||
$hash->{DefFn} = "IPWE_Define";
|
||||
$hash->{GetFn} = "IPWE_Get";
|
||||
$hash->{AttrList}= "model:ipwe delay loglevel:0,1,2,3,4,5,6";
|
||||
}
|
||||
|
||||
#####################################
|
||||
|
||||
sub
|
||||
IPWE_Define($$)
|
||||
{
|
||||
my ($hash, $def) = @_;
|
||||
my $name=$hash->{NAME};
|
||||
my @a = split("[ \t][ \t]*", $def);
|
||||
Log 5, "IPWE Define: $a[0] $a[1] $a[2] $a[3]";
|
||||
return "Define the host as a parameter i.e. ipwe" if(@a < 3);
|
||||
|
||||
my $host = $a[2];
|
||||
my $delay=$a[3];
|
||||
$attr{$name}{delay}=$delay if $delay;
|
||||
Log 1, "ipwe device is none, commands will be echoed only" if($host eq "none");
|
||||
|
||||
my $socket = IO::Socket::INET->new(PeerAddr=>$host,
|
||||
PeerPort=>80, #http
|
||||
timeout=>2,
|
||||
blocking=>1
|
||||
);
|
||||
|
||||
if (!$socket) {
|
||||
$hash->{STATE} = "error opening device";
|
||||
Log 1,"$name: Error opening Connection to $host";
|
||||
return "Can't Connect to $host -> $@ ( $!)\n";
|
||||
}
|
||||
$socket->close;
|
||||
$hash->{Host} = $host;
|
||||
$hash->{STATE} = "Initialized";
|
||||
InternalTimer(gettimeofday()+$delay, "IPWE_GetStatus", $hash, 0);
|
||||
return undef;
|
||||
|
||||
}
|
||||
|
||||
sub IPWE_Get($@)
|
||||
{
|
||||
my ($hash, @a) = @_;
|
||||
return "argument is missing" if(int(@a) != 2);
|
||||
my $msg;
|
||||
$hash->{LOCAL} = 1;
|
||||
my $v = IPWE_GetStatus($hash);
|
||||
delete $hash->{LOCAL};
|
||||
my @data=split (/\n/, $v);
|
||||
if($a[1] eq "status") {
|
||||
$msg= "$a[0] $a[1] =>".$/."$v";
|
||||
}else {
|
||||
my ($l)= grep {/$a[1]/}@data;
|
||||
chop($l);
|
||||
$msg="$a[0] $a[1] =>$l";
|
||||
}
|
||||
$msg="$a[0]: Unknown get command $a[1]" if (!$msg);
|
||||
return $msg;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#####################################
|
||||
|
||||
sub
|
||||
IPWE_GetStatus($)
|
||||
{
|
||||
my ($hash) = @_;
|
||||
|
||||
my $buf;
|
||||
Log 5, "IPWE_GetStatus";
|
||||
my $name = $hash->{NAME};
|
||||
my $host = $hash->{Host};
|
||||
my $text='';
|
||||
my $alldata='';
|
||||
|
||||
my $delay=$attr{$name}{delay}||300;
|
||||
InternalTimer(gettimeofday()+$delay, "IPWE_GetStatus", $hash, 0);
|
||||
my $socket = IO::Socket::INET->new(PeerAddr=>$host,
|
||||
PeerPort=>80, #http
|
||||
timeout=>2,
|
||||
blocking=>1
|
||||
);
|
||||
|
||||
if (!$socket) {
|
||||
$hash->{STATE} = "error opening device";
|
||||
Log 1,"$name: Error opening Connection to $host";
|
||||
return "Can't Connect to $host -> $@ ( $!)\n";
|
||||
}
|
||||
Log 5, "$name: Connected to $host";
|
||||
|
||||
$socket->autoflush(1);
|
||||
$socket->write("GET /ipwe.cgi HTTP/1.0\r\n");
|
||||
my @lines=$socket->getlines();
|
||||
close $socket;
|
||||
Log 5,"$name: Data received";
|
||||
|
||||
my $allines=join('',@lines);
|
||||
my (@tables)= ($allines=~m#<tbody>(?:(?!<tbody>).)*</tbody>#sgi);
|
||||
my ($datatable)=grep{/Sensortyp/} @tables;
|
||||
my (@rows)=($datatable=~m#<tr>(?:(?!<tr>).)*</tr>#sgi);
|
||||
foreach my $l(@rows) {
|
||||
next if ($l=~/Sensortyp/); #headline
|
||||
my ($typ,$id,$sensor,$temp,$hum,$wind,$rain)=($l=~m#<td.*?>(.*?)<br></td>#sgi);
|
||||
next if ($typ=~/^\s+$/);
|
||||
$text= "Typ: $typ, ID: $id, Name $sensor, T: $temp H: $hum";
|
||||
if ($id == 8) {
|
||||
$text.= ",W: $wind, R: $rain";
|
||||
}
|
||||
Log 5,"$name: $text";
|
||||
if (!$hash->{local}){
|
||||
$hash->{CHANGED}[0] = $text;
|
||||
$hash->{READINGS}{$sensor}{TIME} = TimeNow();
|
||||
$hash->{READINGS}{$sensor}{VAL} = $text;;
|
||||
DoTrigger($name, undef) if($init_done);
|
||||
}
|
||||
|
||||
$alldata.="$text\n";
|
||||
}
|
||||
return $alldata;
|
||||
}
|
||||
|
||||
|
||||
1;
|
||||
|
|
@ -1,150 +0,0 @@
|
|||
##############################################
|
||||
package main;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use Time::HiRes qw(gettimeofday);
|
||||
|
||||
#####################################
|
||||
sub
|
||||
at_Initialize($)
|
||||
{
|
||||
my ($hash) = @_;
|
||||
|
||||
$hash->{DefFn} = "at_Define";
|
||||
$hash->{UndefFn} = "at_Undef";
|
||||
$hash->{AttrFn} = "at_Attr";
|
||||
$hash->{AttrList} = "disable:0,1 skip_next:0,1 loglevel:0,1,2,3,4,5,6";
|
||||
}
|
||||
|
||||
|
||||
my $at_tdiff;
|
||||
|
||||
#####################################
|
||||
sub
|
||||
at_Define($$)
|
||||
{
|
||||
my ($hash, $def) = @_;
|
||||
my ($name, undef, $tm, $command) = split("[ \t]+", $def, 4);
|
||||
|
||||
if(!$command) {
|
||||
if($hash->{OLDDEF}) { # Called from modify, where command is optional
|
||||
RemoveInternalTimer($name);
|
||||
(undef, $command) = split("[ \t]+", $hash->{OLDDEF}, 2);
|
||||
$hash->{DEF} = "$tm $command";
|
||||
} else {
|
||||
return "Usage: define <name> at <timespec> <command>";
|
||||
}
|
||||
}
|
||||
return "Wrong timespec, use \"[+][*[{count}]]<time or func>\""
|
||||
if($tm !~ m/^(\+)?(\*({\d+})?)?(.*)$/);
|
||||
my ($rel, $rep, $cnt, $tspec) = ($1, $2, $3, $4);
|
||||
my ($err, $hr, $min, $sec, $fn) = GetTimeSpec($tspec);
|
||||
return $err if($err);
|
||||
|
||||
$rel = "" if(!defined($rel));
|
||||
$rep = "" if(!defined($rep));
|
||||
$cnt = "" if(!defined($cnt));
|
||||
|
||||
my $ot = gettimeofday();
|
||||
my @lt = localtime($ot);
|
||||
my $nt = $ot;
|
||||
|
||||
$nt -= ($lt[2]*3600+$lt[1]*60+$lt[0]) # Midnight for absolute time
|
||||
if($rel ne "+");
|
||||
$nt += ($hr*3600+$min*60+$sec); # Plus relative time
|
||||
$nt += 86400 if($ot >= $nt); # Do it tomorrow...
|
||||
$nt += $at_tdiff if(defined($at_tdiff));
|
||||
|
||||
@lt = localtime($nt);
|
||||
my $ntm = sprintf("%02d:%02d:%02d", $lt[2], $lt[1], $lt[0]);
|
||||
if($rep) { # Setting the number of repetitions
|
||||
$cnt =~ s/[{}]//g;
|
||||
return undef if($cnt eq "0");
|
||||
$cnt = 0 if(!$cnt);
|
||||
$cnt--;
|
||||
$hash->{REP} = $cnt;
|
||||
} else {
|
||||
$hash->{VOLATILE} = 1; # Write these entries to the statefile
|
||||
}
|
||||
$hash->{NTM} = $ntm if($rel eq "+" || $fn);
|
||||
$hash->{TRIGGERTIME} = $nt;
|
||||
RemoveInternalTimer($name);
|
||||
InternalTimer($nt, "at_Exec", $name, 0);
|
||||
|
||||
$hash->{STATE} = ("Next: " . FmtTime($nt))
|
||||
if(!($attr{$name} && $attr{$name}{disable}));
|
||||
|
||||
return undef;
|
||||
}
|
||||
|
||||
sub
|
||||
at_Undef($$)
|
||||
{
|
||||
my ($hash, $name) = @_;
|
||||
RemoveInternalTimer($name);
|
||||
return undef;
|
||||
}
|
||||
|
||||
sub
|
||||
at_Exec($)
|
||||
{
|
||||
my ($name) = @_;
|
||||
my ($skip, $disable) = ("","");
|
||||
|
||||
return if(!$defs{$name}); # Just deleted
|
||||
Log GetLogLevel($name,5), "exec at command $name";
|
||||
|
||||
if(defined($attr{$name})) {
|
||||
$skip = 1 if($attr{$name} && $attr{$name}{skip_next});
|
||||
$disable = 1 if($attr{$name} && $attr{$name}{disable});
|
||||
}
|
||||
|
||||
delete $attr{$name}{skip_next} if($skip);
|
||||
my (undef, $command) = split("[ \t]+", $defs{$name}{DEF}, 2);
|
||||
$command = SemicolonEscape($command);
|
||||
AnalyzeCommandChain(undef, $command) if(!$skip && !$disable);
|
||||
|
||||
return if(!$defs{$name}); # Deleted in the Command
|
||||
|
||||
my $count = $defs{$name}{REP};
|
||||
my $def = $defs{$name}{DEF};
|
||||
|
||||
my $oldattr = $attr{$name}; # delete removes the attributes too
|
||||
|
||||
# Correct drift when the timespec is relative
|
||||
$at_tdiff = $defs{$name}{TRIGGERTIME}-gettimeofday() if($def =~ m/^\+/);
|
||||
CommandDelete(undef, $name); # Recreate ourselves
|
||||
|
||||
if($count) {
|
||||
$def =~ s/{\d+}/{$count}/ if($def =~ m/^\+?\*{\d+}/); # Replace the count
|
||||
Log GetLogLevel($name,5), "redefine at command $name as $def";
|
||||
|
||||
$data{AT_RECOMPUTE} = 1; # Tell sunrise compute the next day
|
||||
CommandDefine(undef, "$name at $def"); # Recompute the next TRIGGERTIME
|
||||
delete($data{AT_RECOMPUTE});
|
||||
$attr{$name} = $oldattr;
|
||||
}
|
||||
$at_tdiff = undef;
|
||||
}
|
||||
|
||||
sub
|
||||
at_Attr(@)
|
||||
{
|
||||
my @a = @_;
|
||||
my $do = 0;
|
||||
|
||||
if($a[0] eq "set" && $a[2] eq "disable") {
|
||||
$do = (!defined($a[3]) || $a[3]) ? 1 : 2;
|
||||
}
|
||||
$do = 2 if($a[0] eq "del" && (!$a[2] || $a[2] eq "disable"));
|
||||
return if(!$do);
|
||||
|
||||
$defs{$a[1]}{STATE} = ($do == 1 ?
|
||||
"disabled" :
|
||||
"Next: " . FmtTime($defs{$a[1]}{TRIGGERTIME}));
|
||||
|
||||
return undef;
|
||||
}
|
||||
|
||||
1;
|
|
@ -1,103 +0,0 @@
|
|||
##############################################
|
||||
package main;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
#####################################
|
||||
sub
|
||||
notify_Initialize($)
|
||||
{
|
||||
my ($hash) = @_;
|
||||
|
||||
$hash->{DefFn} = "notify_Define";
|
||||
$hash->{NotifyFn} = "notify_Exec";
|
||||
$hash->{AttrFn} = "notify_Attr";
|
||||
$hash->{AttrList} = "disable:0,1";
|
||||
}
|
||||
|
||||
|
||||
#####################################
|
||||
sub
|
||||
notify_Define($$)
|
||||
{
|
||||
my ($hash, $def) = @_;
|
||||
my ($name, $type, $re, $command) = split("[ \t]+", $def, 4);
|
||||
|
||||
if(!$command) {
|
||||
if($hash->{OLDDEF}) { # Called from modify, where command is optional
|
||||
(undef, $command) = split("[ \t]+", $hash->{OLDDEF}, 2);
|
||||
$hash->{DEF} = "$re $command";
|
||||
} else {
|
||||
return "Usage: define <name> notify <regexp> <command>";
|
||||
}
|
||||
}
|
||||
|
||||
# Checking for misleading regexps
|
||||
eval { "Hallo" =~ m/^$re$/ };
|
||||
return "Bad regexp: $@" if($@);
|
||||
$hash->{REGEXP} = $re;
|
||||
$hash->{STATE} = "active";
|
||||
|
||||
return undef;
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub
|
||||
notify_Exec($$)
|
||||
{
|
||||
my ($ntfy, $dev) = @_;
|
||||
|
||||
my $ln = $ntfy->{NAME};
|
||||
return "" if($attr{$ln} && $attr{$ln}{disable});
|
||||
|
||||
my $n = $dev->{NAME};
|
||||
my $re = $ntfy->{REGEXP};
|
||||
my $max = int(@{$dev->{CHANGED}});
|
||||
my $t = $dev->{TYPE};
|
||||
|
||||
my $ret = "";
|
||||
for (my $i = 0; $i < $max; $i++) {
|
||||
my $s = $dev->{CHANGED}[$i];
|
||||
$s = "" if(!defined($s));
|
||||
if($n =~ m/^$re$/ || "$n:$s" =~ m/^$re$/) {
|
||||
my (undef, $exec) = split("[ \t]+", $ntfy->{DEF}, 2);
|
||||
$exec = SemicolonEscape($exec);
|
||||
|
||||
$exec =~ s/%%/____/g;
|
||||
my $extsyntax= 0;
|
||||
$extsyntax+= ($exec =~ s/%TYPE/$t/g);
|
||||
$extsyntax+= ($exec =~ s/%NAME/$n/g);
|
||||
$extsyntax+= ($exec =~ s/%EVENT/$s/g);
|
||||
if(!$extsyntax) {
|
||||
$exec =~ s/%/$s/g;
|
||||
}
|
||||
$exec =~ s/____/%/g;
|
||||
|
||||
$exec =~ s/@@/____/g;
|
||||
$exec =~ s/@/$n/g;
|
||||
$exec =~ s/____/@/g;
|
||||
|
||||
my $r = AnalyzeCommandChain(undef, $exec);
|
||||
$ret .= " $r" if($r);
|
||||
}
|
||||
}
|
||||
return $ret;
|
||||
}
|
||||
|
||||
sub
|
||||
notify_Attr(@)
|
||||
{
|
||||
my @a = @_;
|
||||
my $do = 0;
|
||||
|
||||
if($a[0] eq "set" && $a[2] eq "disable") {
|
||||
$do = (!defined($a[3]) || $a[3]) ? 1 : 2;
|
||||
}
|
||||
$do = 2 if($a[0] eq "del" && (!$a[2] || $a[2] eq "disable"));
|
||||
return if(!$do);
|
||||
|
||||
$defs{$a[1]}{STATE} = ($do == 1 ? "disabled" : "active");
|
||||
return undef;
|
||||
}
|
||||
1;
|
|
@ -1,117 +0,0 @@
|
|||
##############################################
|
||||
package main;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use Time::HiRes qw(gettimeofday);
|
||||
|
||||
#####################################
|
||||
sub
|
||||
sequence_Initialize($)
|
||||
{
|
||||
my ($hash) = @_;
|
||||
|
||||
$hash->{DefFn} = "sequence_Define";
|
||||
$hash->{UndefFn} = "sequence_Undef";
|
||||
$hash->{NotifyFn} = "sequence_Notify";
|
||||
$hash->{AttrList} = "disable:0,1 loglevel:0,1,2,3,4,5,6";
|
||||
}
|
||||
|
||||
|
||||
#####################################
|
||||
# define sq1 sequence reg1 [timeout reg2]
|
||||
sub
|
||||
sequence_Define($$)
|
||||
{
|
||||
my ($hash, $def) = @_;
|
||||
my @def = split("[ \t]+", $def);
|
||||
|
||||
my $name = shift(@def);
|
||||
my $type = shift(@def);
|
||||
|
||||
return "Usage: define <name> sequence <re1> <timeout1> <re2> ".
|
||||
"[<timeout2> <re3> ...]"
|
||||
if(int(@def) % 2 == 0 || int(@def) < 3);
|
||||
|
||||
# "Syntax" checking
|
||||
for(my $i = 0; $i < int(@def); $i += 2) {
|
||||
my $re = $def[$i];
|
||||
my $to = $def[$i+1];
|
||||
eval { "Hallo" =~ m/^$re$/ };
|
||||
return "Bad regexp 1: $@" if($@);
|
||||
return "Bad timeout spec $to"
|
||||
if(defined($to) && $to !~ m/^\d*.?\d$/);
|
||||
}
|
||||
|
||||
$hash->{RE} = $def[0];
|
||||
$hash->{IDX} = 0;
|
||||
$hash->{MAX} = int(@def);
|
||||
$hash->{STATE} = "initialized";
|
||||
return undef;
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub
|
||||
sequence_Notify($$)
|
||||
{
|
||||
my ($hash, $dev) = @_;
|
||||
|
||||
my $ln = $hash->{NAME};
|
||||
return "" if($attr{$ln} && $attr{$ln}{disable});
|
||||
|
||||
my $n = $dev->{NAME};
|
||||
my $re = $hash->{RE};
|
||||
my $max = int(@{$dev->{CHANGED}});
|
||||
|
||||
for (my $i = 0; $i < $max; $i++) {
|
||||
my $s = $dev->{CHANGED}[$i];
|
||||
$s = "" if(!defined($s));
|
||||
next if($n !~ m/^$re$/ && "$n:$s" !~ m/^$re$/);
|
||||
|
||||
RemoveInternalTimer($ln);
|
||||
my $idx = $hash->{IDX} + 2;
|
||||
Log GetLogLevel($ln,5), "sequence $ln matched $idx";
|
||||
my @d = split("[ \t]+", $hash->{DEF});
|
||||
|
||||
|
||||
if($idx > $hash->{MAX}) { # Last element reached
|
||||
|
||||
Log GetLogLevel($ln,5), "sequence $ln triggered";
|
||||
DoTrigger($ln, "trigger");
|
||||
$idx = 0;
|
||||
|
||||
} else {
|
||||
|
||||
$hash->{RE} = $d[$idx];
|
||||
my $nt = gettimeofday() + $d[$idx-1];
|
||||
InternalTimer($nt, "sequence_Trigger", $ln, 0);
|
||||
|
||||
}
|
||||
|
||||
$hash->{IDX} = $idx;
|
||||
$hash->{RE} = $d[$idx];
|
||||
last;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
sub
|
||||
sequence_Trigger($)
|
||||
{
|
||||
my ($ln) = @_;
|
||||
my $hash = $defs{$ln};
|
||||
my @d = split("[ \t]+", $hash->{DEF});
|
||||
$hash->{RE} = $d[0];
|
||||
$hash->{IDX} = 0;
|
||||
Log GetLogLevel($ln,5), "sequence $ln timeout";
|
||||
}
|
||||
|
||||
sub
|
||||
sequence_Undef($$)
|
||||
{
|
||||
my ($hash, $name) = @_;
|
||||
RemoveInternalTimer($name);
|
||||
return undef;
|
||||
}
|
||||
|
||||
1;
|
|
@ -1,121 +0,0 @@
|
|||
##############################################
|
||||
package main;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use Time::HiRes qw(gettimeofday);
|
||||
|
||||
#####################################
|
||||
sub
|
||||
watchdog_Initialize($)
|
||||
{
|
||||
my ($hash) = @_;
|
||||
|
||||
$hash->{DefFn} = "watchdog_Define";
|
||||
$hash->{UndefFn} = "watchdog_Undef";
|
||||
$hash->{NotifyFn} = "watchdog_Notify";
|
||||
$hash->{AttrList} = "disable:0,1";
|
||||
}
|
||||
|
||||
|
||||
#####################################
|
||||
# defined watchme watchdog reg1 timeout reg2 command
|
||||
sub
|
||||
watchdog_Define($$)
|
||||
{
|
||||
my ($ntfy, $def) = @_;
|
||||
my ($name, $type, $re1, $to, $re2, $command) = split("[ \t]+", $def, 6);
|
||||
|
||||
return "Usage: define <name> watchdog <re1> <timeout> <re2> <command>"
|
||||
if(!$command);
|
||||
|
||||
# Checking for misleading regexps
|
||||
eval { "Hallo" =~ m/^$re1$/ };
|
||||
return "Bad regexp 1: $@" if($@);
|
||||
$re2 = $re1 if($re2 eq "SAME");
|
||||
eval { "Hallo" =~ m/^$re2$/ };
|
||||
return "Bad regexp 2: $@" if($@);
|
||||
|
||||
return "Wrong timespec, must be HH:MM[:SS]"
|
||||
if($to !~ m/^(\d\d):(\d\d)(:\d\d)?$/);
|
||||
$to = $1*3600+$2*60+($3 ? substr($3,1) : 0);
|
||||
|
||||
$ntfy->{RE1} = $re1;
|
||||
$ntfy->{RE2} = $re2;
|
||||
$ntfy->{TO} = $to;
|
||||
$ntfy->{CMD} = $command;
|
||||
|
||||
|
||||
$ntfy->{STATE} = ($re1 eq ".") ? "active" : "defined";
|
||||
watchdog_Activate($ntfy) if($ntfy->{STATE} eq "active");
|
||||
|
||||
return undef;
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub
|
||||
watchdog_Notify($$)
|
||||
{
|
||||
my ($ntfy, $dev) = @_;
|
||||
|
||||
my $ln = $ntfy->{NAME};
|
||||
return "" if($attr{$ln} && $attr{$ln}{disable});
|
||||
return "" if($ntfy->{INWATCHDOG});
|
||||
|
||||
my $n = $dev->{NAME};
|
||||
my $re1 = $ntfy->{RE1};
|
||||
my $re2 = $ntfy->{RE2};
|
||||
my $max = int(@{$dev->{CHANGED}});
|
||||
|
||||
for (my $i = 0; $i < $max; $i++) {
|
||||
my $s = $dev->{CHANGED}[$i];
|
||||
$s = "" if(!defined($s));
|
||||
|
||||
if($ntfy->{STATE} =~ m/Next:/) {
|
||||
if($n =~ m/^$re2$/ || "$n:$s" =~ m/^$re2$/) {
|
||||
RemoveInternalTimer($ntfy);
|
||||
if($re1 eq $re2) {
|
||||
watchdog_Activate($ntfy);
|
||||
} else {
|
||||
$ntfy->{STATE} = "defined";
|
||||
}
|
||||
}
|
||||
} elsif($n =~ m/^$re1$/ || "$n:$s" =~ m/^$re1$/) {
|
||||
watchdog_Activate($ntfy);
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
sub
|
||||
watchdog_Trigger($)
|
||||
{
|
||||
my ($ntfy) = @_;
|
||||
Log(3, "Watchdog $ntfy->{NAME} triggered");
|
||||
my $exec = SemicolonEscape($ntfy->{CMD});;
|
||||
$ntfy->{STATE} = "triggered";
|
||||
$ntfy->{INWATCHDOG} = 1;
|
||||
AnalyzeCommandChain(undef, $exec);
|
||||
$ntfy->{INWATCHDOG} = 0;
|
||||
}
|
||||
|
||||
sub
|
||||
watchdog_Activate($)
|
||||
{
|
||||
my ($ntfy) = @_;
|
||||
my $nt = gettimeofday() + $ntfy->{TO};
|
||||
$ntfy->{STATE} = "Next: " . FmtTime($nt);
|
||||
RemoveInternalTimer($ntfy);
|
||||
InternalTimer($nt, "watchdog_Trigger", $ntfy, 0)
|
||||
}
|
||||
|
||||
sub
|
||||
watchdog_Undef($$)
|
||||
{
|
||||
my ($hash, $name) = @_;
|
||||
RemoveInternalTimer($hash);
|
||||
return undef;
|
||||
}
|
||||
|
||||
|
||||
1;
|
|
@ -1,414 +0,0 @@
|
|||
##############################################
|
||||
package main;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use IO::File;
|
||||
#use Devel::Size qw(size total_size);
|
||||
|
||||
sub seekTo($$$$);
|
||||
|
||||
#####################################
|
||||
sub
|
||||
FileLog_Initialize($)
|
||||
{
|
||||
my ($hash) = @_;
|
||||
|
||||
$hash->{DefFn} = "FileLog_Define";
|
||||
$hash->{SetFn} = "FileLog_Set";
|
||||
$hash->{GetFn} = "FileLog_Get";
|
||||
$hash->{UndefFn} = "FileLog_Undef";
|
||||
$hash->{NotifyFn} = "FileLog_Log";
|
||||
$hash->{AttrFn} = "FileLog_Attr";
|
||||
# logtype is used by the frontend
|
||||
$hash->{AttrList} = "disable:0,1 logtype nrarchive archivedir archivecmd";
|
||||
}
|
||||
|
||||
|
||||
#####################################
|
||||
sub
|
||||
FileLog_Define($@)
|
||||
{
|
||||
my ($hash, $def) = @_;
|
||||
my @a = split("[ \t][ \t]*", $def);
|
||||
my $fh;
|
||||
|
||||
return "wrong syntax: define <name> FileLog filename regexp" if(int(@a) != 4);
|
||||
|
||||
eval { "Hallo" =~ m/^$a[3]$/ };
|
||||
return "Bad regexp: $@" if($@);
|
||||
|
||||
my @t = localtime;
|
||||
my $f = ResolveDateWildcards($a[2], @t);
|
||||
$fh = new IO::File ">>$f";
|
||||
return "Can't open $f: $!" if(!defined($fh));
|
||||
|
||||
$hash->{FH} = $fh;
|
||||
$hash->{REGEXP} = $a[3];
|
||||
$hash->{logfile} = $a[2];
|
||||
$hash->{currentlogfile} = $f;
|
||||
$hash->{STATE} = "active";
|
||||
|
||||
return undef;
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub
|
||||
FileLog_Undef($$)
|
||||
{
|
||||
my ($hash, $name) = @_;
|
||||
close($hash->{FH});
|
||||
return undef;
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub
|
||||
FileLog_Log($$)
|
||||
{
|
||||
# Log is my entry, Dev is the entry of the changed device
|
||||
my ($log, $dev) = @_;
|
||||
|
||||
my $ln = $log->{NAME};
|
||||
return if($attr{$ln} && $attr{$ln}{disable});
|
||||
|
||||
my $n = $dev->{NAME};
|
||||
my $re = $log->{REGEXP};
|
||||
my $max = int(@{$dev->{CHANGED}});
|
||||
for (my $i = 0; $i < $max; $i++) {
|
||||
my $s = $dev->{CHANGED}[$i];
|
||||
$s = "" if(!defined($s));
|
||||
if($n =~ m/^$re$/ || "$n:$s" =~ m/^$re$/) {
|
||||
my $t = TimeNow();
|
||||
$t = $dev->{CHANGETIME}[$i] if(defined($dev->{CHANGETIME}[$i]));
|
||||
$t =~ s/ /_/; # Makes it easier to parse with gnuplot
|
||||
|
||||
my $fh = $log->{FH};
|
||||
my @t = localtime;
|
||||
my $cn = ResolveDateWildcards($log->{logfile}, @t);
|
||||
|
||||
if($cn ne $log->{currentlogfile}) { # New logfile
|
||||
$fh->close();
|
||||
HandleArchiving($log);
|
||||
$fh = new IO::File ">>$cn";
|
||||
if(!defined($fh)) {
|
||||
Log(0, "Can't open $cn");
|
||||
return;
|
||||
}
|
||||
$log->{currentlogfile} = $cn;
|
||||
$log->{FH} = $fh;
|
||||
}
|
||||
|
||||
print $fh "$t $n $s\n";
|
||||
$fh->flush;
|
||||
$fh->sync if !($^O eq 'MSWin32'); #not implemented in Windows
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
###################################
|
||||
sub
|
||||
FileLog_Attr(@)
|
||||
{
|
||||
my @a = @_;
|
||||
my $do = 0;
|
||||
|
||||
if($a[0] eq "set" && $a[2] eq "disable") {
|
||||
$do = (!defined($a[3]) || $a[3]) ? 1 : 2;
|
||||
}
|
||||
$do = 2 if($a[0] eq "del" && (!$a[2] || $a[2] eq "disable"));
|
||||
return if(!$do);
|
||||
|
||||
$defs{$a[1]}{STATE} = ($do == 1 ? "disabled" : "active");
|
||||
|
||||
return undef;
|
||||
}
|
||||
|
||||
###################################
|
||||
sub
|
||||
FileLog_Set($@)
|
||||
{
|
||||
my ($hash, @a) = @_;
|
||||
|
||||
return "no set argument specified" if(int(@a) != 2);
|
||||
return "Unknown argument $a[1], choose one of reopen"
|
||||
if($a[1] ne "reopen");
|
||||
|
||||
my $fh = $hash->{FH};
|
||||
my $cn = $hash->{currentlogfile};
|
||||
$fh->close();
|
||||
$fh = new IO::File ">>$cn";
|
||||
return "Can't open $cn" if(!defined($fh));
|
||||
$hash->{FH} = $fh;
|
||||
return undef;
|
||||
}
|
||||
|
||||
###################################
|
||||
# We use this function to be able to scroll/zoom in the plots created from the
|
||||
# logfile. When outfile is specified, it is used with gnuplot post-processing,
|
||||
# when outfile is "-" it is used to create SVG graphics
|
||||
#
|
||||
# Up till now following functions are impemented:
|
||||
# - int (to cut off % from a number, as for the actuator)
|
||||
# - delta-h / delta-d to get rain/h and rain/d values from continuous data.
|
||||
#
|
||||
# It will set the %data values
|
||||
# min<x>, max<x>, avg<x>, cnt<x>, lastd<x>, lastv<x>
|
||||
# for each requested column, beggining with <x> = 1
|
||||
|
||||
sub
|
||||
FileLog_Get($@)
|
||||
{
|
||||
my ($hash, @a) = @_;
|
||||
|
||||
return "Usage: get $a[0] <infile> <outfile> <from> <to> <column_spec>...\n".
|
||||
" where column_spec is <col>:<regexp>:<default>:<fn>\n" .
|
||||
" see the FileLogGrep entries in he .gplot files\n" .
|
||||
" <infile> is without direcory, - means the current file\n" .
|
||||
" <outfile> is a prefix, - means stdout\n"
|
||||
if(int(@a) < 5);
|
||||
|
||||
shift @a;
|
||||
my $inf = shift @a;
|
||||
my $outf = shift @a;
|
||||
my $from = shift @a;
|
||||
my $to = shift @a; # Now @a contains the list of column_specs
|
||||
my $internal;
|
||||
if($outf eq "INT") {
|
||||
$outf = "-";
|
||||
$internal = 1;
|
||||
}
|
||||
|
||||
if($inf eq "-") {
|
||||
$inf = $hash->{currentlogfile};
|
||||
} else {
|
||||
my $linf = "$1/$inf" if($hash->{currentlogfile} =~ m,^(.*)/[^/]*$,);
|
||||
if(!-f $linf) {
|
||||
$linf = $attr{$hash->{NAME}}{archivedir} . "/" . $inf;
|
||||
return "Error: File-not-found" if(!-f $linf);
|
||||
}
|
||||
$inf = $linf;
|
||||
}
|
||||
my $ifh = new IO::File $inf;
|
||||
seekTo($inf, $ifh, $hash, $from);
|
||||
|
||||
#############
|
||||
# Digest the input.
|
||||
# last1: first delta value after d/h change
|
||||
# last2: last delta value recorded (for the very last entry)
|
||||
# last3: last delta timestamp (d or h)
|
||||
my (@d, @fname);
|
||||
my (@min, @max, @sum, @cnt, @lastv, @lastd);
|
||||
|
||||
for(my $i = 0; $i < int(@a); $i++) {
|
||||
my @fld = split(":", $a[$i], 4);
|
||||
|
||||
my %h;
|
||||
if($outf ne "-") {
|
||||
$fname[$i] = "$outf.$i";
|
||||
$h{fh} = new IO::File "> $fname[$i]";
|
||||
}
|
||||
$h{re} = $fld[1]; # Filter: regexp
|
||||
$h{df} = defined($fld[2]) ? $fld[2] : ""; # default value
|
||||
$h{fn} = $fld[3]; # function
|
||||
$h{didx} = 10 if($fld[3] && $fld[3] eq "delta-d"); # delta idx, substr len
|
||||
$h{didx} = 13 if($fld[3] && $fld[3] eq "delta-h");
|
||||
|
||||
if($fld[0] =~ m/"(.*)"/) {
|
||||
$h{col} = $1;
|
||||
$h{type} = 0;
|
||||
} else {
|
||||
$h{col} = $fld[0]-1;
|
||||
$h{type} = 1;
|
||||
}
|
||||
if($h{fn}) {
|
||||
$h{type} = 4;
|
||||
$h{type} = 2 if($h{didx});
|
||||
$h{type} = 3 if($h{fn} eq "int");
|
||||
}
|
||||
$h{ret} = "";
|
||||
$d[$i] = \%h;
|
||||
$min[$i] = 999999;
|
||||
$max[$i] = -999999;
|
||||
$sum[$i] = 0;
|
||||
$cnt[$i] = 0;
|
||||
$lastv[$i] = 0;
|
||||
$lastd[$i] = "undef";
|
||||
}
|
||||
|
||||
my %lastdate;
|
||||
my $d; # Used by eval functions
|
||||
while(my $l = <$ifh>) {
|
||||
next if($l lt $from);
|
||||
last if($l gt $to);
|
||||
my @fld = split("[ \r\n]+", $l); # 40%
|
||||
|
||||
for my $i (0..int(@a)-1) { # Process each req. field
|
||||
my $h = $d[$i];
|
||||
next if($h->{re} && $l !~ m/$h->{re}/); # 20%
|
||||
|
||||
my $col = $h->{col};
|
||||
my $t = $h->{type};
|
||||
|
||||
|
||||
my $val = undef;
|
||||
my $dte = $fld[0];
|
||||
|
||||
if($t == 0) { # Fixed text
|
||||
$val = $col;
|
||||
|
||||
} elsif($t == 1) { # The column
|
||||
$val = $fld[$col] if(defined($fld[$col]));
|
||||
|
||||
} elsif($t == 2) { # delta-h or delta-d
|
||||
|
||||
my $hd = $h->{didx};
|
||||
my $ld = substr($fld[0],0,$hd);
|
||||
if(!defined($h->{last1}) || $h->{last3} ne $ld) {
|
||||
if(defined($h->{last1})) {
|
||||
my @lda = split("[_:]", $lastdate{$hd});
|
||||
my $ts = "12:00:00"; # middle timestamp
|
||||
$ts = "$lda[1]:30:00" if($hd == 13);
|
||||
my $v = $fld[$col]-$h->{last1};
|
||||
$v = 0 if($v < 0); # Skip negative delta
|
||||
$dte = "$lda[0]_$ts";
|
||||
$val = sprintf("%0.1f", $v);
|
||||
}
|
||||
$h->{last1} = $fld[$col];
|
||||
$h->{last3} = $ld;
|
||||
}
|
||||
$h->{last2} = $fld[$col];
|
||||
$lastdate{$hd} = $fld[0];
|
||||
|
||||
} elsif($t == 3) { # int function
|
||||
$val = $1 if($fld[$col] =~ m/^([0-9]+).*/);
|
||||
|
||||
} else { # evaluate
|
||||
$val = eval($h->{fn});
|
||||
|
||||
}
|
||||
|
||||
next if(!defined($val));
|
||||
$min[$i] = $val if($val < $min[$i]);
|
||||
$max[$i] = $val if($val > $max[$i]);
|
||||
$sum[$i] += $val;
|
||||
$cnt[$i]++;
|
||||
$lastv[$i] = $val;
|
||||
$lastd[$i] = $dte;
|
||||
|
||||
if($outf eq "-") {
|
||||
$h->{ret} .= "$dte $val\n";
|
||||
} else {
|
||||
my $fh = $h->{fh}; # cannot use $h->{fh} in print directly
|
||||
print $fh "$dte $val\n";
|
||||
$h->{count}++;
|
||||
}
|
||||
}
|
||||
}
|
||||
$ifh->close();
|
||||
|
||||
my $ret = "";
|
||||
for(my $i = 0; $i < int(@a); $i++) {
|
||||
my $h = $d[$i];
|
||||
my $hd = $h->{didx};
|
||||
if($hd && $lastdate{$hd}) {
|
||||
my $val = defined($h->{last1}) ? $h->{last2}-$h->{last1} : 0;
|
||||
|
||||
my @lda = split("[_:]", $lastdate{$hd});
|
||||
my $ts = "12:00:00"; # middle timestamp
|
||||
$ts = "$lda[1]:30:00" if($hd == 13);
|
||||
my $line = sprintf("%s_%s %0.1f\n", $lda[0],$ts, $h->{last2}-$h->{last1});
|
||||
|
||||
if($outf eq "-") {
|
||||
$h->{ret} .= $line;
|
||||
} else {
|
||||
my $fh = $h->{fh};
|
||||
print $fh $line;
|
||||
$h->{count}++;
|
||||
}
|
||||
}
|
||||
if($outf eq "-") {
|
||||
$h->{ret} .= "$from $h->{df}\n" if(!$h->{ret} && $h->{df} ne "");
|
||||
$ret .= $h->{ret} if($h->{ret});
|
||||
$ret .= "#$a[$i]\n";
|
||||
} else {
|
||||
my $fh = $h->{fh};
|
||||
if(!$h->{count} && $h->{df} ne "") {
|
||||
print $fh "$from $h->{df}\n";
|
||||
}
|
||||
$fh->close();
|
||||
}
|
||||
|
||||
my $j = $i+1;
|
||||
$data{"min$j"} = $min[$i] == 999999 ? "undef" : $min[$i];
|
||||
$data{"max$j"} = $max[$i] == -999999 ? "undef" : $max[$i];
|
||||
$data{"avg$j"} = $cnt[$i] ? sprintf("%0.1f", $sum[$i]/$cnt[$i]) : "undef";
|
||||
$data{"cnt$j"} = $cnt[$i] ? $cnt[$i] : "undef";
|
||||
$data{"currval$j"} = $lastv[$i];
|
||||
$data{"currdate$j"} = $lastd[$i];
|
||||
|
||||
}
|
||||
if($internal) {
|
||||
$internal_data = \$ret;
|
||||
return undef;
|
||||
}
|
||||
|
||||
return ($outf eq "-") ? $ret : join(" ", @fname);
|
||||
}
|
||||
|
||||
###################################
|
||||
sub
|
||||
seekTo($$$$)
|
||||
{
|
||||
my ($fname, $fh, $hash, $ts) = @_;
|
||||
|
||||
# If its cached
|
||||
if($hash->{pos} && $hash->{pos}{"$fname:$ts"}) {
|
||||
$fh->seek($hash->{pos}{"$fname:$ts"}, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
$fh->seek(0, 2); # Go to the end
|
||||
my $upper = $fh->tell;
|
||||
|
||||
my ($lower, $next, $last) = (0, $upper/2, 0);
|
||||
my $div = 2;
|
||||
while() { # Binary search
|
||||
$fh->seek($next, 0);
|
||||
my $data = <$fh>;
|
||||
if(!$data) {
|
||||
$last = $next;
|
||||
last;
|
||||
}
|
||||
if($data !~ m/^\d\d\d\d-\d\d-\d\d_\d\d:\d\d:\d\d /) {
|
||||
$next = $fh->tell;
|
||||
$data = <$fh>;
|
||||
if(!$data) {
|
||||
$last = $next;
|
||||
last;
|
||||
}
|
||||
|
||||
# If the second line is longer then the first,
|
||||
# binary search will never get it:
|
||||
if($next eq $last && $data ge $ts && $div < 8192) {
|
||||
$last = 0;
|
||||
$div *= 2;
|
||||
}
|
||||
}
|
||||
if($next eq $last) {
|
||||
$fh->seek($next, 0);
|
||||
last;
|
||||
}
|
||||
|
||||
$last = $next;
|
||||
if(!$data || $data lt $ts) {
|
||||
($lower, $next) = ($next, int(($next+$upper)/$div));
|
||||
} else {
|
||||
($upper, $next) = ($next, int(($lower+$next)/$div));
|
||||
}
|
||||
}
|
||||
$hash->{pos}{"$fname:$ts"} = $last;
|
||||
|
||||
}
|
||||
|
||||
1;
|
|
@ -1,265 +0,0 @@
|
|||
#######################################################################
|
||||
#
|
||||
# 95_PachLog.pm
|
||||
#
|
||||
# Logging to www.pachube.com
|
||||
# Autor: a[PUNKT]r[BEI]oo2p[PUNKT]net
|
||||
# Stand: 09.09.2009
|
||||
# Version: 0.9
|
||||
#######################################################################
|
||||
# Vorausetzung: Account bei www.pachube.com mit API-Key
|
||||
#######################################################################
|
||||
#
|
||||
# FHEM: Neues Pachube-Device erstelle: define <NAME> PachLog API-Key
|
||||
# "define PACH001 PachLog 1234kliceee77hgtzuippkk99"
|
||||
#
|
||||
# PACHUBE: FEED erstellen -> FEED-NR: DATASTREAM-ID:TAGS
|
||||
# Beispiel: HMS_TF (Temperatur und Feuchte Sensor)
|
||||
# FEED-NR: 1234
|
||||
# ID 0 => Temperatur (temperature)
|
||||
# ID 1 => rel. Luftfeuchte (humidity)
|
||||
#
|
||||
# FHEM: PachLog-Devices: PACH01
|
||||
# HMS_DEVICE: HMS_TF01
|
||||
# FEED-NR: 1234
|
||||
# ID 0 => Temperatur (temperature)
|
||||
# ID 1 => rel. Luftfeuchte (humidity)
|
||||
# "set PACH01 ADD HMS_TF01 1234:0:temperature:1:humidity"
|
||||
#
|
||||
# Hinweise:
|
||||
# Ein FEED kann nur komplett upgedated werden:
|
||||
# FEED 3456 -> ID 0 -> DEVICE A
|
||||
# FEED 3456 -> ID 1 -> DEVICE B
|
||||
# => geht nicht
|
||||
#
|
||||
# Es werden nur READINGS mit einfach Werten und Zahlen unterstützt.
|
||||
# Beispiele: NICHT unterstütze READINGS
|
||||
# cum_month => CUM_MONTH: 37.173 CUM: 108.090 COST: 0.00
|
||||
# cum_day => 2009-09-09 00:03:19 T: 1511725.6 H: 4409616 W: 609.4 R: 150.4
|
||||
# israining no => (yes/no)
|
||||
#######################################################################
|
||||
|
||||
|
||||
|
||||
package main;
|
||||
use strict;
|
||||
use warnings;
|
||||
use POSIX;
|
||||
use Data::Dumper;
|
||||
use LWP;
|
||||
use LWP::UserAgent;
|
||||
use HTTP::Request::Common;
|
||||
|
||||
#######################################################################
|
||||
sub
|
||||
PachLog_Initialize($)
|
||||
{
|
||||
my ($hash) = @_;
|
||||
|
||||
$hash->{DefFn} = "PachLog_Define";
|
||||
$hash->{SetFn} = "PachLog_Set";
|
||||
$hash->{GetFn} = "PachLog_Get";
|
||||
$hash->{NotifyFn} = "PachLog_Notify";
|
||||
}
|
||||
#######################################################################
|
||||
sub PachLog_Define($@)
|
||||
{
|
||||
# define <NAME> PachLog Pachube-X-API-Key
|
||||
my ($hash, @a) = @_;
|
||||
# X-API-Key steht im DEF %defs{<NAME>}{DEF}
|
||||
# Alternativ nach $defs{<NAME>}{XAPIKEY}
|
||||
my($package, $filename, $line, $subroutine) = caller(3);
|
||||
Log 0 , "PachLog_Define => $package: $filename LINE: $line SUB: $subroutine \n";
|
||||
Log 5, Dumper(@_) . "\n";
|
||||
return "Unknown argument count " . int(@a) . " , usage set <name> dataset value or set <name> delete dataset" if(int(@a) != 1);
|
||||
return undef;
|
||||
|
||||
|
||||
}
|
||||
#######################################################################
|
||||
sub PachLog_Set($@)
|
||||
{
|
||||
# set <NAME> ADD/DEL <DEVICENAME> FEED:STREAM:VALUE:STREAM:VALUE&FEED-2:STEAM,VALUE
|
||||
my ($hash, @a) = @_ ;
|
||||
# FHEMWEB Frage....Auswahliste
|
||||
return "Unknown argument $a[1], choose one of ". join(" ",sort keys %{$hash->{READINGS}}) if($a[1] eq "?");
|
||||
# Pruefen Uebergabeparameter
|
||||
# @a => a[0]:<NAME>; a[1]=ADD oder DEL; a[2]= DeviceName;
|
||||
# a[3]=FEED:STREAM:VALUE:STREAM:VALUE&FEED-2:STREAM,VALUE
|
||||
# READINGS setzten oder löschen
|
||||
if($a[1] eq "DEL")
|
||||
{
|
||||
GetLogLevel($a[0],2),"PACHLOG -> DELETE: A0= ". $a[0] . " A1= " . $a[1] . " A2=" . $a[2];
|
||||
if(defined($hash->{READINGS}{$a[2]}))
|
||||
{
|
||||
delete($hash->{READINGS}{$a[2]})
|
||||
}
|
||||
}
|
||||
if($a[1] eq "ADD")
|
||||
{
|
||||
if(!defined($defs{$a[2]})) {return "PACHLOG[". $a[2] . "] => Unkown Device";}
|
||||
# Mindestens 3 Parameter
|
||||
my @b = split(/:/, $a[3]);
|
||||
return "PACHLOG[". $a[2] . "] => Argumenete: " . $a[3] . " nicht eindeutig => mind. 3 => FEED-NR:DATASTREAM:READING" if(int(@b) < 3);
|
||||
my $feednr = shift(@b);
|
||||
#FEED-Nr darf nur Zahlen enthalten
|
||||
if($feednr !~ /^\d+$/) {return "PACHLOG[". $a[2] . "] => FEED-Nr >" . $feednr . "< ist ungueltig";}
|
||||
# ??? Pruefen ob READING vorhanden ???
|
||||
my ($i,$j);
|
||||
for ($i=0;$i<@b;$i++)
|
||||
{
|
||||
#Stream nur Zahlen
|
||||
if($b[$i] !~ /^\d+$/) {return "PACHLOG => FEED-Nr[" . $feednr ."] Stream-ID >" . $b[$i] . "< ungueltig";}
|
||||
# Reading existiert
|
||||
$j = $i + 1;
|
||||
if(!defined($defs{$a[2]}{READINGS}{$b[$j]})) {return "PACHLOG[". $a[2] . "] => Unkown READING >" . $b[$j] . "<";}
|
||||
# READING-Value validieren
|
||||
my $r = $defs{$a[2]}{READINGS}{$b[$j]}{VAL};
|
||||
my $rn = &ReadingToNumber($r);
|
||||
if(!defined($rn)) {return "PACHLOG[". $a[$i] . "] => READING not supported >" . $b[$j] . "<";}
|
||||
$i = $j;
|
||||
}
|
||||
$hash->{READINGS}{$a[2]}{TIME} = TimeNow();
|
||||
$hash->{READINGS}{$a[2]}{VAL} = $a[3];
|
||||
}
|
||||
$hash->{CHANGED}[0] = $a[1];
|
||||
$hash->{STATE} = $a[1];
|
||||
return undef;
|
||||
return "Unknown argument count " . int(@a) . " , usage set <name> ADD/DEL <DEVICE-NAME> FEED:STREAM:VALUE:STREAM:VALUE&FEED-2:STREAM,VALUE" if(int(@a) != 4);
|
||||
|
||||
}
|
||||
#######################################################################
|
||||
sub PachLog_Get()
|
||||
{
|
||||
# OHNE FUNKTION ....
|
||||
my ($name, $x_key) = @_;
|
||||
my($package, $filename, $line, $subroutine) = caller(3);
|
||||
Log 5, "PachLog_Get => $package: $filename LINE: $line SUB: $subroutine \n";
|
||||
Log 5, Dumper(@_) . "\n";
|
||||
}
|
||||
#######################################################################
|
||||
sub PachLog_Notify ($$)
|
||||
{
|
||||
my ($me, $trigger) = @_;
|
||||
my $d = $me->{NAME};
|
||||
my $t = $trigger->{NAME};
|
||||
# Eintrag fuer Trigger-Device vorhanden
|
||||
if(!defined($defs{$d}{READINGS}{$t}))
|
||||
{
|
||||
Log 5, ("PACHLOG[INFO] => " . $t . " => Nicht definiert");
|
||||
return undef;}
|
||||
|
||||
# Umwandeln 1234:0:temperature:1:humidity => %feed
|
||||
# Struktur:
|
||||
# %feed{FEED-NR}{READING}{VAL}
|
||||
# %feed{FEED-NR}{READING}{DATASTREAM}
|
||||
my ($dat,@a,$feednr,$i,$j);
|
||||
my %feed = ();
|
||||
$dat = $defs{$d}{READINGS}{$t}{VAL};
|
||||
@a = split(/:/, $dat);
|
||||
$feednr = shift(@a);
|
||||
for ($i=0;$i<@a;$i++)
|
||||
{
|
||||
$j = $i + 1;
|
||||
$feed{$feednr}{$a[$j]}{STREAM} = $a[$i];
|
||||
$i = $j;
|
||||
}
|
||||
# Werte aus Trigger-Device
|
||||
foreach my $r (keys %{$feed{$feednr}})
|
||||
{
|
||||
$i = $defs{$t}{READINGS}{$r}{VAL};
|
||||
# Werte Normalisieren
|
||||
# Einheit -> 21,1 (celsius) -> 21,1
|
||||
# FS20: VAL = on => 1 && VAL = off => 0
|
||||
# @a = split(' ', $i);
|
||||
# $feed{$feednr}{$r}{VAL} = &ReadingToNumber($a[0]) ;
|
||||
$feed{$feednr}{$r}{VAL} = &ReadingToNumber($i) ;
|
||||
|
||||
}
|
||||
Log 5, "PACHLOG => dumper(FEED) => " .Dumper(%feed);
|
||||
|
||||
# CVS-Data
|
||||
# my @cvs = ();
|
||||
# foreach my $r (keys %{$feed{$feednr}})
|
||||
# {
|
||||
# $cvs[$feed{$feednr}{$r}{STREAM}] = $feed{$feednr}{$r}{VAL};
|
||||
# }
|
||||
# my $cvs_data = join(',',@cvs);
|
||||
# Log 5, "PACHLOG[CVSDATA] => $cvs_data";
|
||||
|
||||
# Aufbereiten %feed als EEML-Data
|
||||
my $eeml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
|
||||
$eeml .= "<eeml xmlns=\"http://www.eeml.org/xsd/005\">\n";
|
||||
$eeml .= "<environment>\n";
|
||||
foreach my $r (keys %{$feed{$feednr}})
|
||||
{
|
||||
$eeml .= "<data id=\"" . $feed{$feednr}{$r}{STREAM} . "\">\n";
|
||||
$eeml .= "<value>" . $feed{$feednr}{$r}{VAL} . "</value>\n";
|
||||
# Unit fuer EEML: <unit symbol="C" type="derivedSI">Celsius</unit>
|
||||
my ($u_name,$u_symbol,$u_type,$u_tag) = split(',',&PachLog_ReadingToUnit($r));
|
||||
if(defined($u_name)) {
|
||||
$eeml .= "<tag>". $u_tag . "</tag>\n";
|
||||
$eeml .= "<unit symbol=\"" . $u_symbol. "\" type=\"" . $u_type. "\">" . $u_name . "<\/unit>\n";
|
||||
}
|
||||
$eeml .= "</data>\n";
|
||||
}
|
||||
$eeml .= "</environment>\n";
|
||||
$eeml .= "</eeml>\n";
|
||||
Log 5, "PACHLOG -> " . $t . " EEML => " . $eeml;
|
||||
# Pachube-Update per EEML -> XML
|
||||
my ($res,$ret,$ua,$apiKey,$url);
|
||||
$apiKey = $defs{$d}{DEF};
|
||||
$url = "http://www.pachube.com/api/feeds/" . $feednr . ".xml";
|
||||
$ua = new LWP::UserAgent;
|
||||
$ua->default_header('X-PachubeApiKey' => $apiKey);
|
||||
$res = $ua->request(PUT $url,'Content' => $eeml);
|
||||
# Ueberpruefen wir, ob alles okay war:
|
||||
if ($res->is_success())
|
||||
{
|
||||
Log 0,("PACHLOG => Update[" . $t ."] => SUCCESS\n");
|
||||
# Time setzten
|
||||
$defs{$d}{READINGS}{$t}{TIME} = TimeNow();
|
||||
}
|
||||
else {Log 0,("PACHLOG => Update[" . $t ."] ERROR: " . ($res->as_string) . "\n");}
|
||||
}
|
||||
sub PachLog_ReadingToUnit($)
|
||||
{
|
||||
# Unit fuer EEML: <unit symbol="C" type="derivedSI">Celsius</unit>
|
||||
# Input: READING z.B. temperature
|
||||
# Output: Name,symbol,Type,Tag z.B. Celsius,C,derivedSI
|
||||
# weiters => www.eeml.org
|
||||
# No Match = undef
|
||||
my $in = shift;
|
||||
my %unit = ();
|
||||
%unit = (
|
||||
'temperature' => "Celsius,C,derivedSI,Temperature",
|
||||
'current' => "Power,kW,derivedSI,EnergyConsumption",
|
||||
'humidity' => "Humidity,rel%,contextDependentUnits,Humidity",
|
||||
'rain' => "Rain,l/m2,contextDependentUnits,Rain",
|
||||
'wind' => "Wind,m/s,contextDependentUnits,Wind",
|
||||
);
|
||||
if(defined($unit{$in})) {return $unit{$in};}
|
||||
else {return undef;}
|
||||
}
|
||||
sub ReadingToNumber($)
|
||||
{
|
||||
# Input: reading z.B. 21.1 (Celsius) oder dim10%, on-for-oldtimer etc.
|
||||
# Output: 21.1 oder 10
|
||||
# ERROR = undef
|
||||
# Alles außer Nummern loeschen $t =~ s/[^0123456789.]//g;
|
||||
my $in = shift;
|
||||
Log 5, "PACHLOG[ReadingToNumber] => in => $in";
|
||||
# Bekannte READINGS FS20 Devices oder FHT
|
||||
if($in =~ /^on|Switch.*on/i) {$in = 1;}
|
||||
if($in =~ /^off|Switch.*off|lime-protection/i) {$in = 0;}
|
||||
# Keine Zahl vorhanden
|
||||
if($in !~ /\d{1}/) {return undef;}
|
||||
# Mehrfachwerte in READING z.B. CUM_DAY: 5.040 CUM: 334.420 COST: 0.00
|
||||
my @b = split(' ', $in);
|
||||
if(int(@b) gt 2) {return undef;}
|
||||
# Nurnoch Zahlen z.B. dim10% = 10 oder 21.1 (Celsius) = 21.1
|
||||
$in =~ s/[^0123456789.]//g;
|
||||
Log 5, "PACHLOG[ReadingToNumber] => out => $in";
|
||||
return $in
|
||||
}
|
||||
1;
|
|
@ -1,203 +0,0 @@
|
|||
|
||||
package main;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use POSIX;
|
||||
|
||||
sub holiday_refresh($$);
|
||||
|
||||
#####################################
|
||||
sub
|
||||
holiday_Initialize($)
|
||||
{
|
||||
my ($hash) = @_;
|
||||
|
||||
$hash->{DefFn} = "holiday_Define";
|
||||
$hash->{GetFn} = "holiday_Get";
|
||||
$hash->{UndefFn} = "holiday_Undef";
|
||||
}
|
||||
|
||||
|
||||
#####################################
|
||||
sub
|
||||
holiday_Define($$)
|
||||
{
|
||||
my ($hash, $def) = @_;
|
||||
|
||||
return holiday_refresh($hash->{NAME}, undef) if($init_done);
|
||||
InternalTimer(gettimeofday()+1, "holiday_refresh", $hash->{NAME}, 0);
|
||||
return undef;
|
||||
}
|
||||
|
||||
sub
|
||||
holiday_Undef($$)
|
||||
{
|
||||
my ($hash, $name) = @_;
|
||||
RemoveInternalTimer($name);
|
||||
return undef;
|
||||
}
|
||||
|
||||
sub
|
||||
holiday_refresh($$)
|
||||
{
|
||||
my ($name, $fordate) = (@_);
|
||||
my $hash = $defs{$name};
|
||||
my $internal;
|
||||
|
||||
return if(!$hash); # Just deleted
|
||||
|
||||
my $nt = gettimeofday();
|
||||
my @lt = localtime($nt);
|
||||
my @fd;
|
||||
if(!$fordate) {
|
||||
$internal = 1;
|
||||
$fordate = sprintf("%02d-%02d", $lt[4]+1, $lt[3]);
|
||||
@fd = @lt;
|
||||
} else {
|
||||
my ($m,$d) = split("-", $fordate);
|
||||
@fd = localtime(mktime(1,1,1,$d,$m-1,$lt[5],0,0,-1));
|
||||
}
|
||||
|
||||
my $fname = $attr{global}{modpath} . "/FHEM/" . $hash->{NAME} . ".holiday";
|
||||
return "Can't open $fname: $!" if(!open(FH, $fname));
|
||||
my $found = "none";
|
||||
while(my $l = <FH>) {
|
||||
next if($l =~ m/^\s*#/);
|
||||
next if($l =~ m/^\s*$/);
|
||||
chomp($l);
|
||||
|
||||
if($l =~ m/^1/) { # Exact date: 1 MM-DD Holiday
|
||||
my @args = split(" +", $l, 3);
|
||||
if($args[1] eq $fordate) {
|
||||
$found = $args[2];
|
||||
last;
|
||||
}
|
||||
|
||||
} elsif($l =~ m/^2/) { # Easter date: 2 +1 Ostermontag
|
||||
|
||||
eval { require DateTime::Event::Easter } ;
|
||||
if( $@) {
|
||||
Log 1, "$@";
|
||||
|
||||
} else {
|
||||
my @a = split(" +", $l, 3);
|
||||
my $dt = DateTime::Event::Easter->new(day=>$a[1])
|
||||
->following(DateTime->new(year=>(1900+$lt[5])));
|
||||
next if($dt->day != $fd[3] || $dt->month != $fd[4]+1);
|
||||
$found = $a[2];
|
||||
last;
|
||||
}
|
||||
|
||||
} elsif($l =~ m/^3/) { # Relative date: 3 -1 Mon 03 Holiday
|
||||
my @a = split(" +", $l, 5);
|
||||
my %wd = ("Sun"=>0, "Mon"=>1, "Tue"=>2, "Wed"=>3,
|
||||
"Thu"=>4, "Fri"=>5, "Sat"=>6);
|
||||
my @md = (31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);
|
||||
$md[1]=29 if(schaltjahr($fd[5]+1900) && $fd[4] == 1);
|
||||
my $wd = $wd{$a[2]};
|
||||
if(!defined($wd)) {
|
||||
Log 1, "Wrong timespec: $l";
|
||||
next;
|
||||
}
|
||||
next if($wd != $fd[6]); # Weekday
|
||||
next if($a[3] != ($fd[4]+1)); # Month
|
||||
if($a[1] > 0) { # N'th day from the start
|
||||
my $d = $fd[3] - ($a[1]-1)*7;
|
||||
next if($d < 1 || $d > 7);
|
||||
} elsif($a[1] < 0) { # N'th day from the end
|
||||
my $d = $fd[3] - ($a[1]+1)*7;
|
||||
my $md = $md[$fd[4]];
|
||||
next if($d > $md || $d < $md-6);
|
||||
}
|
||||
|
||||
$found = $a[4];
|
||||
last;
|
||||
|
||||
} elsif($l =~ m/^4/) { # Interval: 4 MM-DD MM-DD Holiday
|
||||
my @args = split(" +", $l, 4);
|
||||
if($args[1] le $fordate && $args[2] ge $fordate) {
|
||||
$found = $args[3];
|
||||
last;
|
||||
}
|
||||
|
||||
} elsif($l =~ m/^5/) { # nth weekday since MM-DD / before MM-DD
|
||||
my @a = split(" +", $l, 6);
|
||||
# arguments: 5 <distance> <weekday> <day> <month> <name>
|
||||
my %wd = ("Sun"=>0, "Mon"=>1, "Tue"=>2, "Wed"=>3,
|
||||
"Thu"=>4, "Fri"=>5, "Sat"=>6);
|
||||
my $wd = $wd{$a[2]};
|
||||
if(!defined($wd)) {
|
||||
Log 1, "Wrong weekday spec: $l";
|
||||
next;
|
||||
}
|
||||
next if $wd != $fd[6]; # check wether weekday matches today
|
||||
my $yday=$fd[7];
|
||||
# create time object of target date - mktime counts months and their
|
||||
# days from 0 instead of 1, so subtract 1 from each
|
||||
my $tgt=mktime(0,0,1,$a[3]-1,$a[4]-1,$fd[5],0,0,-1);
|
||||
my $tgtmin=$tgt;
|
||||
my $tgtmax=$tgt;
|
||||
my $weeksecs=7*24*60*60; # 7 days, 24 hours, 60 minutes, 60seconds each
|
||||
my $cd=mktime(0,0,1,$fd[3],$fd[4],$fd[5],0,0,-1);
|
||||
if ( $a[1] =~ /^-([0-9])*$/ ) {
|
||||
$tgtmin -= $1*$weeksecs; # Minimum: target date minus $1 weeks
|
||||
$tgtmax = $tgtmin+$weeksecs; # Maximum: one week after minimum
|
||||
# needs to be lower than max and greater than or equal to min
|
||||
if ( ($cd ge $tgtmin) && ( $cd lt $tgtmax) ) {
|
||||
$found=$a[5];
|
||||
last;
|
||||
}
|
||||
} elsif ( $a[1] =~ /^\+?([0-9])*$/ ) {
|
||||
$tgtmin += ($1-1)*$weeksecs; # Minimum: target date plus $1-1 weeks
|
||||
$tgtmax = $tgtmin+$weeksecs; # Maximum: one week after minimum
|
||||
# needs to be lower than or equal to max and greater min
|
||||
if ( ($cd gt $tgtmin) && ( $cd le $tgtmax) ) {
|
||||
$found=$a[5];
|
||||
last;
|
||||
}
|
||||
} else {
|
||||
Log 1, "Wrong distance spec: $l";
|
||||
next;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
close(FH);
|
||||
|
||||
RemoveInternalTimer($name);
|
||||
$nt -= ($lt[2]*3600+$lt[1]*60+$lt[0]); # Midnight
|
||||
$nt += 86400 + 2; # Tomorrow
|
||||
$hash->{TRIGGERTIME} = $nt;
|
||||
InternalTimer($nt, "holiday_refresh", $name, 0);
|
||||
|
||||
if($internal) {
|
||||
$hash->{STATE} = $found;
|
||||
return undef;
|
||||
} else {
|
||||
return $found;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
sub
|
||||
holiday_Get($@)
|
||||
{
|
||||
my ($hash, @a) = @_;
|
||||
|
||||
return "argument is missing" if(int(@a) != 2);
|
||||
return "wrong argument: need MM-DD" if($a[1] !~ m/^[01]\d-[0-3]\d$/);
|
||||
return holiday_refresh($hash->{NAME}, $a[1]);
|
||||
}
|
||||
|
||||
sub
|
||||
schaltjahr($)
|
||||
{
|
||||
my($jahr) = @_;
|
||||
return 0 if $jahr % 4; # 2009
|
||||
return 1 unless $jahr % 400; # 2000
|
||||
return 0 unless $jahr % 100; # 2100
|
||||
return 1; # 2012
|
||||
}
|
||||
|
||||
1;
|
|
@ -1,48 +0,0 @@
|
|||
##############################################
|
||||
package main;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
sub
|
||||
dummy_Initialize($)
|
||||
{
|
||||
my ($hash) = @_;
|
||||
|
||||
$hash->{SetFn} = "dummy_Set";
|
||||
$hash->{DefFn} = "dummy_Define";
|
||||
$hash->{AttrList} = "loglevel:0,1,2,3,4,5,6";
|
||||
}
|
||||
|
||||
###################################
|
||||
sub
|
||||
dummy_Set($@)
|
||||
{
|
||||
my ($hash, @a) = @_;
|
||||
|
||||
return "no set value specified" if(int(@a) != 2);
|
||||
return "Unknown argument $a[1], choose one of *" if($a[1] eq "?");
|
||||
|
||||
|
||||
my $v = $a[1];
|
||||
|
||||
Log GetLogLevel($a[0],2), "dummy set @a";
|
||||
|
||||
$hash->{CHANGED}[0] = $v;
|
||||
$hash->{STATE} = $v;
|
||||
$hash->{READINGS}{state}{TIME} = TimeNow();
|
||||
$hash->{READINGS}{state}{VAL} = $v;
|
||||
return undef;
|
||||
}
|
||||
|
||||
sub
|
||||
dummy_Define($$)
|
||||
{
|
||||
my ($hash, $def) = @_;
|
||||
my @a = split("[ \t][ \t]*", $def);
|
||||
|
||||
return "Wrong syntax: use define <name> dummy" if(int(@a) != 2);
|
||||
return undef;
|
||||
}
|
||||
|
||||
1;
|
|
@ -1,207 +0,0 @@
|
|||
##############################################
|
||||
package main;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
sub addToAttrList($);
|
||||
|
||||
#####################################
|
||||
sub
|
||||
structure_Initialize($)
|
||||
{
|
||||
my ($hash) = @_;
|
||||
|
||||
$hash->{DefFn} = "structure_Define";
|
||||
$hash->{UndefFn} = "structure_Undef";
|
||||
|
||||
$hash->{SetFn} = "structure_Set";
|
||||
$hash->{AttrFn} = "structure_Attr";
|
||||
addToAttrList("structexclude");
|
||||
|
||||
my %ahash = ( Fn=>"CommandAddStruct",
|
||||
Hlp=>"<structure> <devspec>,add <devspec> to <structure>" );
|
||||
$cmds{addstruct} = \%ahash;
|
||||
|
||||
my %dhash = ( Fn=>"CommandDelStruct",
|
||||
Hlp=>"<structure> <devspec>,delete <devspec> from <structure>");
|
||||
$cmds{delstruct} = \%dhash;
|
||||
}
|
||||
|
||||
|
||||
#############################
|
||||
sub
|
||||
structure_Define($$)
|
||||
{
|
||||
my ($hash, $def) = @_;
|
||||
my @a = split("[ \t][ \t]*", $def);
|
||||
|
||||
my $u = "wrong syntax: define <name> structure <struct-type> [device ...]";
|
||||
return $u if(int(@a) < 4);
|
||||
|
||||
my $devname = shift(@a);
|
||||
my $modname = shift(@a);
|
||||
my $stype = shift(@a);
|
||||
|
||||
addToAttrList($stype);
|
||||
$hash->{ATTR} = $stype;
|
||||
|
||||
my %list;
|
||||
foreach my $a (@a) {
|
||||
$list{$a} = 1;
|
||||
}
|
||||
$hash->{CONTENT} = \%list;
|
||||
$hash->{STATE} = join(" ", sort(keys %{$hash->{CONTENT}}));
|
||||
|
||||
@a = ( "set", $devname, $stype, $devname );
|
||||
structure_Attr(@a);
|
||||
|
||||
return undef;
|
||||
}
|
||||
|
||||
#############################
|
||||
sub
|
||||
structure_Undef($$)
|
||||
{
|
||||
my ($hash, $def) = @_;
|
||||
my @a = ( "del", $hash->{NAME}, $hash->{ATTR} );
|
||||
structure_Attr(@a);
|
||||
return undef;
|
||||
}
|
||||
|
||||
|
||||
#####################################
|
||||
sub
|
||||
CommandAddStruct($)
|
||||
{
|
||||
my ($cl, $param) = @_;
|
||||
my @a = split(" ", $param);
|
||||
|
||||
if(int(@a) != 2) {
|
||||
return "Usage: addstruct <structure_device> <devspec>";
|
||||
}
|
||||
|
||||
my $name = shift(@a);
|
||||
my $hash = $defs{$name};
|
||||
if(!$hash || $hash->{TYPE} ne "structure") {
|
||||
return "$a is not a structure device";
|
||||
}
|
||||
|
||||
foreach my $d (devspec2array($a[0])) {
|
||||
$hash->{CONTENT}{$d} = 1;
|
||||
}
|
||||
$hash->{STATE} = join(" ", sort(keys %{$hash->{CONTENT}}));
|
||||
|
||||
@a = ( "set", $hash->{NAME}, $hash->{ATTR}, $hash->{NAME} );
|
||||
structure_Attr(@a);
|
||||
return undef;
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub
|
||||
CommandDelStruct($)
|
||||
{
|
||||
my ($cl, $param) = @_;
|
||||
my @a = split(" ", $param);
|
||||
|
||||
if(int(@a) != 2) {
|
||||
return "Usage: delstruct <structure_device> <devspec>";
|
||||
}
|
||||
|
||||
my $name = shift(@a);
|
||||
my $hash = $defs{$name};
|
||||
if(!$hash || $hash->{TYPE} ne "structure") {
|
||||
return "$a is not a structure device";
|
||||
}
|
||||
|
||||
foreach my $d (devspec2array($a[0])) {
|
||||
delete($hash->{CONTENT}{$d});
|
||||
}
|
||||
$hash->{STATE} = join(" ", sort(keys %{$hash->{CONTENT}}));
|
||||
|
||||
@a = ( "del", $hash->{NAME}, $hash->{ATTR} );
|
||||
structure_Attr(@a);
|
||||
return undef;
|
||||
}
|
||||
|
||||
|
||||
###################################
|
||||
sub
|
||||
structure_Set($@)
|
||||
{
|
||||
my ($hash, @list) = @_;
|
||||
my $ret = "";
|
||||
$hash->{INSET} = 1;
|
||||
foreach my $d (sort keys %{ $hash->{CONTENT} }) {
|
||||
next if(!$defs{$d});
|
||||
if($defs{$d}{INSET}) {
|
||||
Log 1, "ERROR: endless loop detected for $d in " . $hash->{NAME};
|
||||
next;
|
||||
}
|
||||
|
||||
if($attr{$d} && $attr{$d}{structexclude}) {
|
||||
my $se = $attr{$d}{structexclude};
|
||||
next if($hash->{NAME} =~ m/$se/);
|
||||
}
|
||||
|
||||
$list[0] = $d;
|
||||
my $sret .= CommandSet(undef, join(" ", @list));
|
||||
if($sret) {
|
||||
$ret .= "\n" if($ret);
|
||||
$ret .= $sret;
|
||||
}
|
||||
}
|
||||
delete($hash->{INSET});
|
||||
Log 5, "ATTR: $ret" if($ret);
|
||||
return undef;
|
||||
}
|
||||
|
||||
###################################
|
||||
sub
|
||||
structure_Attr($@)
|
||||
{
|
||||
my ($type, @list) = @_;
|
||||
|
||||
my $hash = $defs{$list[0]};
|
||||
$hash->{INATTR} = 1;
|
||||
my $ret = "";
|
||||
foreach my $d (sort keys %{ $hash->{CONTENT} }) {
|
||||
next if(!$defs{$d});
|
||||
if($defs{$d}{INATTR}) {
|
||||
Log 1, "ERROR: endless loop detected for $d in " . $hash->{NAME};
|
||||
next;
|
||||
}
|
||||
$list[0] = $d;
|
||||
my $sret;
|
||||
if($type eq "del") {
|
||||
$sret .= CommandDeleteAttr(undef, join(" ", @list));
|
||||
} else {
|
||||
$sret .= CommandAttr(undef, join(" ", @list));
|
||||
}
|
||||
if($sret) {
|
||||
$ret .= "\n" if($ret);
|
||||
$ret .= $sret;
|
||||
}
|
||||
}
|
||||
delete($hash->{INATTR});
|
||||
Log 5, "ATTR: $ret" if($ret);
|
||||
return undef;
|
||||
}
|
||||
|
||||
sub
|
||||
addToAttrList($)
|
||||
{
|
||||
my $arg = shift;
|
||||
|
||||
my $ua = "";
|
||||
$ua = $attr{global}{userattr} if($attr{global}{userattr});
|
||||
my @al = split(" ", $ua);
|
||||
my %hash;
|
||||
foreach my $a (@al) {
|
||||
$hash{$a} = 1;
|
||||
}
|
||||
$hash{$arg} = 1;
|
||||
$attr{global}{userattr} = join(" ", sort keys %hash);
|
||||
}
|
||||
|
||||
1;
|
|
@ -1,345 +0,0 @@
|
|||
##############################################
|
||||
# - Please call sunrise_coord before using this module, else you'll get times
|
||||
# for frankfurt am main (germany). See the "at" entry in commandref.html
|
||||
#
|
||||
# This code is derived from DateTime::Event::Sunrise, version 0.0501.
|
||||
# Simplified and removed further package # dependency (DateTime,
|
||||
# Params::Validate, etc). For comments see the original code.
|
||||
#
|
||||
|
||||
package main;
|
||||
use strict;
|
||||
use warnings;
|
||||
use Math::Trig;
|
||||
|
||||
sub sr($$$$$$);
|
||||
sub sunrise_rel(@);
|
||||
sub sunset_rel(@);
|
||||
sub sunrise_abs(@);
|
||||
sub sunset_abs(@);
|
||||
sub isday();
|
||||
sub sunrise_coord($$$);
|
||||
|
||||
sub SUNRISE_Initialize($);
|
||||
|
||||
# See perldoc DateTime::Event::Sunrise for details
|
||||
my $long = "8.686";
|
||||
my $lat = "50.112";
|
||||
my $tz = ""; # will be overwritten
|
||||
my $altit = "-6"; # Civil twilight
|
||||
my $RADEG = ( 180 / 3.1415926 );
|
||||
my $DEGRAD = ( 3.1415926 / 180 );
|
||||
my $INV360 = ( 1.0 / 360.0 );
|
||||
|
||||
|
||||
sub
|
||||
SUNRISE_EL_Initialize($)
|
||||
{
|
||||
my ($hash) = @_;
|
||||
}
|
||||
|
||||
|
||||
##########################
|
||||
# Compute the _next_ event
|
||||
# rise: 1: event is sunrise (else sunset)
|
||||
# isrel: 1: relative times
|
||||
# seconds: second offset to event
|
||||
# daycheck: if set, then return 1 if the sun is visible, 0 else
|
||||
sub
|
||||
sr($$$$$$)
|
||||
{
|
||||
my ($rise, $seconds, $isrel, $daycheck, $min, $max) = @_;
|
||||
my $needrise = ($rise || $daycheck) ? 1 : 0;
|
||||
my $needset = (!$rise || $daycheck) ? 1 : 0;
|
||||
$seconds = 0 if(!$seconds);
|
||||
|
||||
my $nt = time;
|
||||
my @lt = localtime($nt);
|
||||
my $gmtoff = _calctz($nt,@lt); # in hour
|
||||
|
||||
my ($rt,$st) = _sr($needrise,$needset, $lt[5]+1900,$lt[4]+1,$lt[3], $gmtoff);
|
||||
my $sst = ($rise ? $rt : $st) + ($seconds/3600);
|
||||
|
||||
my $nh = $lt[2] + $lt[1]/60 + $lt[0]/3600; # Current hour since midnight
|
||||
if($daycheck) {
|
||||
return 0 if($nh < $rt || $nh > $st);
|
||||
return 1;
|
||||
}
|
||||
|
||||
my $diff = 0;
|
||||
if($data{AT_RECOMPUTE} || # compute it for tommorow
|
||||
int(($nh-$sst)*3600) >= 0) { # if called a subsec earlier
|
||||
$nt += 86400;
|
||||
$diff = 24;
|
||||
@lt = localtime($nt);
|
||||
$gmtoff = _calctz($nt,@lt); # in hour
|
||||
|
||||
($rt,$st) = _sr($needrise,$needset, $lt[5]+1900,$lt[4]+1,$lt[3], $gmtoff);
|
||||
$sst = ($rise ? $rt : $st) + ($seconds/3600);
|
||||
}
|
||||
|
||||
$sst = hms2h($min) if(defined($min) && (hms2h($min) > $sst));
|
||||
$sst = hms2h($max) if(defined($max) && (hms2h($max) < $sst));
|
||||
|
||||
$sst += $diff if($isrel);
|
||||
$sst -= $nh if($isrel == 1);
|
||||
|
||||
return h2hms_fmt($sst);
|
||||
}
|
||||
|
||||
|
||||
sub
|
||||
_sr($$$$$$)
|
||||
{
|
||||
my ($needrise, $needset, $y, $m, $dy, $offset) = @_;
|
||||
|
||||
my $d = _days_since_2000_Jan_0($y,$m,$dy) + 0.5 - $long / 360.0;
|
||||
my ( $tmp_rise_1, $tmp_set_1 ) =
|
||||
_sunrise_sunset( $d, $long, $lat, $altit, 15.04107 );
|
||||
|
||||
my ($tmp_rise_2, $tmp_rise_3) = (0,0);
|
||||
|
||||
if($needrise) {
|
||||
$tmp_rise_2 = 9; $tmp_rise_3 = 0;
|
||||
until ( _equal( $tmp_rise_2, $tmp_rise_3, 8 ) ) {
|
||||
|
||||
my $d_sunrise_1 = $d + $tmp_rise_1 / 24.0;
|
||||
( $tmp_rise_2, undef ) =
|
||||
_sunrise_sunset( $d_sunrise_1, $long,
|
||||
$lat, $altit, 15.04107 );
|
||||
$tmp_rise_1 = $tmp_rise_3;
|
||||
my $d_sunrise_2 = $d + $tmp_rise_2 / 24.0;
|
||||
( $tmp_rise_3, undef ) =
|
||||
_sunrise_sunset( $d_sunrise_2, $long,
|
||||
$lat, $altit, 15.04107 );
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
my ($tmp_set_2, $tmp_set_3) = (0,0);
|
||||
if($needset) {
|
||||
$tmp_set_2 = 9; $tmp_set_3 = 0;
|
||||
until ( _equal( $tmp_set_2, $tmp_set_3, 8 ) ) {
|
||||
|
||||
my $d_sunset_1 = $d + $tmp_set_1 / 24.0;
|
||||
( undef, $tmp_set_2 ) =
|
||||
_sunrise_sunset( $d_sunset_1, $long,
|
||||
$lat, $altit, 15.04107 );
|
||||
$tmp_set_1 = $tmp_set_3;
|
||||
my $d_sunset_2 = $d + $tmp_set_2 / 24.0;
|
||||
( undef, $tmp_set_3 ) =
|
||||
_sunrise_sunset( $d_sunset_2, $long,
|
||||
$lat, $altit, 15.04107 );
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return $tmp_rise_3+$offset, $tmp_set_3+$offset;
|
||||
}
|
||||
|
||||
|
||||
|
||||
sub
|
||||
_sunrise_sunset($$$$$)
|
||||
{
|
||||
my ( $d, $lon, $lat, $altit, $h ) = @_;
|
||||
|
||||
my $sidtime = _revolution( _GMST0($d) + 180.0 + $lon );
|
||||
|
||||
my ( $sRA, $sdec ) = _sun_RA_dec($d);
|
||||
my $tsouth = 12.0 - _rev180( $sidtime - $sRA ) / $h;
|
||||
my $sradius = 0.2666 / $sRA;
|
||||
|
||||
$altit -= $sradius;
|
||||
|
||||
# Compute the diurnal arc that the Sun traverses to reach
|
||||
# the specified altitude altit:
|
||||
|
||||
my $cost =
|
||||
( sind($altit) - sind($lat) * sind($sdec) ) /
|
||||
( cosd($lat) * cosd($sdec) );
|
||||
|
||||
my $t;
|
||||
if ( $cost >= 1.0 ) {
|
||||
$t = 0.0; # Sun always below altit
|
||||
}
|
||||
elsif ( $cost <= -1.0 ) {
|
||||
$t = 12.0; # Sun always above altit
|
||||
}
|
||||
else {
|
||||
$t = acosd($cost) / 15.0; # The diurnal arc, hours
|
||||
}
|
||||
|
||||
# Store rise and set times - in hours UT
|
||||
|
||||
my $hour_rise_ut = $tsouth - $t;
|
||||
my $hour_set_ut = $tsouth + $t;
|
||||
return ( $hour_rise_ut, $hour_set_ut );
|
||||
|
||||
}
|
||||
|
||||
sub
|
||||
_GMST0($)
|
||||
{
|
||||
my ($d) = @_;
|
||||
|
||||
my $sidtim0 =
|
||||
_revolution( ( 180.0 + 356.0470 + 282.9404 ) +
|
||||
( 0.9856002585 + 4.70935E-5 ) * $d );
|
||||
return $sidtim0;
|
||||
|
||||
}
|
||||
|
||||
sub
|
||||
_sunpos($)
|
||||
{
|
||||
my ($d) = @_;
|
||||
|
||||
my $Mean_anomaly_of_sun = _revolution( 356.0470 + 0.9856002585 * $d );
|
||||
my $Mean_longitude_of_perihelion = 282.9404 + 4.70935E-5 * $d;
|
||||
my $Eccentricity_of_Earth_orbit = 0.016709 - 1.151E-9 * $d;
|
||||
|
||||
# Compute true longitude and radius vector
|
||||
my $Eccentric_anomaly =
|
||||
$Mean_anomaly_of_sun + $Eccentricity_of_Earth_orbit * $RADEG *
|
||||
sind($Mean_anomaly_of_sun) *
|
||||
( 1.0 + $Eccentricity_of_Earth_orbit * cosd($Mean_anomaly_of_sun) );
|
||||
|
||||
my $x = cosd($Eccentric_anomaly) - $Eccentricity_of_Earth_orbit;
|
||||
|
||||
my $y =
|
||||
sqrt( 1.0 - $Eccentricity_of_Earth_orbit * $Eccentricity_of_Earth_orbit )
|
||||
* sind($Eccentric_anomaly);
|
||||
|
||||
my $Solar_distance = sqrt( $x * $x + $y * $y ); # Solar distance
|
||||
my $True_anomaly = atan2d( $y, $x ); # True anomaly
|
||||
|
||||
my $True_solar_longitude =
|
||||
$True_anomaly + $Mean_longitude_of_perihelion; # True solar longitude
|
||||
|
||||
if ( $True_solar_longitude >= 360.0 ) {
|
||||
$True_solar_longitude -= 360.0; # Make it 0..360 degrees
|
||||
}
|
||||
|
||||
return ( $Solar_distance, $True_solar_longitude );
|
||||
}
|
||||
|
||||
sub
|
||||
_sun_RA_dec($)
|
||||
{
|
||||
my ($d) = @_;
|
||||
|
||||
my ( $r, $lon ) = _sunpos($d);
|
||||
|
||||
my $x = $r * cosd($lon);
|
||||
my $y = $r * sind($lon);
|
||||
|
||||
my $obl_ecl = 23.4393 - 3.563E-7 * $d;
|
||||
|
||||
my $z = $y * sind($obl_ecl);
|
||||
$y = $y * cosd($obl_ecl);
|
||||
|
||||
my $RA = atan2d( $y, $x );
|
||||
my $dec = atan2d( $z, sqrt( $x * $x + $y * $y ) );
|
||||
|
||||
return ( $RA, $dec );
|
||||
}
|
||||
|
||||
sub
|
||||
_days_since_2000_Jan_0($$$)
|
||||
{
|
||||
my ($y, $m, $d) = @_;
|
||||
my @mn = (31,28,31,30,31,30,31,31,30,31,30,31);
|
||||
|
||||
my $ms = 0;
|
||||
for(my $i = 0; $i < $m-1; $i++) {
|
||||
$ms += $mn[$i];
|
||||
}
|
||||
my $x = ($y-2000)*365.25 + $ms + $d;
|
||||
$x++ if($m > 2 && ($y%4) == 0);
|
||||
return int($x);
|
||||
}
|
||||
|
||||
sub sind($) { sin( ( $_[0] ) * $DEGRAD ); }
|
||||
sub cosd($) { cos( ( $_[0] ) * $DEGRAD ); }
|
||||
sub tand($) { tan( ( $_[0] ) * $DEGRAD ); }
|
||||
sub atand($) { ( $RADEG * atan( $_[0] ) ); }
|
||||
sub asind($) { ( $RADEG * asin( $_[0] ) ); }
|
||||
sub acosd($) { ( $RADEG * acos( $_[0] ) ); }
|
||||
sub atan2d($$) { ( $RADEG * atan2( $_[0], $_[1] ) ); }
|
||||
|
||||
sub
|
||||
_revolution($)
|
||||
{
|
||||
my $x = $_[0];
|
||||
return ( $x - 360.0 * int( $x * $INV360 ) );
|
||||
}
|
||||
|
||||
sub
|
||||
_rev180($)
|
||||
{
|
||||
my ($x) = @_;
|
||||
return ( $x - 360.0 * int( $x * $INV360 + 0.5 ) );
|
||||
}
|
||||
|
||||
sub
|
||||
_equal($$$)
|
||||
{
|
||||
my ( $A, $B, $dp ) = @_;
|
||||
return sprintf( "%.${dp}g", $A ) eq sprintf( "%.${dp}g", $B );
|
||||
}
|
||||
|
||||
sub
|
||||
_calctz($@)
|
||||
{
|
||||
my ($nt,@lt) = @_;
|
||||
|
||||
my $off = $lt[2]*3600+$lt[1]*60+$lt[0];
|
||||
$off = 12*3600-$off;
|
||||
$nt += $off; # This is noon, localtime
|
||||
|
||||
my @gt = gmtime($nt);
|
||||
|
||||
return (12-$gt[2]);
|
||||
}
|
||||
|
||||
|
||||
sub
|
||||
hms2h($)
|
||||
{
|
||||
my $in = shift;
|
||||
my @a = split(":", $in);
|
||||
return 0 if(int(@a) < 2 || $in !~ m/^[\d:]*$/);
|
||||
return $a[0]+$a[1]/60 + ($a[2] ? $a[2]/3600 : 0);
|
||||
}
|
||||
|
||||
sub
|
||||
h2hms($)
|
||||
{
|
||||
my ($in) = @_;
|
||||
my ($h,$m,$s);
|
||||
$h = int($in);
|
||||
$m = int(60*($in-$h));
|
||||
$s = int(3600*($in-$h)-60*$m);
|
||||
return ($h, $m, $s);
|
||||
}
|
||||
|
||||
sub
|
||||
h2hms_fmt($)
|
||||
{
|
||||
my ($in) = @_;
|
||||
my ($h,$m,$s) = h2hms($in);
|
||||
return sprintf("%02d:%02d:%02d", $h, $m, $s);
|
||||
}
|
||||
|
||||
|
||||
sub sunrise_rel(@) { return sr(1, shift, 1, 0, shift, shift) }
|
||||
sub sunset_rel(@) { return sr(0, shift, 1, 0, shift, shift) }
|
||||
sub sunrise_abs(@) { return sr(1, shift, 0, 0, shift, shift) }
|
||||
sub sunset_abs(@) { return sr(0, shift, 0, 0, shift, shift) }
|
||||
sub sunrise(@) { return sr(1, shift, 2, 0, shift, shift) }
|
||||
sub sunset(@) { return sr(0, shift, 2, 0, shift, shift) }
|
||||
sub isday() { return sr(1, 0, 0, 1, undef, undef) }
|
||||
sub sunrise_coord($$$) { ($long, $lat, $tz) = @_; return undef; }
|
||||
|
||||
1;
|
|
@ -1,54 +0,0 @@
|
|||
package main;
|
||||
use strict;
|
||||
use warnings;
|
||||
use POSIX;
|
||||
|
||||
sub
|
||||
Utils_Initialize($$)
|
||||
{
|
||||
my ($hash) = @_;
|
||||
}
|
||||
|
||||
sub
|
||||
time_str2num($)
|
||||
{
|
||||
my ($str) = @_;
|
||||
my @a = split("[- :]", $str);
|
||||
return mktime($a[5],$a[4],$a[3],$a[2],$a[1]-1,$a[0]-1900,0,0,-1);
|
||||
}
|
||||
|
||||
sub
|
||||
min($$)
|
||||
{
|
||||
my ($a,$b) = @_;
|
||||
return $a if($a lt $b);
|
||||
return $b;
|
||||
}
|
||||
|
||||
sub
|
||||
max($$)
|
||||
{
|
||||
my ($a,$b) = @_;
|
||||
return $a if($a gt $b);
|
||||
return $b;
|
||||
}
|
||||
|
||||
sub
|
||||
abstime2rel($)
|
||||
{
|
||||
my ($h,$m,$s) = split(":", shift);
|
||||
$m = 0 if(!$m);
|
||||
$s = 0 if(!$s);
|
||||
my $t1 = 3600*$h+60*$m+$s;
|
||||
|
||||
my @now = localtime;
|
||||
my $t2 = 3600*$now[2]+60*$now[1]+$now[0];
|
||||
my $diff = $t1-$t2;
|
||||
$diff += 86400 if($diff < 0);
|
||||
|
||||
return sprintf("%02d:%02d:%02d", $diff/3600, ($diff/60)%60, $diff%60);
|
||||
}
|
||||
|
||||
|
||||
|
||||
1;
|
|
@ -1,94 +0,0 @@
|
|||
package main;
|
||||
use strict;
|
||||
use warnings;
|
||||
use POSIX;
|
||||
|
||||
sub CommandXmlList($$);
|
||||
sub XmlEscape($);
|
||||
|
||||
|
||||
#####################################
|
||||
sub
|
||||
XmlList_Initialize($$)
|
||||
{
|
||||
my %lhash = ( Fn=>"CommandXmlList",
|
||||
Hlp=>",list definitions and status info as xml" );
|
||||
$cmds{xmllist} = \%lhash;
|
||||
}
|
||||
|
||||
|
||||
#####################################
|
||||
sub
|
||||
XmlEscape($)
|
||||
{
|
||||
my $a = shift;
|
||||
return "" if(!$a);
|
||||
$a =~ s/\\\n/<br>/g; # Multi-line
|
||||
$a =~ s/&/&/g;
|
||||
$a =~ s/"/"/g;
|
||||
$a =~ s/</</g;
|
||||
$a =~ s/>/>/g;
|
||||
$a =~ s/([^ -~])/sprintf("#%02x;", ord($1))/ge;
|
||||
return $a;
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub
|
||||
CommandXmlList($$)
|
||||
{
|
||||
my ($cl, $param) = @_;
|
||||
my $str = "<FHZINFO>\n";
|
||||
my $lt = "";
|
||||
|
||||
delete($modules{""}) if(defined($modules{""}));
|
||||
for my $d (sort { my $x = $modules{$defs{$a}{TYPE}}{ORDER} cmp
|
||||
$modules{$defs{$b}{TYPE}}{ORDER};
|
||||
$x = ($a cmp $b) if($x == 0); $x; } keys %defs) {
|
||||
|
||||
my $p = $defs{$d};
|
||||
my $t = $p->{TYPE};
|
||||
|
||||
if($t ne $lt) {
|
||||
$str .= "\t</${lt}_LIST>\n" if($lt);
|
||||
$str .= "\t<${t}_LIST>\n";
|
||||
}
|
||||
$lt = $t;
|
||||
|
||||
my $a1 = XmlEscape($p->{STATE});
|
||||
my $a2 = XmlEscape(getAllSets($d));
|
||||
my $a3 = XmlEscape(getAllAttr($d));
|
||||
|
||||
$str .= "\t\t<$t name=\"$d\" state=\"$a1\" sets=\"$a2\" attrs=\"$a3\">\n";
|
||||
|
||||
foreach my $c (sort keys %{$p}) {
|
||||
next if(ref($p->{$c}));
|
||||
$str .= sprintf("\t\t\t<INT key=\"%s\" value=\"%s\"/>\n",
|
||||
XmlEscape($c), XmlEscape($p->{$c}));
|
||||
}
|
||||
$str .= sprintf("\t\t\t<INT key=\"IODev\" value=\"%s\"/>\n",
|
||||
$p->{IODev}{NAME}) if($p->{IODev});
|
||||
|
||||
foreach my $c (sort keys %{$attr{$d}}) {
|
||||
$str .= sprintf("\t\t\t<ATTR key=\"%s\" value=\"%s\"/>\n",
|
||||
XmlEscape($c), XmlEscape($attr{$d}{$c}));
|
||||
}
|
||||
|
||||
my $r = $p->{READINGS};
|
||||
if($r) {
|
||||
foreach my $c (sort keys %{$r}) {
|
||||
my $h = $r->{$c};
|
||||
next if(!$h->{VAL} || !$h->{TIME});
|
||||
$str .=
|
||||
sprintf("\t\t\t<STATE key=\"%s\" value=\"%s\" measured=\"%s\"/>\n",
|
||||
XmlEscape($c), XmlEscape($h->{VAL}), $h->{TIME});
|
||||
}
|
||||
}
|
||||
$str .= "\t\t</$t>\n";
|
||||
}
|
||||
$str .= "\t</${lt}_LIST>\n" if($lt);
|
||||
$str .= "</FHZINFO>\n";
|
||||
return $str;
|
||||
}
|
||||
|
||||
|
||||
1;
|
435
fhem/HISTORY
435
fhem/HISTORY
|
@ -1,435 +0,0 @@
|
|||
- Rudi, Thu Feb 1 13:27:15 MET 2007
|
||||
Created the file HISTORY and the file README.DEV
|
||||
|
||||
- Pest, Thu Feb 1 20:45 MET 2007
|
||||
Added description for attribute ,
|
||||
|
||||
- Rudi, Sun Feb 11 18:56:05 MET 2007
|
||||
- showtime added for pgm2 (useful for FS20 piri display)
|
||||
- defattr command added, it makes easier to assign room, type, etc for a group
|
||||
of devices.
|
||||
- em1010.pl added to the contrib directory. It seems that we are able
|
||||
to read out the EM1010PC. Not verified with multiple devices.
|
||||
|
||||
- Pest, Thu Feb 11 23:35 MET 2007
|
||||
- Added doc/linux.html (multiple USDB devices, udev links)
|
||||
- Linked fhem.html and commandref.html to linux.html
|
||||
|
||||
- Martin Haas, Fri Feb 23 10:18 MET 2007
|
||||
- ARM-Section (NSLU2) added to doc/linux.html
|
||||
|
||||
- Pest, Sat Feb 24 18:30 MET 2007
|
||||
- doc/linux.html: Module build re-written.
|
||||
|
||||
- Rudi, Sun Mar 4 11:18:10 MET 2007
|
||||
Reorganization. Goal: making attribute adding/deleting more uniform
|
||||
(,
|
||||
possible (i.e. saving the configfile, list of possible devices etc).
|
||||
|
||||
Internal changes:
|
||||
- %logmods,%devmods moved to %modules. Makes things more uniform
|
||||
- %logs merged into %defs
|
||||
- local state info (%readings) changed to global ($defs{$d}{READINGS})
|
||||
-> No need for the listfn function in each module
|
||||
-> User written scripts can more easily analyze device states
|
||||
|
||||
User visible changes:
|
||||
- at/notify ,
|
||||
modules. Now it is possible
|
||||
- to have a further ,
|
||||
(notify & filelog use the same interface)
|
||||
- to have more than one notify for the same event
|
||||
- to delete at commands without strange escapes.
|
||||
The delete syntax changed (no more def/at/ntfy needed)
|
||||
- at/notify can have attributes
|
||||
Drawback: each at and notify must have a name, which is strange first.
|
||||
- logfile/modpath/pidfile/port/verbose ,
|
||||
Dumping and extending these attributes is easier, no special handling
|
||||
required in the web-frontend.
|
||||
- savefile renamed to ,
|
||||
- configfile global attribute added.
|
||||
- save command added, it writes the statefile and then the configfile.
|
||||
- delattr added to delete single attributes
|
||||
- list/xmllist format changed, they contain more information.
|
||||
- ,
|
||||
in the same format. This data is contained in the xmllist.
|
||||
- disable attribute for at/notify/filelog
|
||||
- rename added
|
||||
|
||||
- Rudi, Tue Mar 27 20:43:15 MEST 2007
|
||||
fhemweb.pl (webpgm2) changes:
|
||||
- adopted to the new syntax
|
||||
- better commandline support (return <pre> formatted)
|
||||
- FileLog attribute logtype added, and 4 logtypes (== gnuplot files)
|
||||
defined: fs20, fht, ks300_1, ks300_2
|
||||
- links in the commandref.html file added
|
||||
- device dependent attribute and set support
|
||||
|
||||
- Pest, Sun Apr 08 17:55:15 MEST 2007
|
||||
em1010.pl:
|
||||
- Make difference between sensors 1..4 and 5..
|
||||
- Checked values for sensor 5 (cur.energy + cur.power) - ok
|
||||
- Checked values for sensor 5 cur.energy is ok, cur.power still off.
|
||||
Correction factor needs to be determined.
|
||||
- setTime: Without argument, now the current time of the PC is used.
|
||||
- setRperKW: Factor of 10 required, 75 U/kWh -> 750.
|
||||
|
||||
- Pest, Tue Apr 10 20:31:22 MEST 2007
|
||||
em1010.pl:
|
||||
- Introduced new double-word function (dw)
|
||||
- getDevStatus: energy values kWh/h, kWh/d, total.
|
||||
|
||||
- Rudi Sat Apr 14 10:34:36 MEST 2007
|
||||
final documentations, release 4.0. Tagged as FHEM_4_0
|
||||
|
||||
- Pest, Sat Apr 14 14:21:00 MEST 2007
|
||||
- doc: linux.html (private udev-rules, not 50-..., ATTRS)
|
||||
|
||||
- Pest, Sun Apr 15 14:54:30 MEST 2007
|
||||
- doc: fhem.pl and commandref.html (notifyon -> notify, correction of examples)
|
||||
|
||||
- Rudi, Tue Apr 24 08:10:43 MEST 2007
|
||||
- feature: modify command added. It helps change e.g. only the time component
|
||||
for an at command, without deleting and creating it again and then
|
||||
reapplying all the attributes.
|
||||
- feature: the ,
|
||||
instead. The - is used to separate ranges in the set command.
|
||||
|
||||
- Rudi, Sun May 27 12:51:52 MEST 2007
|
||||
- Archiving FileLogs. Added fhemweb.pl (pgm2) code, to show logs from the
|
||||
archive directory. See the attributes archivedir/archivecmd.
|
||||
- Added EM1010PC suppoort (right now only with EM1000WZ). Support added
|
||||
for displaying logs in the fhemweb.pl (webfrontends/pgm2)
|
||||
|
||||
- Pest, Mon May 28 19:39:22 MEST 2007
|
||||
- Added 62_EMEM.pm to support EM1000-EM devices.
|
||||
- doc: Update of commandref.htm (typos and EMEM).
|
||||
|
||||
- Pest, Mon May 29 00:07:00 MEST 2007
|
||||
- check-in changes of 60_EM.pm to make EMEM work.
|
||||
|
||||
- Mon Jun 4 08:23:43 MEST 2007
|
||||
- Small changes for EM logging
|
||||
|
||||
- Pest Jun 10, 23:16:23 MEST 2007
|
||||
- Set wrong values in 62_EMEM to -1
|
||||
|
||||
- Pest Jun 12, 21:33:00 MEST 2007
|
||||
- in 62_EMEM.pm: added energy_today and energy_total
|
||||
|
||||
- Pest Jun 18, 20:06:23 MEST 2007
|
||||
- in 62_EMEM.pm: Power units removed from value content added to name.
|
||||
|
||||
- Rudi Sun Aug 5 10:59:51 MEST 2007
|
||||
- WS300 Loglevel changed for KS300 device (from 2 to GetLogLevel or 5)
|
||||
- First version of the FritzBox port:
|
||||
- Perl binary/ftdi_sio module
|
||||
- EM: added setTime, reset
|
||||
- docs/fritzbox.html. Note: The fb_fhem_0.1.tar.gz won't be part of CVS
|
||||
as it contains largee binaries (swapfile, perl mipsel executable, etc).
|
||||
|
||||
- Rudi Mon Aug 6 20:15:00 MEST 2007
|
||||
- archiving added to the main logs.
|
||||
NOTE: The FileLog filename (INT attribute) is now also called logfile.
|
||||
|
||||
- Rudi Wed Aug 29 08:28:34 MEST 2007
|
||||
- archive attributes clarified in the doc
|
||||
|
||||
- Rudi Mon Sep 3 15:47:59 MEST 2007
|
||||
- 99_Sunrise_EL.pm checked in. Replaces 99_Sunrise.pm, and does not need
|
||||
any Date module.
|
||||
|
||||
- Rudi Sun Sep 9 08:43:03 MEST 2007
|
||||
- mode holiday_short added + documentation. Not tested.
|
||||
any Date module.
|
||||
|
||||
- Rudi Wed Oct 3 18:21:36 MEST 2007
|
||||
- weblinks added. Used by webpgm2 to display more than one plot at once
|
||||
- webpgm2 output reformatted. Using CSS to divide the screen area in 3
|
||||
parts: command line, room-list and rest
|
||||
|
||||
- Dirk Wed Oct 7 12:45:09 MEST 2007
|
||||
- FHT lime-protection code discovered
|
||||
|
||||
- Dirk Wed Oct 18 23:28:00 MEST 2007
|
||||
- Softwarebuffer for FHT devices with queuing unsent commands and
|
||||
repeating commands by transmission failure
|
||||
- FHT low temperatur warning and setting for lowtemp-offset
|
||||
- Change naming for state into warnings
|
||||
Tagged as dirkh_20071019_0
|
||||
|
||||
- Martin Fri Dec 21 13:39:17 CET 2007
|
||||
- voip2fhem added (contrib/)
|
||||
|
||||
- Peter Sun Dec 23 19:59:00 MEST 2007
|
||||
- linux.html: Introduction refinement.
|
||||
|
||||
- Rudi Sat Dec 29 16:27:14 MET 2007
|
||||
- delattr renamed to deleteattr, as del should complete to delete and not to
|
||||
delattr
|
||||
- defattr renamed to setdefaultattr (same as before for def)
|
||||
- devicespec introduced:
|
||||
it may contain a list of devices, a range of devices, or multiple devices
|
||||
identified by regexp. Following commands take a devicespec as argument:
|
||||
attr, deleteattr, delete, get, list, set, setstate, trigger
|
||||
|
||||
- Boris Sat Dec 29 16:56:00 CET 2007
|
||||
- %NAME, %EVENT, %TYPE parameters in notify definition, commandref.html update
|
||||
|
||||
- Boris Sun Dec 30 22:35:00 CET 2007
|
||||
- added dblog/93_DbLog.pm and samples in contrib directory, commandref.html
|
||||
update
|
||||
|
||||
- Rudi Mon Dec 31 15:37:19 MET 2007
|
||||
- feature: webfrontend/pgm2 converted to a FHEM module
|
||||
No more need for a webserver for basic WEB administration. For HTTPS or
|
||||
password you still need apache or the like.
|
||||
One step closer for complete fhem on the FritzBox.
|
||||
|
||||
- Boris Sun Jan 06 13:35:00 CET 2008
|
||||
- bugfix: 62_EMEM.pm: changed reading energy_total_kWh to energy_kWh_w,
|
||||
added energy_kWh (formerly energy_total_kWh)
|
||||
- changed em1010.pl accordingly, added em1000em doc for getDevStatus reply
|
||||
from device
|
||||
- minor changes in fhem.html
|
||||
|
||||
- Rudi Tue Jan 8 21:13:08 MET 2008
|
||||
- feature: attr global allowfrom <ip-adresses/hostnames>
|
||||
If set, only connects from these addresses are allowed. This is to
|
||||
"simulate" a little bit of security.
|
||||
|
||||
- Rudi Sat Jan 19 18:04:12 MET 2008
|
||||
- FHT: multiple commands
|
||||
Up to 8 commands in one set, these are transmitted at once to the FHT
|
||||
- softbuffer changes
|
||||
minfhtbuffer attribute, as otherwise nearly everything will be sent to
|
||||
the FHT buffer, so ordering won't take effect.
|
||||
- cmd rename
|
||||
report1,report2. refreshvalues changed to report1 and report2. refreshvalues
|
||||
won't be advertized but still replaced with "report1 255 report2 255"
|
||||
- extensive documentation update for the FHT
|
||||
- lime-protection changed, as it is an actuator subcommand. Further actuator
|
||||
commands added.
|
||||
|
||||
- Rudi Sun Jan 27 18:12:42 MET 2008
|
||||
- em1010PC: sending a "67" after a reset skips the manual interaction:
|
||||
automatic reset is now possible.
|
||||
|
||||
- Peter S. Sat Feb 16 22:22:21 MET 2008
|
||||
- linux.html: Note on kernel 2.6.24.2 (includes our changes)
|
||||
|
||||
- Peter S. Wed Mar 19 08:24:00 MET 2008
|
||||
- 00_FHZ.pm: DoTriger -> DoTrigger
|
||||
|
||||
- Rudi Fri May 9 20:00:00 MEST 2008
|
||||
- feature: FHEM modules may live on a filesystem with "ignorant" casing (FAT)
|
||||
If you install FHEM on a USB-Stick (e.g. for the FritzBox) it may happen
|
||||
that the filename casing is different from the function names inside the
|
||||
file.
|
||||
-> Fhem won't find the <module>_Initialize function. Fixed by searching all
|
||||
function-names for a match with "ignore-case"
|
||||
- feature: FileLog function "set reopen" impemented. In case you want to
|
||||
delete some wrong entries from a current logfile, you must tell fhem to
|
||||
reopen the file again
|
||||
- feature: multiline commands are supported through the command line
|
||||
Up till now multiline commands were supported only by "include". Now they
|
||||
are supprted from the (tcp/ip) connection too, so they can be used by the
|
||||
web frontends.
|
||||
- feature: pgm2 installation changes, multiple instances, external css
|
||||
pgm2 (FHEMWEB) is now a "real" fhem module:
|
||||
- the configuration takes place via attributes
|
||||
- the css file is external, and each FHEMWEB instance can use its own set
|
||||
- the default location for pictures, gnuplot scripts and css is the FHEM
|
||||
module directory
|
||||
- multiline support for notify and at scripts.
|
||||
- feature: FileLog "set reopen" for manual tweaking of logfiles.
|
||||
- feature: multiline commands are supported through the command line
|
||||
- feature: pgm2 installation changes, multiple instances, external css
|
||||
|
||||
-tdressler Sa May 10 23:00:00 MEST 2008
|
||||
- feature:add WS2000 Support new modul 87_ws2000.pm and standalone
|
||||
reader/server ws2000_reader.pl
|
||||
- doc: modified fhem.html/commandref.html reflectiing ws2000 device and
|
||||
added windows support (tagged:before tdressler_20080510_1, after
|
||||
tdressler_20080510_2)
|
||||
|
||||
-tdressler So May 11 19:30:00 MEST 2008
|
||||
- feature: add ReadyFn to fhem.pl in main loop to have an alternative for
|
||||
select, which is not working on windows (thomas 11.05)
|
||||
- feature: set timeout to 0.2s, if HandleTimeout returns undef=forever
|
||||
(tagged tdressler_20080511_1/2)
|
||||
- bugfix : WS2000:fixed serial port access on windows by replacing FD with
|
||||
ReadyFn
|
||||
- bugfix : FileLog: dont use FH->sync on windows (not implemented there)
|
||||
- feature: EM, WS300, FHZ:Add Switch for Device::SerialPort and
|
||||
Win32::SerialPort to get it running in Windows (sorry, untestet)
|
||||
|
||||
-tdressler So May 11 23:30:00 MEST 2008
|
||||
- bugfix: FileLog undefined $data in FileLog_Get
|
||||
- feature: fhem.pl check modules for compiletime errors and do not initialize
|
||||
them if any
|
||||
- bugfix: EM, WS300, FHZ scope of portobj variable
|
||||
|
||||
-tdressler Mo May 12 14:00:00 MEST 2008
|
||||
- bugfix: FHZ with windows, use there ReadyFn if windows; small cosmetic
|
||||
changes
|
||||
- doc: add hint to virtual com port driver, modification for FHZ to use
|
||||
default FTDI driver
|
||||
|
||||
-tdressler Mo May 12 19:00:00 MEST 2008
|
||||
- feature : add windows support to M232
|
||||
|
||||
-tdressler So May 18 13:30:00 MEST 2008
|
||||
- feature : add ELV IPWE1 support
|
||||
|
||||
- Peter S. Mon Jun 02 00:39 MET 2008
|
||||
- linux.html: openSUSE 11 contains our changes.
|
||||
|
||||
- Thu Jun 12 07:15:03 MEST 2008
|
||||
- feature: FileLog get to read logfiles / webpgm2: gnuplot-scroll mode to
|
||||
navigate/zoom in logfiles
|
||||
webpgm2 uses the FileLog get to grep data for a given data range from a
|
||||
logfile. Using this grep scrolling to a different date range / zooming
|
||||
to another resolution (day/week/month/year) can be implemented.
|
||||
The logfiles should be large, as scrolling across logfiles is not
|
||||
implemented. To speed up the grep, a binary search with seek is used, and
|
||||
seek positions are cached.
|
||||
|
||||
- Thu Jul 11 07:15:03 MEST 2008
|
||||
- feature: 99_SVG.pm for webpgm2: generates SVG from the logfile.
|
||||
Generating SVG is configurable, the "old" gnuplot mode is still there.
|
||||
Downside of the SVG: the browser must support SVG (Firefox/Opera does,
|
||||
I.E. with the Adobe plugin), and the browsesr consumes more memory.
|
||||
Upside: no gnuplot needed on the server, faster(?), less data to transfer
|
||||
for daily data.
|
||||
Tested with Firefox 3.0.
|
||||
Todo: Test with IE+Adobe Plugin/Opera.
|
||||
- feature: HOWTO for webpgm2 (first chapter)
|
||||
|
||||
- Fri Jul 25 18:14:26 MEST 2008
|
||||
- Autoloading modules. In order to make module installation easier and
|
||||
to optimize memory usage, modules are loaded when the first device of a
|
||||
certain category is defined. Exceptions are the modules prefixed with 99,
|
||||
these are considered "utility" modules and are loaded at the beginning.
|
||||
Some of the older 99_x modules were renamed (99_SVG, 99_dummy), and most
|
||||
contrib modules were moved to the main FHEM directory.
|
||||
|
||||
- Boris Sat Nov 01 CET 2008
|
||||
- feature: new commands fullinit and reopen for FHZ, commandref.html update
|
||||
- bugfix: avoid access to undefined NotifyFn in hash in fhem.pl
|
||||
|
||||
- Boris Sun Nov 02 CET 2008
|
||||
- feature: new modules 00_CM11.pm and 20_X10.pm for integration of X10
|
||||
devices in fhem
|
||||
- feature: X10 support for pgm3
|
||||
|
||||
- Sat Nov 15 10:23:56 MET 2008 (Rudi)
|
||||
- Watchdog crash fixed: watchdog could insert itself more than once in the
|
||||
internal timer queue. The first one deletes all occurances from the list,
|
||||
but the loop over the list works on the cached keys -> the function/arg for
|
||||
the second key is already removed.
|
||||
- feature: X10 support for pgm3
|
||||
|
||||
- Boris Sat Nov 15 CET 2008
|
||||
- bugfix: correct correction factors for EMEM in 15_CUL_EM.pm
|
||||
|
||||
- Wed Dec 3 18:36:56 MET 2008 (Rudi)
|
||||
- reorder commandref.html, so that all aspects of a device
|
||||
(define/set/get/attributes) are in one block. This makes possible to
|
||||
"outsource" device documentation
|
||||
- added "mobile" flag to the CUL definition, intended for a CUR, which is
|
||||
a remote with a battery, so it is not connected all the time to fhem.
|
||||
Without the flag fhem will block when the CUR is disconnected.
|
||||
Note: we have to sleep after disconnect for 5 seconds, else the Linux
|
||||
kernel sends us a SIGSEGV, and the USB device is gone till the next reboot.
|
||||
- the fhem CUL part documented
|
||||
|
||||
- Sun Dec 7 21:09 (Boris)
|
||||
- reworked 15_CUL_EM.pm to account for timer wraparounds, more readings added
|
||||
- speed gain through disabled refreshvalues query to all FHTs at definition;
|
||||
if you want it back at a "set myFHT report1 255 report2 255" command to the
|
||||
config file.
|
||||
|
||||
- Mon Dec 8 21:26 MET 2008 (Rudi)
|
||||
- Modules can now modify the cmds hash, i.e. modules can add / overwrite /
|
||||
delete internal fhem commands. See 99_XmlList.pm for an example. Since this
|
||||
modules is called 99_xxx, it will be always loaded, but user of webpgm2 do
|
||||
not need it.
|
||||
|
||||
- Wed Dec 17 19:48 (Boris)
|
||||
- attribute rainadjustment for KS300 in 13_KS300.pm to account for random
|
||||
switches in the rain counter (see commandref.html)
|
||||
|
||||
- Fri Jan 2 10:29 2009 (Rudi)
|
||||
- 00_CUL responds to CUR request. These are sent as long FS20 messages, with
|
||||
a housecode stored in CUR_id_list attribute of the CUL device. If the ID
|
||||
matches, the message is analyzed, and an FS20 message to the same address
|
||||
is sent back. The CUR must have reception enabled.
|
||||
Right now status/set time/set FHT desired temp are implemented.
|
||||
|
||||
- Fri Jan 6 (Boris)
|
||||
- daily/monthly cumulated values for EMWZ/EMGZ/EMWM with 15_CUL_EM by Klaus
|
||||
|
||||
- Fri Jan 9
|
||||
- Added a unified dispatch for CUL/FHZ and CM11, since all of them used the
|
||||
same code.
|
||||
|
||||
- Addedd IODev attribute to FS20/FHT/HMS/KS300/CUL_WS/CUL/EMWZ/EMGZ/EMEM
|
||||
- Sun Jan 11 (Klaus)
|
||||
- Added fixedrange option day|week|month|year (for pgm2)
|
||||
e.g.: attr wlEnergiemonat fixedrange month
|
||||
- Added multiple room assignments for one device (for pgm2):
|
||||
e.g.: attr Heizvorlauftemp room Energie,Heizung
|
||||
- Added attr title and label(s) for more flexible .gplot files (for pgm2)
|
||||
e.g.: attr wl_KF title "Fenster:".$value{KellerFenster}.", Entfeuchter: ".$value{Entfeuchter}
|
||||
.gplot: <TL> (is almost there!)
|
||||
attr wl_KF label "Fenster":"Entfeuchter"
|
||||
.gplot: <L0> ... <L9> (constant text is to be replaced individually)
|
||||
- Added attr global logdir, used by wildcard %ld in perl.pm
|
||||
e.g.: attr global logdir /var/tmp
|
||||
define emGaslog FileLog %ld/emGas.log emGas:.*CNT.*
|
||||
|
||||
- Sat Feb 15 2009 (Boris)
|
||||
- added counter differential per time in 81_M232Counter.pm, commandref.html
|
||||
updated
|
||||
|
||||
- Thu Mar 29 2009 (MartinH)
|
||||
- pgm3: bugfix, format table for userdef
|
||||
- pgm3: feature X10_support, taillogorder optional with date
|
||||
- pgm3: HMS100CO added, fhem.html relating pgm3 updated
|
||||
|
||||
- Sat May 30 2009 (Rudi)
|
||||
- 99_SUNRISE_EL: sunrise/sunset called in "at" computes correctly the next
|
||||
event. New "sunrise()/sunset()" calls added, min max optional parameter.
|
||||
|
||||
- Sun May 31 2009 (Boris)
|
||||
- 81_M232Counter.pm: counter stops at 65536; workaround makes counter wraparound
|
||||
|
||||
- Mon Jun 01 2009 (Boris)
|
||||
- 59_Weather.pm: new virtual device for weather forecasts, documentation
|
||||
updated.
|
||||
|
||||
- Tue Jun 09 2009 (Boris)
|
||||
- 11_FHT.pm: lazy attribute for FHT devices
|
||||
|
||||
- Sun Jun 14 2009 (Rudi)
|
||||
- 11_FHT.pm: tmpcorr attribute for FHT devices
|
||||
|
||||
- Sat Jun 20 2009 (Boris)
|
||||
- 09_USF1000.pm: new module to support USF1000S devices.
|
||||
|
||||
- Fri Aug 08 2009 (Boris)
|
||||
- 09_USF1000.pm: suppress inplausible readings from USF1000
|
||||
|
||||
- Sat Sep 12 2009 (Boris)
|
||||
- 00_CM11.pm: feature: get time, fwrev, set reopen for CM11 (Boris 2009-09-12)
|
||||
|
||||
- Sun Sep 20 2009 (Boris)
|
||||
- Module 09_BS.pm for brightness sensor added (Boris 2009-09-20)
|
||||
|
||||
- Sat Oct 03 2009 (Boris)
|
||||
- bugfix: missing blank in attribute list for FHT; exclude report from lazy
|
||||
- typos and anchors in documentation corrected
|
||||
|
||||
- Sun Oct11 2009 (Boris)
|
||||
- finalized 09_BS.pm and documentation
|
|
@ -1,52 +0,0 @@
|
|||
BINDIR=/usr/local/bin
|
||||
MODDIR=/usr/local/lib
|
||||
VARDIR=/var/log/fhem
|
||||
|
||||
VERS=4.7
|
||||
DATE=2009-09-26
|
||||
|
||||
all:
|
||||
@echo Nothing to do for all.
|
||||
@echo To install, check the Makefile, and then \'make install\'
|
||||
@echo or \'make install-pgm2\' to install a web frontend too.
|
||||
|
||||
install:install-base
|
||||
-mv $(VARDIR)/fhem.cfg $(VARDIR)/fhem.cfg.`date "+%Y-%m-%d_%H:%M:%S"`
|
||||
cp examples/sample_fhem $(VARDIR)/fhem.cfg
|
||||
@echo
|
||||
@echo
|
||||
@echo Edit $(VARDIR)/fhem.cfg then type
|
||||
@echo perl $(BINDIR)/fhem.pl $(VARDIR)/fhem.cfg
|
||||
|
||||
install-pgm2:install-base
|
||||
cp -r webfrontend/pgm2/* $(MODDIR)/FHEM
|
||||
cp docs/commandref.html docs/faq.html docs/HOWTO.html $(MODDIR)/FHEM
|
||||
-mv $(VARDIR)/fhem.cfg $(VARDIR)/fhem.cfg.`date "+%Y-%m-%d_%H:%M:%S"`
|
||||
cp examples/sample_pgm2 $(VARDIR)/fhem.cfg
|
||||
cd examples; for i in *; do cp -r $$i $(MODDIR)/FHEM/example.$$i; done
|
||||
@echo
|
||||
@echo
|
||||
@echo Edit $(VARDIR)/fhem.cfg then type
|
||||
@echo perl $(BINDIR)/fhem.pl $(VARDIR)/fhem.cfg
|
||||
|
||||
install-base:
|
||||
mkdir -p $(BINDIR) $(MODDIR) $(VARDIR)
|
||||
cp fhem.pl $(BINDIR)
|
||||
cp -r FHEM $(MODDIR)
|
||||
perl -pi -e 's,modpath \.,modpath $(MODDIR),' examples/[a-z]*
|
||||
perl -pi -e 's,/tmp,$(VARDIR),' examples/[a-z]*
|
||||
|
||||
dist:
|
||||
@echo Version is $(VERS), Date is $(DATE)
|
||||
mkdir .f
|
||||
cp -r CHANGED FHEM HISTORY Makefile README.CVS\
|
||||
TODO contrib docs examples fhem.pl webfrontend .f
|
||||
find .f -name CVS -print | xargs rm -rf
|
||||
find .f -name \*.orig -print | xargs rm -f
|
||||
find .f -name .#\* -print | xargs rm -f
|
||||
find .f -type f -print |\
|
||||
xargs perl -pi -e 's/=VERS=/$(VERS)/g;s/=DATE=/$(DATE)/g'
|
||||
mv .f fhem-$(VERS)
|
||||
tar cf - fhem-$(VERS) | gzip > fhem-$(VERS).tar.gz
|
||||
mv fhem-$(VERS)/docs/*.html .
|
||||
rm -rf fhem-$(VERS)
|
|
@ -1,65 +0,0 @@
|
|||
The source of this project is hosted on Berlios, if you wish you can get
|
||||
the latest version with the following commands:
|
||||
cvs -d:pserver:anonymous@cvs.fhem.berlios.de:/cvsroot/fhem login
|
||||
cvs -z3 -d:pserver:anonymous@cvs.fhem.berlios.de:/cvsroot/fhem co fhem
|
||||
|
||||
If you wish to contribute to the project, then
|
||||
- create a berlios account
|
||||
- send an email to the project manager to add you as developer to the project
|
||||
(right know this is r dot koenig at koeniglich dot de)
|
||||
- check out the source with
|
||||
% cvs -z3 -d<berlios-uid>@cvs.berlios.de:/cvsroot/fhem co fhem
|
||||
- if it is already checked out, it makes sense to do an update before
|
||||
implementing your changes:
|
||||
% cvs update
|
||||
- make your changes
|
||||
- test if it is working (Really !!!)
|
||||
- make an entry in the CHANGED file, giving your changes a "title".
|
||||
- describe your changes in the file HISTORY, and dont forget to mention your
|
||||
name and the date of change
|
||||
- it makes sense to do a "cvs diff" before checking in the stuff with
|
||||
cvs commit
|
||||
- if you do complex/nontrivial changes affecting more than one file, then
|
||||
please tag the whole software before and after the change with:
|
||||
- before: % cvs tag <berlios-uid>_<date_as_YYYYMMDD>_0
|
||||
- after: % cvs tag <berlios-uid>_<date_as_YYYYMMDD>_1
|
||||
You can increase the counter for bugfixing. Dont forget to mention the
|
||||
tagname in the HISTORY file. Tagging helps to remove more complex changes or
|
||||
to merge them in other releases/branches.
|
||||
|
||||
|
||||
Some useful CVS commands/flags for the beginner:
|
||||
|
||||
|
||||
# Get the newest stuff from the server and merge it into your changes.
|
||||
# Watch out for lines beginning with C (collisions), edit them immediately
|
||||
# after check out, and look for ====
|
||||
# Without -d new directories won't be checked out
|
||||
cvs update -d .
|
||||
|
||||
# Before checking in, make sure you changed only what you intended:
|
||||
cvs diff filename
|
||||
|
||||
# Add new file. "-kb" adds binary files. Forgetting -kb will cause
|
||||
# problems if somebody is checking out on a different OS (windows)
|
||||
# Note: it is complicated to rename files in CVS, so think twice about
|
||||
# filenames before adding them. e.g. do not use version names in them.
|
||||
cvs add [-kb] filename
|
||||
|
||||
# Look at the change history
|
||||
cvs log <filename>
|
||||
|
||||
# Commit changes. Set the EDITOR environment variable to use your editor.
|
||||
cvs commit .
|
||||
|
||||
# Check which files were changed. Type ^C when it asks you to really release it.
|
||||
# (is there a more elegant way?)
|
||||
cvs release .
|
||||
|
||||
# We recommend to set some options in our ~/.cvsrc file:
|
||||
cvs -q
|
||||
update -d
|
||||
# The 'cvs -q' option will suppress some output e.g. during update
|
||||
# only the updated and unknown files etc. are displayed (not every
|
||||
# folder etc.).
|
||||
# The 'update -d' will automatically create folders as required.
|
13
fhem/TODO
13
fhem/TODO
|
@ -1,13 +0,0 @@
|
|||
FHEM:
|
||||
- RAWTIME
|
||||
|
||||
- Remote serial device via IP (for the FHZ1300 WLAN)
|
||||
- Common buffer for parallel use of two devices: CUL+FHZ, (WS300/EM1000PC?)
|
||||
- fhem-to-fhem module
|
||||
- CUR built-in MENU creation support
|
||||
- Remove or reimplement repeater attribute (cul/fhz/doc)
|
||||
|
||||
Webpgm2
|
||||
- plot data from multiple files in a single picture
|
||||
- setting the dummy state via dropdown is not possible
|
||||
- SVG gimmicks for the plot
|
|
@ -1,166 +0,0 @@
|
|||
##############################################
|
||||
package main;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use Device::SerialPort;
|
||||
use IO::Socket::INET;
|
||||
|
||||
my $fs10data = "";
|
||||
my $pcwsdsocket;
|
||||
|
||||
|
||||
#####################################
|
||||
sub
|
||||
FS10_Initialize($)
|
||||
{
|
||||
my ($hash) = @_;
|
||||
|
||||
# Consumer
|
||||
$hash->{DefFn} = "FS10_Define";
|
||||
$hash->{AttrList}= "model:FS10 loglevel:0,1,2,3,4,5,6";
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub
|
||||
FS10_Define($$)
|
||||
{
|
||||
my ($hash, $def) = @_;
|
||||
my @a = split("[ \t][ \t]*", $def);
|
||||
|
||||
Log 3, "FS10 Define: $a[0] $a[1] $a[2] $a[3]";
|
||||
|
||||
return "Define the host and portnr as a parameter i.e. 127.0.0.1 4711"
|
||||
if(@a != 4);
|
||||
|
||||
$hash->{Timer} = 600;
|
||||
$hash->{Host} = $a[2];
|
||||
$hash->{Port} = $a[3];
|
||||
$hash->{STATE} = "Initialized";
|
||||
|
||||
my $dev = $a[2];
|
||||
Log 1, "FS10 device is none, commands will be echoed only"
|
||||
if($dev eq "none");
|
||||
|
||||
$hash->{DeviceName} = $dev;
|
||||
|
||||
FS10_GetStatus($hash);
|
||||
return undef;
|
||||
}
|
||||
|
||||
|
||||
#####################################
|
||||
sub
|
||||
FS10_GetStatus($)
|
||||
{
|
||||
my ($hash) = @_;
|
||||
my $buf;
|
||||
#my $banner;
|
||||
my $reqcmd;
|
||||
my $fs10time;
|
||||
my $dt;
|
||||
my $x;
|
||||
my $result = "";
|
||||
|
||||
Log 3, "FS10_GetStatus";
|
||||
|
||||
# Call us in 5 minutes again.
|
||||
InternalTimer(gettimeofday()+300, "FS10_GetStatus", $hash, 0);
|
||||
|
||||
my $dnr = $hash->{DEVNR};
|
||||
my $name = $hash->{NAME};
|
||||
my $host = $hash->{Host};
|
||||
my $port = $hash->{Port};
|
||||
my %vals;
|
||||
my $pcwsd ="$host:$port";
|
||||
my $pcwsdsocket = IO::Socket::INET->new( $pcwsd )
|
||||
or return "FS10 Can't bind to pcwsd" if(!$pcwsdsocket);
|
||||
|
||||
my $banner = $pcwsdsocket->getline();
|
||||
my @x = split(" ", $banner);
|
||||
my @y;
|
||||
my $fs10name;
|
||||
|
||||
for(my $i = 0; $i < 8; $i++) #Outdoor
|
||||
{
|
||||
$fs10name ="Ta$i";
|
||||
$reqcmd = "get od2temp $i\r\n";
|
||||
$pcwsdsocket->print($reqcmd);
|
||||
$buf = $pcwsdsocket->getline();
|
||||
$result = "$result $buf";
|
||||
|
||||
@x = split(" ", $buf);
|
||||
$fs10time = FmtDateTime($x[1]);
|
||||
|
||||
$hash->{CHANGED}[$i] = "Ta$i: $x[0]";
|
||||
$hash->{READINGS}{$fs10name}{TIME} = $fs10time;
|
||||
$hash->{READINGS}{$fs10name}{VAL} = $x[0];
|
||||
}
|
||||
|
||||
$fs10name="Ti";
|
||||
$reqcmd = "get idtemp 7\r\n";
|
||||
$pcwsdsocket->print($reqcmd);
|
||||
$buf = $pcwsdsocket->getline();
|
||||
@x = split(" ", $buf);
|
||||
$fs10time = FmtDateTime($x[1]);
|
||||
|
||||
$hash->{CHANGED}[8] = "Ti: $x[0]";
|
||||
$hash->{READINGS}{$fs10name}{TIME} = $fs10time;
|
||||
$hash->{READINGS}{$fs10name}{VAL} = $x[0];
|
||||
|
||||
$fs10name="Rain";
|
||||
$reqcmd = "get rain 7\r\n";
|
||||
$pcwsdsocket->print($reqcmd);
|
||||
$buf = $pcwsdsocket->getline();
|
||||
@x = split(" ", $buf);
|
||||
$fs10time = FmtDateTime($x[1]);
|
||||
|
||||
$hash->{CHANGED}[9] = "Rain: $x[0]";
|
||||
$hash->{READINGS}{$fs10name}{TIME} = $fs10time;
|
||||
$hash->{READINGS}{$fs10name}{VAL} = $x[0];
|
||||
|
||||
$fs10name="Sun";
|
||||
$reqcmd = "get bright 7\r\n";
|
||||
$pcwsdsocket->print($reqcmd);
|
||||
$buf = $pcwsdsocket->getline();
|
||||
@x = split(" ", $buf);
|
||||
$fs10time = FmtDateTime($x[1]);
|
||||
|
||||
$hash->{CHANGED}[10] = "Sun: $x[0]";
|
||||
$hash->{READINGS}{$fs10name}{TIME} = $fs10time;
|
||||
$hash->{READINGS}{$fs10name}{VAL} = $x[0];
|
||||
|
||||
$fs10name="Windspeed";
|
||||
$reqcmd = "get wspd 7\r\n";
|
||||
$pcwsdsocket->print($reqcmd);
|
||||
$buf = $pcwsdsocket->getline();
|
||||
@x = split(" ", $buf);
|
||||
$fs10time = FmtDateTime($x[1]);
|
||||
|
||||
$hash->{CHANGED}[11] = "Windspeed: $x[0]";
|
||||
$hash->{READINGS}{$fs10name}{TIME} = $fs10time;
|
||||
$hash->{READINGS}{$fs10name}{VAL} = $x[0];
|
||||
|
||||
close($pcwsdsocket);
|
||||
|
||||
$result =~ s/[\r\n]//g;
|
||||
DoTrigger($name, undef) if($init_done);
|
||||
|
||||
$hash->{STATE} = "$result";
|
||||
Log 3,"FS10 Result: $result";
|
||||
return $hash->{STATE};
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub
|
||||
FS10Log($$)
|
||||
{
|
||||
my ($a1, $a2) = @_;
|
||||
|
||||
#define n31 notify fs10 {FS10Log("@", "%")}
|
||||
#define here notify action
|
||||
|
||||
Log 2,"FS10 $a1 = $a2 old: $oldvalue{$a1}{TIME}=> $oldvalue{$a1}{VAL});";
|
||||
}
|
||||
|
||||
1;
|
|
@ -1,97 +0,0 @@
|
|||
##############################################
|
||||
# Example for logging KS300 data into a DB.
|
||||
#
|
||||
# Prerequisites:
|
||||
# - The DBI and the DBD::<dbtype> modules must be installed.
|
||||
# - a Database is created/configured
|
||||
# - a db table: create table FHZLOG (TIMESTAMP varchar(20), TEMP varchar(5),
|
||||
# HUM varchar(3), WIND varchar(4), RAIN varchar(8));
|
||||
# - Change the content of the dbconn variable below
|
||||
# - extend your FHEM config file with
|
||||
# notify .*H:.* {DbLog("@","%")}
|
||||
# - copy this file into the <modpath>/FHEM and restart fhem.pl
|
||||
#
|
||||
# If you want to change this setup, your starting point is the DbLog function
|
||||
|
||||
my $dbconn = "Oracle:DBNAME:user:password";
|
||||
|
||||
package main;
|
||||
use strict;
|
||||
use warnings;
|
||||
use DBI;
|
||||
|
||||
my $dbh;
|
||||
|
||||
sub DbDo($);
|
||||
sub DbConnect();
|
||||
|
||||
|
||||
################################################################
|
||||
sub
|
||||
DbLog_Initialize($)
|
||||
{
|
||||
my ($hash) = @_;
|
||||
|
||||
# Lets connect here, so we see the error at startup
|
||||
DbConnect();
|
||||
}
|
||||
|
||||
################################################################
|
||||
sub
|
||||
DbLog($$)
|
||||
{
|
||||
my ($a1, $a2) = @_;
|
||||
|
||||
# a2 is like "T: 21.2 H: 37 W: 0.0 R: 0.0 IR: no"
|
||||
my @a = split(" ", $a2);
|
||||
my $tm = TimeNow();
|
||||
|
||||
DbDo("insert into FHZLOG (TIMESTAMP, TEMP, HUM, WIND, RAIN) values " .
|
||||
"('$tm', '$a[1]', '$a[3]', '$a[5]', '$a[7]')");
|
||||
}
|
||||
|
||||
|
||||
################################################################
|
||||
sub
|
||||
DbConnect()
|
||||
{
|
||||
return 1 if($dbh);
|
||||
Log 5, "Connecting to database $dbconn";
|
||||
my @a = split(":", $dbconn);
|
||||
$dbh = DBI->connect("dbi:$a[0]:$a[1]", $a[2], $a[3]);
|
||||
if(!$dbh) {
|
||||
Log 1, "Can't connect to $a[1]: $DBI::errstr";
|
||||
return 0;
|
||||
}
|
||||
Log 5, "Connection to db $a[1] established";
|
||||
return 1;
|
||||
}
|
||||
|
||||
################################################################
|
||||
sub
|
||||
DbDo($)
|
||||
{
|
||||
my $str = shift;
|
||||
|
||||
return 0 if(!DbConnect());
|
||||
Log 5, "Executing $str";
|
||||
my $sth = $dbh->do($str);
|
||||
if(!$sth) {
|
||||
Log 2, "DB: " . $DBI::errstr;
|
||||
$dbh->disconnect;
|
||||
$dbh = 0;
|
||||
return 0 if(!DbConnect());
|
||||
#retry
|
||||
$sth = $dbh->do($str);
|
||||
if($sth)
|
||||
{
|
||||
Log 2, "Retry ok: $str";
|
||||
return 1;
|
||||
}
|
||||
#
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
1;
|
|
@ -1,149 +0,0 @@
|
|||
#############################################
|
||||
# Low Budget ALARM System
|
||||
##############################################
|
||||
# ATTENTION! This is more a toy than a professional alarm system!
|
||||
# You must know what you do!
|
||||
##############################################
|
||||
#
|
||||
# Concept:
|
||||
# 1x Signal Light (FS20 allight) to show the status (activated/deactivated)
|
||||
# 2x Sirene (in/out) (FS20 alsir1 alsir2 )
|
||||
# 2x PIRI-2 (FS20 piriu pirio)
|
||||
# 1x Sender (FS20 alsw) to activate/deactivate the system.
|
||||
# Tip: use the KeyMatic CAC with pin code or
|
||||
# optional a normal sender (FS20 alsw2)
|
||||
#
|
||||
# Add something like the following lines to the configuration file :
|
||||
# notifyon alsw {MyAlsw()}
|
||||
# notifyon alsw2 {MyAlswNoPin()}
|
||||
# notifyon piriu {MyAlarm()}
|
||||
# notifyon pirio {MyAlarm()}
|
||||
# and put this file in the <modpath>/FHZ1000 directory.
|
||||
#
|
||||
# Martin Haas
|
||||
##############################################
|
||||
|
||||
|
||||
package main;
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
sub
|
||||
ALARM_Initialize($$)
|
||||
{
|
||||
my ($hash) = @_;
|
||||
}
|
||||
|
||||
|
||||
##############################################
|
||||
# Switching Alarm System on or off
|
||||
sub
|
||||
MyAlsw()
|
||||
{
|
||||
my $ON="set allight on; setstate alsw on";
|
||||
my $OFF="set allight off; set alsir1 off; set alsir2 off; setstate alsw off";
|
||||
|
||||
if ( -e "/var/tmp/alertsystem")
|
||||
{
|
||||
unlink "/var/tmp/alertsystem";
|
||||
#Paranoia
|
||||
for (my $i = 0; $i < 2; $i++ )
|
||||
{
|
||||
fhem "$OFF";
|
||||
};
|
||||
Log 2, "alarm system is OFF";
|
||||
} else {
|
||||
system "touch /var/tmp/alertsystem";
|
||||
#Paranoia
|
||||
for (my $i = 0; $i < 2; $i++ )
|
||||
{
|
||||
fhem "$ON"
|
||||
}
|
||||
Log 2, "alarm system is ON";
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
##############################################
|
||||
# If you have no Keymatic then use this workaround:
|
||||
# After 4x pushing a fs20-button within some seconds it will activate/deactivate the alarm system.
|
||||
sub
|
||||
MyAlswNoPin()
|
||||
{
|
||||
|
||||
my $timedout=5;
|
||||
|
||||
## first time
|
||||
if ( ! -e "/var/tmp/alontest1")
|
||||
{
|
||||
for (my $i = 1; $i < 4; $i++ )
|
||||
{
|
||||
system "touch -t 200601010101 /var/tmp/alontest$i";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
## test 4 times
|
||||
my $now= `date +%s`;
|
||||
for (my $i = 1; $i < 4; $i++ )
|
||||
{
|
||||
my $tagx=`date -r /var/tmp/alontest$i +%s`;
|
||||
my $testx=$now-$tagx;
|
||||
|
||||
if ( $testx > $timedout )
|
||||
{
|
||||
system "touch /var/tmp/alontest$i";
|
||||
die "test$i: more than $timedout sec";
|
||||
}
|
||||
}
|
||||
system "touch -t 200601010101 /var/tmp/alontest*";
|
||||
Log 2, "ok, let's switch the alarm system...";
|
||||
|
||||
#if you only allow to activate (and not deactivate) with this script:
|
||||
# if ( -e "/var/tmp/alertsystem") { die "deactivating alarm system not allowed"};
|
||||
|
||||
MyAlsw();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
##############################################
|
||||
# ALARM! Do what you want!
|
||||
sub
|
||||
MyAlarm()
|
||||
{
|
||||
|
||||
#alarm-system activated??
|
||||
if ( -e "/var/tmp/alertsystem")
|
||||
{
|
||||
|
||||
my $timer=180; # time until the sirene will be quiet
|
||||
my $ON1="set alsir1 on-for-timer $timer";
|
||||
my $ON2="set alsir2 on-for-timer $timer";
|
||||
|
||||
|
||||
#Paranoia
|
||||
for (my $i = 0; $i < 2; $i++ )
|
||||
{
|
||||
fhem "$ON1";
|
||||
fhem "$ON2";
|
||||
}
|
||||
Log 2, "ALARM! #################" ;
|
||||
|
||||
|
||||
# have fun
|
||||
my @lights=("stuwz1", "stuwz2", "nachto", "nachtu", "stoliba" ,"stlileo");
|
||||
my @rollos=("rolu4", "rolu5", "roloadi", "rololeo", "roloco", "rolowz", "rolunik1", "rolunik2");
|
||||
|
||||
foreach my $light (@lights) {
|
||||
fhem "set $light on"
|
||||
}
|
||||
|
||||
foreach my $rollo (@rollos) {
|
||||
fhem "set $rollo on"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
1;
|
|
@ -1,50 +0,0 @@
|
|||
##############################################
|
||||
# Example perl functions. Put this file into the FHEM directory.
|
||||
#
|
||||
# # Activate 2 rollades at once with one button, open them to
|
||||
# # a different degree.
|
||||
# define ntfy_1 notifyon btn3 {MyFunc("@", "%")}
|
||||
#
|
||||
# # Swith the heater off if all FHT actuators are closed,
|
||||
# # and on if at least one is open
|
||||
# define at_1 at +*00:05 { fhem "set heater " . (sumactuator()?"on":"off") };
|
||||
|
||||
package main;
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
sub
|
||||
PRIV_Initialize($$)
|
||||
{
|
||||
my ($hash, $init) = @_;
|
||||
}
|
||||
|
||||
sub
|
||||
sumactuator()
|
||||
{
|
||||
my $sum = 0;
|
||||
foreach my $d (keys %defs) {
|
||||
next if($defs{$d}{TYPE} ne "FHT");
|
||||
my ($act, undef) = split(" ", $defs{$d}{READINGS}{"actuator"}{VAL});
|
||||
$act =~ s/%//;
|
||||
$sum += $act;
|
||||
}
|
||||
return $sum;
|
||||
}
|
||||
|
||||
sub
|
||||
MyFunc($$)
|
||||
{
|
||||
my ($a1, $a2) = @_;
|
||||
|
||||
Log 2, "Device $a1 was set to $a2 (type: $defs{$a1}{TYPE})";
|
||||
if($a2 eq "on") {
|
||||
fhem "set roll1 on-for-timer 10";
|
||||
fhem "set roll2 on-for-timer 16";
|
||||
} else {
|
||||
fhem "set roll1 off";
|
||||
fhem "set roll2 off";
|
||||
}
|
||||
}
|
||||
|
||||
1;
|
|
@ -1,89 +0,0 @@
|
|||
##############################################
|
||||
# - Use 99_SUNRISE_EL.pm instead of this module
|
||||
# - Be aware: Installing the DateTime modules might be tedious, one way is:
|
||||
# perl -MCPAN -e shell
|
||||
# cpan> install DateTime::Event::Sunrise
|
||||
# - Please call sunrise_coord before using this module, else you'll get times
|
||||
# for frankfurt am main (germany). See the "at" entry in commandref.html
|
||||
|
||||
package main;
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use DateTime;
|
||||
use DateTime::Event::Sunrise;
|
||||
|
||||
sub sr($$$$);
|
||||
sub sunrise_rel(@);
|
||||
sub sunset_rel(@);
|
||||
sub sunrise_abs(@);
|
||||
sub sunset_abs(@);
|
||||
sub isday();
|
||||
sub sunrise_coord($$$);
|
||||
sub SUNRISE_Initialize($);
|
||||
|
||||
# See perldoc DateTime::Event::Sunrise for details
|
||||
my $long = "8.686";
|
||||
my $lat = "50.112";
|
||||
my $tz = "Europe/Berlin";
|
||||
|
||||
sub
|
||||
SUNRISE_Initialize($)
|
||||
{
|
||||
my ($hash) = @_;
|
||||
}
|
||||
|
||||
|
||||
##########################
|
||||
# Compute:
|
||||
# rise: 1: event is sunrise (else sunset)
|
||||
# isrel: 1: _relative_ times until the next event (else absolute for today)
|
||||
# seconds: second offset to event
|
||||
# daycheck: if set, then return 1 if the sun is visible, 0 else
|
||||
sub
|
||||
sr($$$$)
|
||||
{
|
||||
my ($rise, $seconds, $isrel, $daycheck) = @_;
|
||||
|
||||
my $sunrise = DateTime::Event::Sunrise ->new(
|
||||
longitude => $long,
|
||||
latitude => $lat,
|
||||
altitude => '-6', # Civil twilight
|
||||
iteration => '3');
|
||||
my $now = DateTime->now(time_zone => $tz);
|
||||
my $stm = ($rise ? $sunrise->sunrise_datetime( $now ) :
|
||||
$sunrise->sunset_datetime( $now ));
|
||||
|
||||
if($daycheck) {
|
||||
return 0 if(DateTime->compare($now, $stm) < 0);
|
||||
$stm = $sunrise->sunset_datetime( $now );
|
||||
return 0 if(DateTime->compare($now, $stm) > 0);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if(!$isrel) {
|
||||
$stm = $stm->add(seconds => $seconds) if($seconds);
|
||||
return $stm->hms();
|
||||
}
|
||||
|
||||
$stm = $stm->add(seconds => $seconds) if($seconds);
|
||||
|
||||
if(DateTime->compare($now, $stm) >= 0) {
|
||||
my $tom = DateTime->now(time_zone => $tz)->add(days => 1);
|
||||
$stm = ($rise ? $sunrise->sunrise_datetime( $tom ) :
|
||||
$sunrise->sunset_datetime( $tom ));
|
||||
$stm = $stm->add(seconds => $seconds) if($seconds);
|
||||
}
|
||||
|
||||
my $diff = $stm->epoch - $now->epoch;
|
||||
return sprintf("%02d:%02d:%02d", $diff/3600, ($diff/60)%60, $diff%60);
|
||||
}
|
||||
|
||||
sub sunrise_rel(@) { return sr(1, shift, 1, 0) }
|
||||
sub sunset_rel(@) { return sr(0, shift, 1, 0) }
|
||||
sub sunrise_abs(@) { return sr(1, shift, 0, 0) }
|
||||
sub sunset_abs(@) { return sr(0, shift, 0, 0) }
|
||||
sub isday() { return sr(1, 0, 0, 1) }
|
||||
sub sunrise_coord($$$) { ($long, $lat, $tz) = @_; return undef; }
|
||||
|
||||
1;
|
|
@ -1,53 +0,0 @@
|
|||
##############################################
|
||||
# VarDump for FHEM-Devices
|
||||
##############################################
|
||||
# Installation
|
||||
# 99_dumpdef.pm ins FHEM-Verzeichis kopieren
|
||||
# dann: "reload 99_dumpdef.pm"
|
||||
##############################################
|
||||
# Aufruf: dumpdef "DEVICE-NAME"
|
||||
##############################################
|
||||
# Aufruf: dumpdef <XXX>
|
||||
# <MOD> = %modules
|
||||
# <SEL> = %selectlist
|
||||
# <CMD> = %cmds
|
||||
# <DAT> = %data
|
||||
##############################################
|
||||
package main;
|
||||
use strict;
|
||||
use warnings;
|
||||
use POSIX;
|
||||
use Data::Dumper;
|
||||
sub Commanddumpdef($);
|
||||
|
||||
#####################################
|
||||
sub
|
||||
dumpdef_Initialize($)
|
||||
{
|
||||
my %lhash = ( Fn=>"Commanddumpdef",
|
||||
Hlp=>"Dump <devspec> to FHEMWEB & LOG" );
|
||||
$cmds{dumpdef} = \%lhash;
|
||||
}
|
||||
|
||||
|
||||
#####################################
|
||||
sub Commanddumpdef($)
|
||||
{
|
||||
my ($cl, $d) = @_;
|
||||
return "Usage: dumpdef <DeviceName>" if(!$d);
|
||||
my($package, $filename, $line, $subroutine) = caller(3);
|
||||
my $r = "CALLER => $package: $filename LINE: $line SUB: $subroutine \n";
|
||||
$r .= "SUB-NAME: " .(caller(0))[3] . "\n";
|
||||
if($d eq "CMD") {$r .= Dumper(%cmds) . "\n"; return $r; }
|
||||
if($d eq "DAT") {$r .= Dumper(%data) . "\n"; return $r; }
|
||||
if($d eq "MOD") {$r .= Dumper(%modules) . "\n"; return $r; }
|
||||
if($d eq "SEL") {$r .= Dumper(%selectlist) . "\n"; return $r; }
|
||||
if(!defined($defs{$d})) {
|
||||
return "Unkown Device";}
|
||||
$r .= "DUMP-DEVICE: $d \n";
|
||||
$r .= Dumper($defs{$d}) . "\n";
|
||||
$r .= "DUMP-DEVICE-ATTR \n";
|
||||
$r .= Dumper($attr{$d}) . "\n";
|
||||
return $r;
|
||||
}
|
||||
1;
|
|
@ -1,488 +0,0 @@
|
|||
################################################################################
|
||||
# FHEM PRIV-CGI
|
||||
# Stand: 08/2009
|
||||
# Update:
|
||||
# 08/2009 ROOMS -> Übersicht aller Räume mit Devices und STATE
|
||||
# 08/2009 READINGS -> Übersicht aller READIMGS nach Datum -> READING -> Device
|
||||
# 08/2009 Excute FHEMCommands /privcgi?Task=EXEC&cmd=FHEMCOMMAND&dev=DEVICENAME&attr=ATTRIBUTE&val=Value
|
||||
################################################################################
|
||||
# Danke an Rudi für die Schnittstelle....
|
||||
# Dies Modul als Beispiel für die Nutzung der Schnittstelle...
|
||||
# Das Modul verändert nichts an FHEM
|
||||
################################################################################
|
||||
# Beschreibung
|
||||
# Es werden lediglich vorhanden Information aus FHEM in eigenen Ansichten/Listen dargestellt.
|
||||
#
|
||||
# Ansicht/List
|
||||
# ALL -> Überblick über alle Devices
|
||||
# FHT -> Übersicht aller FHT's incl. Programme
|
||||
# FS20 -> Übersicht alle FS20-Devices
|
||||
# TH -> Alle Devices (die ich habe) die eine Temperatur oder Luftfeuchte messen (FHT,KS300,HMS,S300TH...)
|
||||
# ROOMS -> Übersicht aller Räume mit Devices und STATE
|
||||
# READINGS -> Übersicht aller READINGS; Gruppiert nach Datum -> READING -> Device
|
||||
# DUMMY -> Überischt aller DUMMY-Devices (als Beispiel für eigene Functionen)
|
||||
################################################################################
|
||||
# Installation
|
||||
#
|
||||
# Modul ins FHEM-Modul Verzeichnis kopieren
|
||||
# entweder FHEM neu starten
|
||||
# oder "reload 99_priv_cgi.pm"
|
||||
#
|
||||
################################################################################
|
||||
# Aufruf:
|
||||
# Bsp.: FHEMWEB => http://localhost:8083/fhem
|
||||
# PRIV-CGI => http://localhost:8083/fhem/privcgi
|
||||
#
|
||||
# Eigene Erweiterungen implementieren:
|
||||
# Aufruf: http://localhost:8083/fhem/privcgi?Type=FHT&Task=List
|
||||
# A. Ergänzung LIST-Funktion
|
||||
# - Eigene Funktion schreiben z.B. sub priv_cgi_my_function($)
|
||||
# - Eigenen Key festlegen z.B. myKey
|
||||
# - Function sub priv_cgi_Initialize($) ergänzen $data{$cgi_key}{TASK_LIST}{TYPE}{myKey} = "priv_cgi_my_function";
|
||||
# - reload 99_priv_cgi.pm
|
||||
#
|
||||
# B. Eigene Funktion
|
||||
# - z.B. MyFunc
|
||||
# - eigenen Key im HASH $data{$cgi_key}{TASK} erzeugen
|
||||
# - $data{$cgi_key}{TASK}{MyFunc} = "Function_Aufruf"
|
||||
##############################################
|
||||
package main;
|
||||
use strict;
|
||||
use warnings;
|
||||
use Data::Dumper;
|
||||
use vars qw(%data);
|
||||
|
||||
sub priv_cgi_Initialize($)
|
||||
{
|
||||
my $cgi_key = "privcgi";
|
||||
my $fhem_url = "/" . $cgi_key ;
|
||||
$data{FWEXT}{$fhem_url}{FUNC} = "priv_cgi_callback";
|
||||
$data{FWEXT}{$fhem_url}{LINK} = "privcgi";
|
||||
$data{FWEXT}{$fhem_url}{NAME} = "MyFHEM";
|
||||
$data{$cgi_key}{QUERY} = {};
|
||||
# Default: in Case of /privcgi
|
||||
# Task=List&Type=FHT
|
||||
$data{$cgi_key}{default}{QUERY} = "Task=List&Type=ALL";
|
||||
# Dispatcher Functions
|
||||
# Task = List -> Call Function
|
||||
$data{$cgi_key}{TASK}{List} = "priv_cgi_list";
|
||||
# List -> Type -> Call Function
|
||||
$data{$cgi_key}{TASK_LIST}{TYPE} = {};;
|
||||
$data{$cgi_key}{TASK_LIST}{TYPE}{ALL} = "priv_cgi_print_all";
|
||||
$data{$cgi_key}{TASK_LIST}{TYPE}{FHT} = "priv_cgi_print_fht";
|
||||
$data{$cgi_key}{TASK_LIST}{TYPE}{FS20} = "priv_cgi_print_fs20";
|
||||
$data{$cgi_key}{TASK_LIST}{TYPE}{TH} = "priv_cgi_print_th";
|
||||
$data{$cgi_key}{TASK_LIST}{TYPE}{ROOMS} = "priv_cgi_print_rooms";
|
||||
$data{$cgi_key}{TASK_LIST}{TYPE}{READINGS} = "priv_cgi_print_readings";
|
||||
# $data{$cgi_key}{TASK_LIST}{TYPE}{DUMMY} = "priv_cgi_print_dummy";
|
||||
|
||||
# ExcuteFhemCommands
|
||||
# /privcgi?EXEC=FHEMCOMMAD&DEVICE&VALUE-1&VALUE-2
|
||||
# /privcgi?Task=EXEC&cmd=FHEMCOMMAND&dev=DEVICENAME&attr=VALUE-1
|
||||
$data{$cgi_key}{TASK}{EXEC} = "priv_cgi_exec";
|
||||
}
|
||||
|
||||
sub
|
||||
priv_cgi_callback($$)
|
||||
{
|
||||
my ($htmlarg) = @_;
|
||||
my ($ret_html, $func,$qtask);
|
||||
my $cgikey = &priv_cgi_get_start($htmlarg);
|
||||
Log 0, "CGI-KEY: $cgikey";
|
||||
# Dispatch TASK... choose Function
|
||||
$qtask = $data{$cgikey}{QUERY}{Task};
|
||||
$func = $data{$cgikey}{TASK}{$qtask};
|
||||
Log 0, "Func: $func";
|
||||
no strict "refs";
|
||||
# Call Function
|
||||
$ret_html .= &$func($cgikey);
|
||||
use strict "refs";
|
||||
Log 1, "Got $htmlarg";
|
||||
return ("text/html; charset=ISO-8859-1", $ret_html);
|
||||
}
|
||||
|
||||
sub
|
||||
priv_cgi_get_start($)
|
||||
{
|
||||
my $in = shift;
|
||||
print "CGI_START: " . Dumper(@_) . "\n";
|
||||
my (@tmp,$n,$v,$cgikey,$param);
|
||||
# Aufruf mit oder ohne Argumente
|
||||
# /privcgi oder /privcgi??Type=FHT&Task=List
|
||||
if($in =~ /\?/)
|
||||
{
|
||||
# Aufruf mit Argumenten: /privcgi?Type=FHT&Task=List
|
||||
@tmp = split(/\?/, $in);
|
||||
$cgikey = shift(@tmp);
|
||||
$cgikey =~ s/\///;
|
||||
$param = shift(@tmp);
|
||||
}
|
||||
else
|
||||
{
|
||||
$cgikey = $in;
|
||||
# Aufruf OHNE Argumenten: /privcgi
|
||||
$cgikey =~ s/\///;
|
||||
# Default Werte
|
||||
$param = $data{$cgikey}{default}{QUERY};
|
||||
}
|
||||
# Param nach $data{$cgikey}{QUERY} schreiben
|
||||
Log 0, "PRIV-CGI: START -> param: " . $param;
|
||||
@tmp = split(/&/, $param);
|
||||
foreach my $pair(@tmp)
|
||||
{
|
||||
($n,$v) = split(/=/, $pair);
|
||||
Log 0, "PRIV-CGI: START -> param: $n - $v";
|
||||
$data{$cgikey}{QUERY}{$n} = $v;
|
||||
}
|
||||
return $cgikey;
|
||||
}
|
||||
|
||||
|
||||
sub
|
||||
priv_cgi_html_head($)
|
||||
{
|
||||
# HTML-Content for HEAD
|
||||
my $cgikey = shift;
|
||||
my $html = "<!DOCTYPE html PUBLIC \"-\/\/W3C\/\/DTD HTML 4.01\/\/EN\" \"http:\/\/www.w3.org\/TR\/html4\/strict.dtd\">\n";
|
||||
$html .= "<html>\n";
|
||||
$html .= "<head>\n";
|
||||
$html .= "<style type=\"text/css\"><!--";
|
||||
$html .= "\#hdr {margin: 0em 0em 1em 0em;padding: 0em 1em;background-color: \#CCCCCC;}";
|
||||
$html .= "\#left {float: left; width: 15%; padding: 1em;}";
|
||||
$html .= "\#right {float: left;width: 70%;}";
|
||||
$html .= "body {font-size: 14px;padding: 0px;margin: 0px;font-family: 'Courier New', Courier, Monospace;";
|
||||
$html .= "\/\/--><\/style>";
|
||||
$html .= "<title>FHEM PRIV-CGI<\/title>\n";
|
||||
$html .= "<\/head>\n";
|
||||
$html .= "<body>\n";
|
||||
return $html;
|
||||
}
|
||||
|
||||
sub
|
||||
priv_cgi_html_body_div_hdr($)
|
||||
{
|
||||
# HTML-Content BODY & DIV-ID HDR
|
||||
my $cgikey = shift;
|
||||
my $html = "<div id=\"hdr\">";
|
||||
$html .= "<h3><a href=\"/fhem\">FHEM</a></h3>\n";
|
||||
$html .= "<p style=\"font-size:8pt;\">";
|
||||
$html .= $attr{global}{version} . "<br></p>\n";
|
||||
$html .= "<hr><br>\n";
|
||||
return $html;
|
||||
}
|
||||
|
||||
sub
|
||||
priv_cgi_html_div_left($)
|
||||
{
|
||||
# HTML-Content BODY & DIV-ID LEFT
|
||||
my $cgikey = shift;
|
||||
my $html = "<\/div>";
|
||||
$html .= "<div id=\"left\">";
|
||||
$html .= "<h3>Ansichten:<h3>";
|
||||
$html .= "<form method=\"get\" action=\"\/fhem\/privcgi\" name=\"myfhem\">\n";
|
||||
$html .= "<select name=\"Type\">\n";
|
||||
|
||||
foreach my $d (sort keys %{$data{$cgikey}{TASK_LIST}{TYPE}}) {
|
||||
$html .= "<option value=\"$d\">$d</option>\n";
|
||||
}
|
||||
$html .= "</select>\n";
|
||||
$html .= "<input name=\"Task\" value=\"List\"type=\"submit\"><br>\n";
|
||||
$html .= "</form>\n";
|
||||
$html .= "<\/div>";
|
||||
return $html ;
|
||||
}
|
||||
|
||||
sub
|
||||
priv_cgi_list($)
|
||||
{
|
||||
my $cgikey = shift;
|
||||
my $html;
|
||||
Log 0,"PRIV_CGI_LIST: START";
|
||||
# HTML-HEAD
|
||||
$html = &priv_cgi_html_head($cgikey);
|
||||
# HTML-BODY-DIV-HDR
|
||||
$html .= &priv_cgi_html_body_div_hdr($cgikey);
|
||||
# HTML-BODY-DIV-ID-LEFT
|
||||
$html .= &priv_cgi_html_div_left($cgikey);
|
||||
my $type = $data{$cgikey}{QUERY}{Type};
|
||||
Log 0,"PRIV_CGI_LIST: TYPE = " . $type;
|
||||
my $func = $data{$cgikey}{TASK_LIST}{TYPE}{$type};
|
||||
Log 0,"PRIV_CGI_LIST: TYPE = $type -> Func -> $func";
|
||||
no strict "refs";
|
||||
# Call Function
|
||||
$html .= &$func;
|
||||
use strict "refs";
|
||||
|
||||
# HTML-BODY-FOOTER
|
||||
$html .= priv_cgi_html_footer();
|
||||
return $html;
|
||||
}
|
||||
sub
|
||||
priv_cgi_html_footer()
|
||||
{
|
||||
# HTML-BODY Footer
|
||||
my $html = "<\/body>\n";
|
||||
$html .= "<\/html>\n";
|
||||
return $html;
|
||||
}
|
||||
|
||||
sub priv_cgi_print_fs20()
|
||||
{
|
||||
my $str = "<table summary=\"List of FS20 devices\">\n";
|
||||
$str .= "<tr ALIGN=LEFT><th>Name<\/th><th>Model<\/th><th>State<\/th><th>Code<\/th><th>Button<\/th><th>Room<\/th><\/tr>\n";
|
||||
$str .= "<colgroup>\n";
|
||||
$str .= "<col width=\"130\"><col width=\"130\"><col width=\"130\"><col width=\"130\">\n";
|
||||
$str .= "</colgroup>\n";
|
||||
foreach my $d (sort keys %defs) {
|
||||
next if($defs{$d}{TYPE} ne "FS20");
|
||||
$str .= "<tr ALIGN=LEFT><td>" . $d . "<\/td><td>" . $attr{$d}{model} . "<\/td><td>". $defs{$d}{STATE} . "<\/td><td>". $defs{$d}{XMIT} . "<\/td><td>". $defs{$d}{BTN} . "<\/td><td>". $attr{$d}{room} . "<\/td><\/tr>\n";
|
||||
}
|
||||
$str .= "<\/table>\n";
|
||||
return ($str);
|
||||
}
|
||||
sub priv_cgi_print_fht()
|
||||
{
|
||||
my ($str,@fp);
|
||||
$str = "<table class=\"Fht\" summary=\"List of fht devices\">\n";
|
||||
$str .= "<tr ALIGN=LEFT><th>Name<\/th><th>Ventil<\/th><th>Ziel<\/th><th>Aktuell<\/th>" ;
|
||||
$str .= "<th>Nacht<\/th><th>Tag<\/th><th>Fenster<\/th><th>IODev<\/th><th>Time<\/th><th>CODE<\/th><\/tr>\n";
|
||||
# Init Tabel FHT-Program
|
||||
$fp[0] .= "<th></th>";
|
||||
$fp[1] .= "<td>Montag</td>";
|
||||
$fp[2] .= "<td></td>";
|
||||
$fp[3] .= "<td>Dienstag</td>";
|
||||
$fp[4] .= "<td></td>";
|
||||
$fp[5] .= "<td>Mittwoch</td>";
|
||||
$fp[6] .= "<td></td>";
|
||||
$fp[7] .= "<td>Donnerstag</td>";
|
||||
$fp[8] .= "<td></td>";
|
||||
$fp[9] .= "<td>Freitag</td>";
|
||||
$fp[10] .= "<td></td>";
|
||||
$fp[11] .= "<td>Samstag</td>";
|
||||
$fp[12] .= "<td></td>";
|
||||
$fp[13] .= "<td>Sonntag</td>";
|
||||
$fp[14] .= "<td></td>";
|
||||
|
||||
|
||||
# actuator desired-temp measured-temp night-temp day-temp windowopen-temp
|
||||
foreach my $d (sort keys %defs)
|
||||
{
|
||||
next if($defs{$d}{TYPE} ne "FHT");
|
||||
$str .= "<tr ALIGN=LEFT>" ;
|
||||
$str .= "<td>" . $d . "<\/td>" ;
|
||||
$str .= "<td>" . $defs{$d}{READINGS}{"actuator"}{VAL} . "<\/td>" ;
|
||||
$str .= "<td>" . $defs{$d}{READINGS}{"desired-temp"}{VAL} . "<\/td>" ;
|
||||
$str .= "<td>" . $defs{$d}{READINGS}{"measured-temp"}{VAL} . "<\/td>" ;
|
||||
$str .= "<td>" . $defs{$d}{READINGS}{"night-temp"}{VAL} . "<\/td>" ;
|
||||
$str .= "<td>" . $defs{$d}{READINGS}{"day-temp"}{VAL} . "<\/td>" ;
|
||||
$str .= "<td>" . $defs{$d}{READINGS}{"windowopen-temp"}{VAL} . "<\/td>" ;
|
||||
$str .= "<td>" . $defs{$d}{IODev}{NAME} . "<\/td>" ;
|
||||
$str .= "<td>" . $defs{$d}{READINGS}{"actuator"}{TIME} . "<\/td>" ;
|
||||
$str .= "<td>" . $defs{$d}{CODE} . "<\/td>" ;
|
||||
$str .= "<\/tr>\n";
|
||||
# FHT-Programme
|
||||
no strict "subs";
|
||||
$fp[0] .= "<th>" . $d . "</th>";
|
||||
$fp[1] .= "<td>" . $defs{$d}{READINGS}{'mon-from1'}{VAL} . "-" . $defs{$d}{READINGS}{'mon-to1'}{VAL} . "</td>";
|
||||
$fp[2] .= "<td>" . $defs{$d}{READINGS}{'mon-from2'}{VAL} . "-" . $defs{$d}{READINGS}{'mon-to2'}{VAL} . "</td>";
|
||||
$fp[3] .= "<td>" . $defs{$d}{READINGS}{'tue-from1'}{VAL} . "-" . $defs{$d}{READINGS}{'tue-to1'}{VAL} . "</td>";
|
||||
$fp[4] .= "<td>" . $defs{$d}{READINGS}{'tue-from2'}{VAL} . "-" . $defs{$d}{READINGS}{'tue-to2'}{VAL} . "</td>";
|
||||
$fp[5] .= "<td>" . $defs{$d}{READINGS}{'wed-from1'}{VAL} . "-" . $defs{$d}{READINGS}{'wed-to1'}{VAL} . "</td>";
|
||||
$fp[6] .= "<td>" . $defs{$d}{READINGS}{'wed-from2'}{VAL} . "-" . $defs{$d}{READINGS}{'wed-to2'}{VAL} . "</td>";
|
||||
$fp[7] .= "<td>" . $defs{$d}{READINGS}{'thu-from1'}{VAL} . "-" . $defs{$d}{READINGS}{'thu-to1'}{VAL} . "</td>";
|
||||
$fp[8] .= "<td>" . $defs{$d}{READINGS}{'thu-from2'}{VAL} . "-" . $defs{$d}{READINGS}{'thu-to2'}{VAL} . "</td>";
|
||||
$fp[9] .= "<td>" . $defs{$d}{READINGS}{'fri-from1'}{VAL} . "-" . $defs{$d}{READINGS}{'fri-to1'}{VAL} . "</td>";
|
||||
$fp[10] .= "<td>" . $defs{$d}{READINGS}{'fri-from2'}{VAL} . "-" . $defs{$d}{READINGS}{'fri-to2'}{VAL} . "</td>";
|
||||
$fp[11] .= "<td>" . $defs{$d}{READINGS}{'sat-from1'}{VAL} . "-" . $defs{$d}{READINGS}{'sat-to1'}{VAL} . "</td>";
|
||||
$fp[12] .= "<td>" . $defs{$d}{READINGS}{'sat-from2'}{VAL} . "-" . $defs{$d}{READINGS}{'sat-to2'}{VAL} . "</td>";
|
||||
$fp[13] .= "<td>" . $defs{$d}{READINGS}{'sun-from1'}{VAL} . "-" . $defs{$d}{READINGS}{'sun-to1'}{VAL} . "</td>";
|
||||
$fp[14] .= "<td>" . $defs{$d}{READINGS}{'sun-from2'}{VAL} . "-" . $defs{$d}{READINGS}{'sun-to2'}{VAL} . "</td>";
|
||||
use strict "subs";
|
||||
}
|
||||
$str .= "<\/table>\n";
|
||||
|
||||
$str .= "<br>\n";
|
||||
$str .= "<table>\n";
|
||||
$str .= "<colgroup>\n";
|
||||
$str .= "<col width=\"130\"><col width=\"130\"><col width=\"130\"><col width=\"130\">\n";
|
||||
$str .= "<col width=\"130\"><col width=\"130\"><col width=\"130\"><col width=\"130\">\n";
|
||||
$str .= "</colgroup>\n";
|
||||
|
||||
foreach (@fp) {
|
||||
$str .= "<tr ALIGN=LEFT>" . $_ . "</tr>\n";
|
||||
}
|
||||
$str .= "<\/table>\n";
|
||||
return ($str);
|
||||
}
|
||||
sub priv_cgi_print_dummy()
|
||||
{
|
||||
my $str = "<table summary=\"List of Dummy devices\">\n";
|
||||
$str .= "<colgroup>\n";
|
||||
$str .= "<col width=\"130\"><col width=\"130\">\n";
|
||||
$str .= "</colgroup>\n";
|
||||
$str .= "<tr ALIGN=LEFT><th>Name<\/th><th>State<\/th><\/tr>\n";
|
||||
foreach my $d (keys %defs) {
|
||||
next if($defs{$d}{TYPE} ne "dummy");
|
||||
$str .= "<tr ALIGN=LEFT><td>" . $d . "<\/td><td>". $defs{$d}{STATE} . "<\/td><\/tr>\n";}
|
||||
$str .= "<\/table>\n";
|
||||
return ($str);
|
||||
|
||||
}
|
||||
|
||||
sub priv_cgi_print_th()
|
||||
{
|
||||
# List All-Devices with Temp od Humidity
|
||||
my ($type,$str,$s,$t,$h,$i);
|
||||
$str = "<table summary=\"List of ALL devices\">\n";
|
||||
$str .= "<tr ALIGN=LEFT><th>Name</th><th>Temperature</th><th>Humidity</th><th>Information</th><th>Type</th><th>Room</th></tr>";
|
||||
foreach my $d (sort keys %defs) {
|
||||
$type = $defs{$d}{TYPE};
|
||||
next if(!($type =~ m/^(FHT|HMS|KS300|CUL_WS)/));
|
||||
$t = "";
|
||||
$h = "";
|
||||
$i = "";
|
||||
if ($type eq "FHT"){
|
||||
$i = $defs{$d}{'READINGS'}{'warnings'}{'VAL'};
|
||||
$t = $defs{$d}{'READINGS'}{'measured-temp'}{'VAL'};
|
||||
$t =~ s/\(Celsius\)//;};
|
||||
if ($type eq "HMS" || $type eq "CUL_WS"){
|
||||
$i = $defs{$d}{'READINGS'}{'battery'}{'VAL'};
|
||||
$t = $defs{$d}{'READINGS'}{'temperature'}{'VAL'};
|
||||
$t =~ s/\(Celsius\)//;
|
||||
$h = $defs{$d}{'READINGS'}{'humidity'}{'VAL'};
|
||||
$h =~ s/\(%\)//;};
|
||||
if ($type eq "KS300"){
|
||||
$i = "Raining: " . $defs{$d}{'READINGS'}{'israining'}{'VAL'};
|
||||
$i =~ s/\(yes\/no\)//;
|
||||
$t = $defs{$d}{'READINGS'}{'temperature'}{'VAL'};
|
||||
$t =~ s/\(Celsius\)//;
|
||||
$h = $defs{$d}{'READINGS'}{'humidity'}{'VAL'};
|
||||
$h =~ s/\(%\)//;};
|
||||
$str .= "<tr ALIGN=LEFT><td>" . $d . "<\/td><td>". $t . "<\/td><td>". $h . "<\/td><td>". $i . "<\/td><td>". $type . "<\/td><td>". $attr{$d}{room} . "<\/td><\/tr>\n";
|
||||
}
|
||||
$str .= "<\/table>\n";
|
||||
return ($str);
|
||||
}
|
||||
sub priv_cgi_print_all()
|
||||
{
|
||||
# List All-Devices
|
||||
my ($type,$str,$s,$t,$h,$i);
|
||||
$str = "<table summary=\"List of ALL devices\">\n";
|
||||
$str .= "<tr ALIGN=LEFT><th>Name</th><th>State</th><th>Type</th><th>Model</th><th>Room</th><th>IODev</th></tr>";
|
||||
foreach my $d (sort keys %defs)
|
||||
{
|
||||
$str .= "<tr ALIGN=LEFT><td>" . $d . "<\/td><td>". $defs{$d}{STATE} . "<\/td><td>". $defs{$d}{TYPE} . "<\/td><td>". $attr{$d}{model} . "<\/td><td>". $attr{$d}{room} . "<\/td><td>". $defs{$d}{IODev}{NAME} . "<\/td><\/tr>\n";
|
||||
}
|
||||
$str .= "<\/table>\n";
|
||||
return ($str);
|
||||
}
|
||||
sub priv_cgi_print_rooms()
|
||||
{
|
||||
my ($str,$r,$d,$ri);
|
||||
my %rooms = ();
|
||||
# Quelle 01_FHEMWEB.pm ...
|
||||
foreach $d (sort keys %defs ) {
|
||||
foreach my $r (split(",", FW_getAttr($d, "room", "Unsorted"))) {
|
||||
$rooms{$r}{$d} = $defs{$d}{STATE};}
|
||||
}
|
||||
# print Dumper(%rooms);
|
||||
# Tabelle
|
||||
# Raum | DEVICE | TYPE | MODELL | STATE
|
||||
$str = "<table>";
|
||||
$str .= "<tr ALIGN=LEFT><th>Raum</th><th>Device</th><th>Type</th><th>Model</th><th>State</th></tr>";
|
||||
foreach $r (sort keys %rooms)
|
||||
{
|
||||
$ri = 0;
|
||||
# $str .= "<tr><td>" . $r . "</td><td></td><td></td><td></td><td></td></tr>\n";
|
||||
foreach $d (sort keys %{$rooms{$r}}){
|
||||
if($ri eq 0) {$str .= "<tr bgcolor=\"#CCCCCC\"><td>" . $r . "</td>";}
|
||||
else {$str .= "<tr><td></td>"}
|
||||
# $str .= "<tr><td></td><td>" . $d . "</td>";
|
||||
$str .= "<td>" . $d . "</td>";
|
||||
$str .= "<td>" . $defs{$d}{TYPE} . "</td>";
|
||||
$str .= "<td>" . $attr{$d}{model} . "</td>";
|
||||
$str .= "<td>" . $defs{$d}{STATE} . "</td></tr>\n";
|
||||
$ri++;
|
||||
}
|
||||
}
|
||||
$str .= "</table>";
|
||||
return ($str);
|
||||
}
|
||||
sub priv_cgi_print_readings()
|
||||
{
|
||||
my ($d,$r,$d1,$str,@tmp);
|
||||
# Übersicht aller READINGS
|
||||
# Tabelle:
|
||||
# READING
|
||||
# DATUM
|
||||
# DEVICE VALUE TIME
|
||||
# %reads{DATUM}{READINGS}{DEVICE}{READINGS}{VALUE} = VAL
|
||||
# %reads{DATUM}{READINGS}{DEVICE}{READINGS}{TIME} = ZEIT
|
||||
my (%reads,$readings,$datum,$device,$value,$zeit);
|
||||
foreach $device (sort keys %defs )
|
||||
{
|
||||
foreach $r (sort keys %{$defs{$device}{READINGS}})
|
||||
{
|
||||
@tmp = split(' ', $defs{$device}{READINGS}{$r}{TIME});
|
||||
$readings = $r;
|
||||
$datum = $tmp[0];
|
||||
$value = $defs{$device}{READINGS}{$r}{VAL};
|
||||
$zeit = $tmp[1];
|
||||
$reads{$datum}{$readings}{$device}{$readings}{VALUE} = $defs{$device}{READINGS}{$r}{VAL};
|
||||
$reads{$datum}{$readings}{$device}{$readings}{TIME} = $zeit;
|
||||
}
|
||||
}
|
||||
$str = "<table>\n";
|
||||
# Counter
|
||||
my ($ri,$di);
|
||||
# Datum
|
||||
foreach $r (sort keys %reads)
|
||||
{
|
||||
# READINGS
|
||||
$ri = 0;
|
||||
foreach $d (sort keys %{$reads{$r}})
|
||||
{
|
||||
$di = 0;
|
||||
foreach $d1 (sort keys %{$reads{$r}{$d}})
|
||||
{
|
||||
if($ri eq 0){$str .= "<tr bgcolor=\"#CCCCCC\"><td>" . $r . "</td>";}
|
||||
else{$str .= "<tr><td></td>";}
|
||||
if($di eq 0) {$str .= "<td>" . $d . "</td>";}
|
||||
else {$str .= "<td></td>"}
|
||||
$str .= "<td>" . $d1 . "</td><td>" . $reads{$r}{$d}{$d1}{$d}{VALUE} . "</td><td>" .$reads{$r}{$d}{$d1}{$d}{TIME} . "</td></tr>\n";
|
||||
$di++;
|
||||
}
|
||||
$ri++;
|
||||
}
|
||||
|
||||
}
|
||||
$str .= "</table>\n";
|
||||
return ($str);
|
||||
}
|
||||
sub
|
||||
priv_cgi_exec($$)
|
||||
{
|
||||
# /privcgi?Task=EXEC&cmd=FHEMCOMMAND&dev=DEVICENAME&attr=ATTRIBUTE&val=Value
|
||||
# Task=EXEC&cmd=set&dev=WaWaZiDATA&attr=active&val=100
|
||||
# Task=EXEC&cmd=attr&dev=WaWaZiDATA&attr=room&val=PRIVCGIEXEC
|
||||
Log 0, "PRIVCGIEXEC: @_\n";
|
||||
my $cgikey = shift;
|
||||
my $ret_param = "text/plain; charset=ISO-8859-1";
|
||||
my $ret_txt = undef;
|
||||
my $cmd = lc($data{$cgikey}{QUERY}{cmd});
|
||||
my $dev = $data{$cgikey}{QUERY}{dev};
|
||||
my $attr = $data{$cgikey}{QUERY}{attr};
|
||||
my $val = $data{$cgikey}{QUERY}{val};
|
||||
Log 0, "PRIVCGIEXEC: FHEM-Command: $cmd $dev $attr $val\n";
|
||||
if(!defined($cmds{$cmd}))
|
||||
{
|
||||
return ($ret_param, "PRIVCGIEXEC: unkown COMMAND $cmd");
|
||||
}
|
||||
if(!defined($defs{$dev}))
|
||||
{
|
||||
return ($ret_param, "PRIVCGIEXEC: unknown DEVICE $dev");
|
||||
}
|
||||
$ret_txt = AnalyzeCommand(undef, "$cmd $dev $attr $val");
|
||||
return ($ret_param, $ret_txt);
|
||||
}
|
||||
1;
|
|
@ -1,386 +0,0 @@
|
|||
################################################################
|
||||
#
|
||||
# Copyright notice
|
||||
#
|
||||
# (c) 2008 Copyright: Martin Fischer (m_fischer at gmx dot de)
|
||||
# All rights reserved
|
||||
#
|
||||
# This script 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.
|
||||
#
|
||||
# The GNU General Public License can be found at
|
||||
# http://www.gnu.org/copyleft/gpl.html.
|
||||
# A copy is found in the textfile GPL.txt and important notices to the license
|
||||
# from the author is found in LICENSE.txt distributed with these scripts.
|
||||
#
|
||||
# This script 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.
|
||||
#
|
||||
################################################################
|
||||
# examples:
|
||||
# jsonslist - returns all definitions and status infos
|
||||
# jsonlist lamp1 - returns definitions and status infos for 'lamp1'
|
||||
# jsonlist FS20 - returns status infos for FS20 devices
|
||||
# jsonlist ROOMS - returns a list of rooms
|
||||
################################################################
|
||||
|
||||
package main;
|
||||
use strict;
|
||||
use warnings;
|
||||
use POSIX;
|
||||
|
||||
sub CommandJsonList($$);
|
||||
sub JsonEscape($);
|
||||
sub PrintHashJson($$);
|
||||
|
||||
|
||||
#####################################
|
||||
sub
|
||||
JsonList_Initialize($$)
|
||||
{
|
||||
my %lhash = ( Fn=>"CommandJsonList",
|
||||
Hlp=>"[<devspec>|<devtype>|rooms],list definitions and status info or rooms as JSON" );
|
||||
$cmds{jsonlist} = \%lhash;
|
||||
}
|
||||
|
||||
|
||||
#####################################
|
||||
sub
|
||||
JsonEscape($)
|
||||
{
|
||||
my $a = shift;
|
||||
return "null" if(!$a);
|
||||
my %esc = (
|
||||
"\n" => '\n',
|
||||
"\r" => '\r',
|
||||
"\t" => '\t',
|
||||
"\f" => '\f',
|
||||
"\b" => '\b',
|
||||
"\"" => '\"',
|
||||
"\\" => '\\\\',
|
||||
"\'" => '\\\'',
|
||||
);
|
||||
$a =~ s/([\x22\x5c\n\r\t\f\b])/$esc{$1}/eg;
|
||||
return $a;
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub
|
||||
PrintHashJson($$)
|
||||
{
|
||||
my ($h, $lev) = @_;
|
||||
|
||||
my ($str,$sstr) = ("","");
|
||||
|
||||
my $hc = keys %{$h};
|
||||
my $cc = 1;
|
||||
|
||||
foreach my $c (sort keys %{$h}) {
|
||||
|
||||
if(ref($h->{$c})) {
|
||||
if(ref($h->{$c}) eq "HASH" && $c ne "PortObj") {
|
||||
if($c eq "IODev" || $c eq "HASH") {
|
||||
$str .= sprintf("%*s\"%s\": \"%s\"", $lev," ",$c, JsonEscape($h->{$c}{NAME}));
|
||||
} else {
|
||||
$str .= sprintf("%*s\"%s\": {\n", $lev, " ", $c);
|
||||
if(keys(%{$h->{$c}}) != 0) {
|
||||
$str .= PrintHashJson($h->{$c}, $lev+2);
|
||||
} else {
|
||||
$str .= sprintf("%*s\"null\": \"null\"\n", $lev+4, " ");
|
||||
}
|
||||
$str .= sprintf("%*s}", $lev, " ");
|
||||
}
|
||||
} elsif(ref($h->{$c}) eq "ARRAY") {
|
||||
$str .= sprintf("%*s\"%s\": \"%s\"", $lev," ",$c, "ARRAY");
|
||||
} elsif($c eq "PortObj") {
|
||||
$str .= sprintf("%*s\"%s\": \"%s\"", $lev," ",$c, "PortObj");
|
||||
}
|
||||
} else {
|
||||
$str .= sprintf("%*s\"%s\": \"%s\"", $lev," ",$c, JsonEscape($h->{$c}));
|
||||
}
|
||||
$str .= ",\n" if($cc != $hc);
|
||||
$str .= "\n" if($cc == $hc);
|
||||
$cc++;
|
||||
}
|
||||
|
||||
return $str;
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub
|
||||
CommandJsonList($$)
|
||||
{
|
||||
my ($cl, $param) = @_;
|
||||
my $lt = "";
|
||||
my $str = "";
|
||||
|
||||
# Text indentation
|
||||
my $lev = 2;
|
||||
|
||||
if(!$param) {
|
||||
# Array counter
|
||||
my @ac;
|
||||
my $ac = 0;
|
||||
my $cc = 0;
|
||||
my @dc;
|
||||
my $dc = 0;
|
||||
my $tc = 0; # total available
|
||||
my $tr = 0; # results returned
|
||||
|
||||
my $q = "";
|
||||
|
||||
# Open JSON object
|
||||
$str = "{\n";
|
||||
$str .= sprintf("%*s\"ResultSet\": {\n", $lev, " ");
|
||||
# Open JSON array
|
||||
$str .= sprintf("%*s\"Results\": [\n", $lev+2, " ");
|
||||
|
||||
delete($modules{""}) if(defined($modules{""}));
|
||||
@dc = keys(%defs);
|
||||
$dc = @dc;
|
||||
#$tc = 0;
|
||||
for my $d (sort { my $x = $modules{$defs{$a}{TYPE}}{ORDER} cmp
|
||||
$modules{$defs{$b}{TYPE}}{ORDER};
|
||||
$x = ($a cmp $b) if($x == 0); $x; } keys %defs) {
|
||||
|
||||
my $p = $defs{$d};
|
||||
my $t = $p->{TYPE};
|
||||
$t = $q if($q ne "");
|
||||
|
||||
$str .= sprintf("} ") if($t eq $lt);
|
||||
$str .= sprintf("},\n") if($t eq $lt);
|
||||
|
||||
if($t ne $lt) {
|
||||
$str .= sprintf("} ") if($lt);
|
||||
$str .= sprintf("}\n") if($lt);
|
||||
$str .= sprintf("%*s]\n", $lev+6, " ") if($lt);
|
||||
$str .= sprintf("%*s},\n", $lev+4, " ") if($lt);
|
||||
#$str .= sprintf("%*s{\n", $lev+4, " ");
|
||||
$str .= sprintf("%*s\{ \"%s_LIST\": [\n", $lev+4, " ", $t);
|
||||
}
|
||||
$lt = $t;
|
||||
|
||||
my $a1 = JsonEscape($p->{STATE});
|
||||
my $a2 = JsonEscape(getAllSets($d));
|
||||
my @sets;
|
||||
foreach my $k2 (split(" ", $a2)) {
|
||||
push @sets, $k2;
|
||||
}
|
||||
my $a3 = JsonEscape(getAllAttr($d));
|
||||
my @attrs;
|
||||
foreach my $k3 (split(" ", $a3)) {
|
||||
push @attrs, $k3;
|
||||
}
|
||||
|
||||
#$str .= sprintf("%*s{\n", $lev+8, " ");
|
||||
$str .= sprintf("%*s{ \"%s\": { ", $lev+8, " ", $t);
|
||||
$str .= sprintf("\"name\": \"%s\", ", $d);
|
||||
$str .= sprintf("\"state\": \"%s\", ", $a1);
|
||||
$str .= sprintf("\"sets\": [ ");
|
||||
|
||||
$ac = @sets;
|
||||
$cc = 0;
|
||||
foreach my $set (@sets) {
|
||||
$str .= sprintf("{ \"VAL\": \"%s\" }", $set);
|
||||
$cc++;
|
||||
#$str .= ",\n" if($cc != $ac);
|
||||
$str .= ", " if($cc != $ac);
|
||||
#$str .= "\n" if($cc == $ac);
|
||||
}
|
||||
$str .= sprintf(" ], ");
|
||||
$str .= sprintf("\"attrs\": [ ");
|
||||
$ac = @attrs;
|
||||
$cc = 0;
|
||||
foreach my $attr (@attrs) {
|
||||
$str .= sprintf("{ \"VAL\": \"%s\" }", $attr);
|
||||
$cc++;
|
||||
#$str .= ",\n" if($cc != $ac);
|
||||
$str .= "," if($cc != $ac);
|
||||
#$str .= "\n" if($cc == $ac);
|
||||
}
|
||||
$str .= sprintf(" ], ");
|
||||
|
||||
$str .= sprintf("\"INT\": { ");
|
||||
@ac = keys(%{$p});
|
||||
$ac = 0;
|
||||
foreach my $k (sort @ac) {
|
||||
next if(ref($p->{$k}));
|
||||
$ac++;
|
||||
}
|
||||
$cc = 0;
|
||||
|
||||
foreach my $c (sort keys %{$p}) {
|
||||
next if(ref($p->{$c}));
|
||||
$str .= sprintf("\"%s\": \"%s\"",
|
||||
JsonEscape($c), JsonEscape($p->{$c}));
|
||||
$cc++;
|
||||
#$str .= ",\n" if($cc != $ac || ($cc == $ac && $p->{IODev}));
|
||||
$str .= ", " if($cc != $ac || ($cc == $ac && $p->{IODev}));
|
||||
#$str .= "\n" if($cc == $ac && !$p->{IODev});
|
||||
}
|
||||
$str .= sprintf("\"IODev\": \"%s\" ",
|
||||
$p->{IODev}{NAME}) if($p->{IODev});
|
||||
$str .= sprintf(" }, ");
|
||||
|
||||
$str .= sprintf("\"ATTR\": { ");
|
||||
@ac = keys(%{$attr{$d}});
|
||||
$ac = @ac;
|
||||
$cc = 0;
|
||||
foreach my $c (sort keys %{$attr{$d}}) {
|
||||
$str .= sprintf("\"%s\": \"%s\"",
|
||||
JsonEscape($c), JsonEscape($attr{$d}{$c}));
|
||||
$cc++;
|
||||
#$str .= ",\n" if($cc != $ac);
|
||||
$str .= ", " if($cc != $ac);
|
||||
#$str .= "\n" if($cc == $ac);
|
||||
}
|
||||
$str .= sprintf(" }, ") if($p->{READINGS});
|
||||
$str .= sprintf(" } ") if(!$p->{READINGS});
|
||||
|
||||
my $r = $p->{READINGS};
|
||||
if($r) {
|
||||
$str .= sprintf("\"STATE\": { ");
|
||||
@ac = keys(%{$r});
|
||||
$ac = @ac;
|
||||
$cc = 0;
|
||||
foreach my $c (sort keys %{$r}) {
|
||||
$str .=
|
||||
sprintf("\"%s\": \"%s\", \"measured\": \"%s\"",
|
||||
JsonEscape($c), JsonEscape($r->{$c}{VAL}), $r->{$c}{TIME});
|
||||
$cc++;
|
||||
#$str .= ",\n" if($cc != $ac);
|
||||
$str .= ", " if($cc != $ac);
|
||||
#$str .= "\n" if($cc == $ac);
|
||||
}
|
||||
$str .= sprintf(" } ");
|
||||
}
|
||||
$tc++;
|
||||
$tr = $tc if($q eq "");
|
||||
$tr++ if($q ne "" && $p->{TYPE} eq $t);
|
||||
$str .= sprintf("} ") if(($tc == $dc) || (!$lt));
|
||||
$str .= sprintf("}\n") if(($tc == $dc) || (!$lt));
|
||||
$str .= sprintf("%*s]\n", $lev+6, " ") if(($tc == $dc) || (!$lt));
|
||||
}
|
||||
$str .= sprintf("%*s}\n", $lev+4, " ") if($lt);
|
||||
$str .= sprintf("%*s],\n", $lev+2, " ");
|
||||
$str .= sprintf("%*s\"totalResultsAvailable\": %s,\n", $lev+2, " ",$tc);
|
||||
$str .= sprintf("%*s\"totalResultsReturned\": %s\n", $lev+2, " ",$tr);
|
||||
$str .= sprintf("%*s}\n", $lev, " ");
|
||||
$str .= "}";
|
||||
} else {
|
||||
if($param eq "ROOMS") {
|
||||
my @rooms;
|
||||
foreach my $d (sort keys %defs) {
|
||||
if($attr{$d}{"room"}) {
|
||||
push(@rooms, $attr{$d}{"room"}) unless(grep($_ eq $attr{$d}{"room"}, @rooms));
|
||||
next;
|
||||
}
|
||||
}
|
||||
@rooms = sort(@rooms);
|
||||
|
||||
# Result counter
|
||||
my $c = 0;
|
||||
|
||||
# Open JSON object
|
||||
$str .= "{\n";
|
||||
$str .= sprintf("%*s\"%s\": {\n", $lev, " ", "ResultSet");
|
||||
# Open JSON array
|
||||
$str .= sprintf("%*s\"%s\": [", $lev+2, " ", "Results");
|
||||
|
||||
for (my $i=0; $i<@rooms; $i++) {
|
||||
$str .= " }," if($i <= $#rooms && $i > 0);
|
||||
$str .= sprintf("\n%*s{ \"NAME\": \"%s\"", $lev+4, " ", $rooms[$i]);
|
||||
$c++;
|
||||
}
|
||||
|
||||
$str .= " }\n";
|
||||
# Close JSON array
|
||||
$str .= sprintf("%*s],\n", $lev+2, " ");
|
||||
# Result summary
|
||||
$str .= sprintf("%*s\"%s\": %s,\n", $lev+2, " ", "totalResultsAvailable", $c);
|
||||
$str .= sprintf("%*s\"%s\": %s\n", $lev+2, " ", "totalResultsReturned", $c);
|
||||
# Close JSON object
|
||||
$str .= sprintf("%*s}\n", $lev, " ");
|
||||
$str .= "}";
|
||||
|
||||
} else {
|
||||
# Search for given device-type
|
||||
my $listDev = "";
|
||||
foreach my $d (sort { my $x = $defs{$a}{TYPE} cmp
|
||||
$defs{$b}{TYPE};
|
||||
$x = ($a cmp $b) if($x == 0); $x; } keys %defs) {
|
||||
if($param eq $defs{$d}{TYPE}) {
|
||||
$listDev = $defs{$d}{TYPE};
|
||||
next;
|
||||
}
|
||||
}
|
||||
|
||||
# List devices by type
|
||||
if($listDev ne "") {
|
||||
my $lt = "";
|
||||
my $ld = "";
|
||||
# Result counter
|
||||
my $c = 0;
|
||||
|
||||
# Open JSON object
|
||||
$str .= "{\n";
|
||||
$str .= sprintf("%*s\"%s\": {\n", $lev, " ", "ResultSet");
|
||||
# Open JSON array
|
||||
$str .= sprintf("%*s\"%s\": [", $lev+2, " ", "Results");
|
||||
|
||||
# Sort first by type then by name
|
||||
for my $d (sort { my $x = $modules{$defs{$a}{TYPE}}{ORDER} cmp
|
||||
$modules{$defs{$b}{TYPE}}{ORDER};
|
||||
$x = ($a cmp $b) if($x == 0); $x; } keys %defs) {
|
||||
if($defs{$d}{TYPE} eq $param) {
|
||||
my $t = $defs{$d}{TYPE};
|
||||
$str .= " }," if($d ne $ld && $lt ne "");
|
||||
$str .= sprintf("\n%*s{ \"NAME\": \"%s\", \"STATE\": \"%s\"",
|
||||
$lev+4, " ", $d, $defs{$d}{STATE});
|
||||
$lt = $t;
|
||||
$ld = $d;
|
||||
$c++;
|
||||
}
|
||||
}
|
||||
|
||||
$str .= " }\n";
|
||||
# Close JSON array
|
||||
$str .= sprintf("%*s],\n", $lev+2, " ");
|
||||
# Result summary
|
||||
$str .= sprintf("%*s\"%s\": %s,\n", $lev+2, " ", "totalResultsAvailable", $c);
|
||||
$str .= sprintf("%*s\"%s\": %s\n", $lev+2, " ", "totalResultsReturned", $c);
|
||||
# Close JSON object
|
||||
$str .= sprintf("%*s}\n", $lev, " ");
|
||||
$str .= "}";
|
||||
|
||||
} else {
|
||||
# List device
|
||||
foreach my $sdev (devspec2array($param)) {
|
||||
if(!defined($defs{$sdev})) {
|
||||
$str .= "No device named or type $param found, try <list> for all devices";
|
||||
next;
|
||||
}
|
||||
$defs{$sdev}{"ATTRIBUTES"} = $attr{$sdev};
|
||||
# Open JSON object
|
||||
$str = "{\n";
|
||||
$str .= sprintf("%*s\"%s\": {\n", $lev, " ", "ResultSet");
|
||||
# Open JSON array
|
||||
$str .= sprintf("%*s\"%s\": {\n", $lev+2, " ", "Results");
|
||||
$str .= PrintHashJson($defs{$sdev}, $lev+4);
|
||||
# Close JSON array
|
||||
$str .= sprintf("%*s}\n", $lev+2, " ");
|
||||
# Close JSON object
|
||||
$str .= sprintf("%*s}\n", $lev, " ");
|
||||
$str .= "}";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $str;
|
||||
}
|
||||
|
||||
1;
|
|
@ -1,109 +0,0 @@
|
|||
JsonList.pm - Copyright (c)2008 Martin Fischer <m_fischer@gmx.de>
|
||||
|
||||
Description:
|
||||
The module JsonList.pm extends FHEM to support a JSON output
|
||||
similar to FHEM's xmllist.
|
||||
|
||||
Installation:
|
||||
Copy the script 99_JsonList.pm to FHEM modules directory, e.g.
|
||||
'cp 99_JsonList.pm /usr/local/lib/FHEM'
|
||||
and restart FHEM.
|
||||
|
||||
Keep your commandref.html up-to-date:
|
||||
copy the patchfile commandref.html.patch to your directory with
|
||||
the original commandref.html and type
|
||||
'patch < commandref.html.patch'
|
||||
You can remove the patchfile after this step.
|
||||
|
||||
Usage:
|
||||
jsonlist [<devspec>|<typespec>|ROOMS]
|
||||
|
||||
Returns an JSON tree of all definitions, all notify settings and
|
||||
all at entries if no parameter is given.
|
||||
|
||||
If specifying <devspec>, then a detailed status for <devspec> will
|
||||
be displayed.
|
||||
|
||||
If specifying <typespec>, then a list with the status for the
|
||||
defined <typespec> devices will be displayed, e.g.:
|
||||
|
||||
If specifying ROOMS, then a list with the defined rooms will be
|
||||
displayed, e.g.:
|
||||
|
||||
Example:
|
||||
FHZ> jsonlist
|
||||
{
|
||||
"ResultSet": {
|
||||
"Results": [
|
||||
{ "_internal__LIST": [
|
||||
{ "_internal_": {
|
||||
"name": "global",
|
||||
"state": "",
|
||||
"sets": [ { "VAL": "null" } ],
|
||||
"attrs": [ { "VAL": "room" },{ "VAL": "comment" },
|
||||
[...]
|
||||
],
|
||||
"totalResultsAvailable": 86,
|
||||
"totalResultsReturned": 86
|
||||
}
|
||||
}
|
||||
|
||||
Example for <devspec>:
|
||||
FHZ> jsonlist lamp1
|
||||
{
|
||||
"ResultSet": {
|
||||
"Results": {
|
||||
"ATTRIBUTES": {
|
||||
"comment": "Light",
|
||||
"room": "Livingroom"
|
||||
},
|
||||
"BTN": "f0",
|
||||
[...]
|
||||
"NAME": "lamp1",
|
||||
"NR": "26",
|
||||
"READINGS": {
|
||||
"state": {
|
||||
"TIME": "2008-12-11 18:11:21",
|
||||
"VAL": "toggle"
|
||||
}
|
||||
},
|
||||
"STATE": "on",
|
||||
"TYPE": "FS20",
|
||||
"XMIT": "0b0b"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Example for <typespec>:
|
||||
FHZ> jsonlist HMS
|
||||
{
|
||||
"ResultSet": {
|
||||
"Results": [
|
||||
{ "NAME": "smokeDetector01", "STATE": "smoke_detect: off" },
|
||||
{ "NAME": "smokeDetector02", "STATE": "smoke_detect: off" },
|
||||
{ "NAME": "smokeDetector03", "STATE": "smoke_detect: off" }
|
||||
],
|
||||
"totalResultsAvailable": 3,
|
||||
"totalResultsReturned": 3
|
||||
}
|
||||
}
|
||||
|
||||
Example for ROOMS:
|
||||
FHZ> jsonlist ROOMS
|
||||
{
|
||||
"ResultSet": {
|
||||
"Results": [
|
||||
{ "NAME": "Bathroom" },
|
||||
{ "NAME": "Office" },
|
||||
{ "NAME": "Diningroom" },
|
||||
{ "NAME": "Garden" },
|
||||
{ "NAME": "House" },
|
||||
{ "NAME": "Bedroom" },
|
||||
{ "NAME": "Garage" },
|
||||
{ "NAME": "Livingroom" },
|
||||
{ "NAME": "hidden" }
|
||||
],
|
||||
"totalResultsAvailable": 9,
|
||||
"totalResultsReturned": 9
|
||||
}
|
||||
}
|
|
@ -1,108 +0,0 @@
|
|||
--- commandref.html-orig 2008-12-11 19:37:59.000000000 +0100
|
||||
+++ commandref.html 2008-12-11 19:15:02.000000000 +0100
|
||||
@@ -27,6 +27,7 @@
|
||||
<a href="#help">?,help</a>
|
||||
<a href="#include">include</a>
|
||||
<a href="#inform">inform</a>
|
||||
+ <a href="#jsonlist">jsonlist</a>
|
||||
<a href="#list">list</a>
|
||||
<a href="#modify">modify</a>
|
||||
<a href="#quit">quit</a>
|
||||
@@ -434,6 +435,97 @@
|
||||
<br>
|
||||
</ul>
|
||||
|
||||
+<a name="jsonlist"></a>
|
||||
+<h3>jsonlist</h3>
|
||||
+<ul>
|
||||
+ <code>jsonlist [<devspec>|<typespec>|ROOMS]</code>
|
||||
+ <br><br>
|
||||
+ Returns an JSON tree of all definitions, all notify settings and all at
|
||||
+ entries if no parameter is given.
|
||||
+ <br><br>
|
||||
+ Example:
|
||||
+ <pre><code> FHZ> jsonlist
|
||||
+ {
|
||||
+ "ResultSet": {
|
||||
+ "Results": [
|
||||
+ { "_internal__LIST": [
|
||||
+ { "_internal_": {
|
||||
+ "name": "global",
|
||||
+ "state": "<no definition>",
|
||||
+ "sets": [ { "VAL": "null" } ],
|
||||
+ "attrs": [ { "VAL": "room" },{ "VAL": "comment" },
|
||||
+ [...]
|
||||
+ ],
|
||||
+ "totalResultsAvailable": 86,
|
||||
+ "totalResultsReturned": 86
|
||||
+ }
|
||||
+ }
|
||||
+ </code></pre>
|
||||
+ If specifying <code><devspec></code>, then a detailed status for
|
||||
+ <code><devspec></code> will be displayed, e.g.:
|
||||
+ <pre><code> FHZ> jsonlist lamp1
|
||||
+ {
|
||||
+ "ResultSet": {
|
||||
+ "Results": {
|
||||
+ "ATTRIBUTES": {
|
||||
+ "comment": "Light",
|
||||
+ "room": "Livingroom"
|
||||
+ },
|
||||
+ "BTN": "f0",
|
||||
+ [...]
|
||||
+ "NAME": "lamp1",
|
||||
+ "NR": "26",
|
||||
+ "READINGS": {
|
||||
+ "state": {
|
||||
+ "TIME": "2008-12-11 18:11:21",
|
||||
+ "VAL": "toggle"
|
||||
+ }
|
||||
+ },
|
||||
+ "STATE": "on",
|
||||
+ "TYPE": "FS20",
|
||||
+ "XMIT": "0b0b"
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ </code></pre>
|
||||
+ If specifying <code><typespec></code>, then a list with the status for
|
||||
+ the defined <code><typespec></code> devices will be displayed, e.g.:
|
||||
+ <pre><code> FHZ> jsonlist HMS
|
||||
+ {
|
||||
+ "ResultSet": {
|
||||
+ "Results": [
|
||||
+ { "NAME": "smokeDetector01", "STATE": "smoke_detect: off" },
|
||||
+ { "NAME": "smokeDetector02", "STATE": "smoke_detect: off" },
|
||||
+ { "NAME": "smokeDetector03", "STATE": "smoke_detect: off" }
|
||||
+ ],
|
||||
+ "totalResultsAvailable": 3,
|
||||
+ "totalResultsReturned": 3
|
||||
+ }
|
||||
+ }
|
||||
+ </code></pre>
|
||||
+ If specifying <code>ROOMS</code>, then a list with the defined rooms
|
||||
+ will be displayed, e.g.:
|
||||
+ <pre><code> FHZ> jsonlist ROOMS
|
||||
+ {
|
||||
+ "ResultSet": {
|
||||
+ "Results": [
|
||||
+ { "NAME": "Bathroom" },
|
||||
+ { "NAME": "Office" },
|
||||
+ { "NAME": "Diningroom" },
|
||||
+ { "NAME": "Garden" },
|
||||
+ { "NAME": "House" },
|
||||
+ { "NAME": "Bedroom" },
|
||||
+ { "NAME": "Garage" },
|
||||
+ { "NAME": "Livingroom" },
|
||||
+ { "NAME": "hidden" }
|
||||
+ ],
|
||||
+ "totalResultsAvailable": 9,
|
||||
+ "totalResultsReturned": 9
|
||||
+ }
|
||||
+ }
|
||||
+ </code></pre>
|
||||
+</ul>
|
||||
+
|
||||
<a name="list"></a>
|
||||
<h3>list</h3>
|
||||
<ul>
|
|
@ -1,53 +0,0 @@
|
|||
- 70_SCIVT.pm
|
||||
Support for an SCD series solar controler device. Details see
|
||||
http://english.ivt-hirschau.de/content.php?parent_id=CAT_64&doc_id=DOC_118
|
||||
- 86_FS10.pm
|
||||
Support for FS10. Read README.FS10, you have to install pcwsd first.
|
||||
- 91_DbLog.pm
|
||||
Example to log data in a (DBI supported) database (MySQL, Oracle, etc)
|
||||
see dblog for a full-featured database log
|
||||
- 95_PachLog.pm
|
||||
Pachube module from Axel
|
||||
- 99_dumpdef.pm
|
||||
Debugging helpers from Axel
|
||||
- 99_ALARM.pm
|
||||
Example for a Low Budget ALARM System by Martin
|
||||
- 99_SUNRISE.pm
|
||||
The "original" (i.e. old) Sunrise/Sunset support. Needs the hard-to-install
|
||||
DateTime::Event::Sunrise perl module. Use the 99_SUNRISE_EL.pm module instead.
|
||||
|
||||
- checkmsg.pl
|
||||
Check header/function/crc of an FS20 hex message
|
||||
- crc.pl
|
||||
Computing CRC16 in perl
|
||||
- dblog/*
|
||||
Support for a full-featured database log by Boris. See the README.
|
||||
- em1010.pl / em1010.gnuplot
|
||||
Standalone EM1010PC reader program and a gnuplot file to plot em1010 data
|
||||
- four2hex
|
||||
Convert housecode from ELV notation (4) to fhem.pl notation (hex)
|
||||
Not needed any more, as current fhem versions understand both.
|
||||
- fs20_holidays.sh
|
||||
STefan's "presence simulator" for holidays
|
||||
- garden.pl
|
||||
Garden irrigation regulator with weather dependency (KS300 temp + rain)
|
||||
- JsonList
|
||||
99_JsonList.pm adds a jsonlist command, which is list in JSON format.
|
||||
See JsonList/README.JsonList for more. By Martin.
|
||||
- fhem-speech
|
||||
Martins instructions on how to make FHEM talk using the MBROLA speech
|
||||
synthesizer
|
||||
- init-scripts
|
||||
RC scripts to be put into /etc/init.d and then symlinked to /etc/rc3.d or
|
||||
similar.
|
||||
- ks300avg.pl
|
||||
Computing daily/monthly avarage values from a KS300 log
|
||||
- rolwzo_not_off.sh
|
||||
Martin's "don't lock me out" program: look at the comment
|
||||
- rotateShiftWork
|
||||
Shellskript for changing am/pm temperatures when working on a shift
|
||||
- rrd
|
||||
Peter's RRD support. See the HOWTO
|
||||
- serial.pm
|
||||
Serial line analyzer
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
86_FS10.pm is for reading ELV (www.elv.de) weather Sensors, using a Hardware
|
||||
(Part No. 68-390-61) and communicates with pcwsd from Sven Geggus.
|
||||
|
||||
Currently temperature, windspeed, rain and brightness sensors are supported.
|
||||
|
||||
For use with FHEM you have to compile pcwsd like usual, it can be found here
|
||||
http://geggus.net/sven/pcwsd/
|
||||
|
||||
Start pcwsd deamon with
|
||||
pcwsd -d /dev/ttyUSB4 -ld /var/log/fs10- -tlf %Y-%m-%d_%T
|
||||
|
||||
A few minutes later you should see files with temperature values.
|
||||
|
||||
For use with FHEM define
|
||||
|
||||
define fs10 FS10 127.0.0.1 4711 which means pcwsd run on localhost, port 4711
|
||||
|
||||
If you only interested in viewing temperature values with a FHEM frontend like
|
||||
pgm3, 86_FS10.pm can be ommited.
|
||||
|
||||
To display a user defined FS10 temperature graphic in pgm3 define
|
||||
|
||||
########################
|
||||
#
|
||||
$sortnumber=7;
|
||||
$userdef[$sortnumber]['name']='IndoorTemp';
|
||||
$userdef[$sortnumber]['valuefield']=2;
|
||||
$userdef[$sortnumber]['gnuplottype']='temperature';
|
||||
$userdef[$sortnumber]['logpath']='/var/log/fs10/idtemp_7.gnu';
|
||||
$userdef[$sortnumber]['room']='indoor';
|
||||
$userdef[$sortnumber]['semlong']='Temp indor';
|
||||
$userdef[$sortnumber]['semshort']='°';
|
||||
$userdef[$sortnumber]['imagemax']=725;
|
||||
$userdef[$sortnumber]['imagemay']=52;
|
||||
$userdef[$sortnumber]['maxcount']=575;
|
||||
$userdef[$sortnumber]['XcorrectMainText']=25;
|
||||
$userdef[$sortnumber]['logrotatelines']=2050;
|
|
@ -1,22 +0,0 @@
|
|||
# Siehe auch
|
||||
# http://de.wikipedia.org/wiki/Feiertage_in_Österreich
|
||||
|
||||
1 01-01 Neujahr
|
||||
1 01-06 Heilige Drei Koenige
|
||||
1 05-01 Tag der Arbeit
|
||||
1 08-15 Mariae Himmelfahrt
|
||||
1 10-26 Nationalfeiertag
|
||||
1 11-01 Allerheiligen
|
||||
1 12-08 Mariä Empfängnis
|
||||
1 12-24 Weihnachten
|
||||
1 12-25 1. Weihnachtstag
|
||||
1 12-26 2. Weihnachtstag
|
||||
1 12-31 Silvester
|
||||
|
||||
2 1 Ostermontag
|
||||
2 39 Christi Himmelfahrt
|
||||
2 50 Pfingsten
|
||||
2 60 Fronleichnam
|
||||
|
||||
4 07-01 08-31 Sommerferien
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
# Siehe auch
|
||||
# http://de.wikipedia.org/wiki/Feiertage_in_Deutschland
|
||||
|
||||
1 01-01 Neujahr
|
||||
1 01-06 Heilige Drei Koenige
|
||||
1 05-01 Tag der Arbeit
|
||||
1 08-15 Mariae Himmelfahrt (nur bei ueberwiegend katholischer Bevoelkerung)
|
||||
1 10-03 Tag der deutschen Einheit
|
||||
1 11-01 Allerheiligen
|
||||
1 12-25 1. Weihnachtstag
|
||||
1 12-26 2. Weihnachtstag
|
||||
|
||||
2 -2 Karfreitag
|
||||
2 1 Ostermontag
|
||||
2 39 Christi Himmelfahrt
|
||||
2 50 Pfingsten
|
||||
2 60 Fronleichnam
|
||||
#5 -1 Wed 11 23 Buss und Bettag (first Wednesday before Nov, 23rd)<br>
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
#!/usr/bin/perl
|
||||
die("Usage: checkmsg HEX-FHZ-MESSAGE\n") if(int(@ARGV) != 1);
|
||||
my $msg = $ARGV[0];
|
||||
|
||||
die("Bad prefix (not 0x81)\n") if($msg !~ m/^81/);
|
||||
print("Prefix is ok (0x81)\n");
|
||||
|
||||
my $l = hex(substr($msg, 2, 2));
|
||||
my $rl = length($msg)/2-2;
|
||||
die("Bad length $rl (should be $l)\n") if($rl != $l);
|
||||
print("Length is ok ($l)\n");
|
||||
|
||||
my @data;
|
||||
for(my $i = 8; $i < length($msg); $i += 2) {
|
||||
push(@data, ord(pack('H*', substr($msg, $i, 2))));
|
||||
}
|
||||
|
||||
my $rcrc = 0;
|
||||
map { $rcrc += $_; } @data;
|
||||
$rcrc &= 0xFF;
|
||||
|
||||
my $crc = hex(substr($msg, 6, 2));
|
||||
my $str = sprintf("Bad CRC 0x%02x (should be 0x%02x)\n", $crc, $rcrc);
|
||||
die($str) if($crc ne $rcrc);
|
||||
printf("CRC is ok (0x%02x)\n", $crc);
|
||||
|
||||
exit(0);
|
|
@ -1,33 +0,0 @@
|
|||
die("Usage: crc HEX-MESSAGE\n") if(int(@ARGV) != 2);
|
||||
my $msg = $ARGV[0];
|
||||
$msg =~ s/ //g;
|
||||
|
||||
my $des = $ARGV[1];
|
||||
$des =~ s/ //g;
|
||||
|
||||
# FFFF: 77 72 statt 2c 7f
|
||||
# FFFF: 5C AC statt DC D9
|
||||
|
||||
|
||||
#for(my $ic = 0; $ic < 65536; $ic++) {
|
||||
for(my $ic = 0; $ic < 2; $ic++) {
|
||||
my $crc = ($ic == 0?0:0xffffffff);
|
||||
for(my $i = 0; $i < length($msg); $i += 2) {
|
||||
my $n = ord(pack('H*', substr($msg, $i, 2)));
|
||||
|
||||
my $od = $n;
|
||||
for my $b (0..7) {
|
||||
my $crcbit = ($crc & 0x80000000) ? 1 : 0;
|
||||
my $databit = ($n & 0x80) ? 1 : 0;
|
||||
$crc <<= 1;
|
||||
$n <<= 1;
|
||||
$crc ^= 0x04C11DB7 if($crcbit != $databit);
|
||||
# printf("%3d.%d %02x CRC %x ($crcbit $databit)\n", $i/2, $b, $n, $crc);
|
||||
}
|
||||
# printf("%3d %02x CRC %02x %02x\n", $i/2, $od, ($crc&0xff00)>>8, $crc&0xff);
|
||||
}
|
||||
# print "$ic\n" if($ic % 10000 == 0);
|
||||
printf("%02x %02x\n",($crc&0xff00)>>8,$crc&0xff);
|
||||
print "got $ic\n"
|
||||
if(sprintf("%02x%02x",($crc&0xff00)>>8,$crc&0xff) eq $des);
|
||||
}
|
|
@ -1,325 +0,0 @@
|
|||
##############################################
|
||||
#
|
||||
# 93_DbLog.pm
|
||||
# written by Dr. Boris Neubert 2007-12-30
|
||||
# e-mail: omega at online dot de
|
||||
#
|
||||
##############################################
|
||||
|
||||
package main;
|
||||
use strict;
|
||||
use warnings;
|
||||
use DBI;
|
||||
|
||||
sub DbLog($$$);
|
||||
|
||||
|
||||
################################################################
|
||||
sub
|
||||
DbLog_Initialize($)
|
||||
{
|
||||
my ($hash) = @_;
|
||||
|
||||
$hash->{DefFn} = "DbLog_Define";
|
||||
$hash->{UndefFn} = "DbLog_Undef";
|
||||
$hash->{NotifyFn} = "DbLog_Log";
|
||||
$hash->{AttrFn} = "DbLog_Attr";
|
||||
$hash->{AttrList} = "disable:0,1";
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub
|
||||
DbLog_Define($@)
|
||||
{
|
||||
my ($hash, $def) = @_;
|
||||
my @a = split("[ \t][ \t]*", $def);
|
||||
|
||||
return "wrong syntax: define <name> DbLog configuration regexp"
|
||||
if(int(@a) != 4);
|
||||
|
||||
my $regexp = $a[3];
|
||||
|
||||
eval { "Hallo" =~ m/^$regexp$/ };
|
||||
return "Bad regexp: $@" if($@);
|
||||
$hash->{REGEXP} = $regexp;
|
||||
|
||||
$hash->{configuration}= $a[2];
|
||||
|
||||
return "Can't connect to database." if(!DbLog_Connect($hash));
|
||||
|
||||
$hash->{STATE} = "active";
|
||||
|
||||
return undef;
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub
|
||||
DbLog_Undef($$)
|
||||
{
|
||||
my ($hash, $name) = @_;
|
||||
my $dbh= $hash->{DBH};
|
||||
$dbh->disconnect() if(defined($dbh));
|
||||
return undef;
|
||||
}
|
||||
|
||||
################################################################
|
||||
sub
|
||||
DbLog_Attr(@)
|
||||
{
|
||||
my @a = @_;
|
||||
my $do = 0;
|
||||
|
||||
if($a[0] eq "set" && $a[2] eq "disable") {
|
||||
$do = (!defined($a[3]) || $a[3]) ? 1 : 2;
|
||||
}
|
||||
$do = 2 if($a[0] eq "del" && (!$a[2] || $a[2] eq "disable"));
|
||||
return if(!$do);
|
||||
|
||||
$defs{$a[1]}{STATE} = ($do == 1 ? "disabled" : "active");
|
||||
|
||||
return undef;
|
||||
}
|
||||
|
||||
################################################################
|
||||
sub
|
||||
DbLog_ParseEvent($$)
|
||||
{
|
||||
my ($type, $event)= @_;
|
||||
my @result;
|
||||
|
||||
# split the event into reading and argument
|
||||
# "day-temp: 22.0 (Celsius)" -> "day-temp", "22.0 (Celsius)"
|
||||
my @parts= split(/: /,$event);
|
||||
my $reading= $parts[0]; if(!defined($reading)) { $reading= ""; }
|
||||
my $arg= $parts[1];
|
||||
|
||||
# the interpretation of the argument depends on the device type
|
||||
|
||||
#default
|
||||
my $value= $arg; if(!defined($value)) { $value= ""; }
|
||||
my $unit= "";
|
||||
|
||||
|
||||
# EMEM, M232Counter, M232Voltage return plain numbers
|
||||
if(($type eq "M232Voltage") ||
|
||||
($type eq "M232Counter") ||
|
||||
($type eq "EMEM")) {
|
||||
}
|
||||
# FS20
|
||||
elsif(($type eq "FS20") ||
|
||||
($type eq "X10")) {
|
||||
@parts= split(/ /,$value);
|
||||
my $reading= $parts[0]; if(!defined($reading)) { $reading= ""; }
|
||||
if($#parts>=1) {
|
||||
$value= join(" ", shift @parts);
|
||||
if($reading =~ m(^dim*%$)) {
|
||||
$value= substr($reading,3,length($reading)-4);
|
||||
$reading= "dim";
|
||||
$unit= "%";
|
||||
}
|
||||
else {
|
||||
$value= "";
|
||||
}
|
||||
}
|
||||
}
|
||||
# FHT
|
||||
elsif($type eq "FHT") {
|
||||
if($reading =~ m(-from[12]\ ) || $reading =~ m(-to[12]\ )) {
|
||||
@parts= split(/ /,$event);
|
||||
$reading= $parts[0];
|
||||
$value= $parts[1];
|
||||
$unit= "";
|
||||
}
|
||||
if($reading =~ m(-temp)) { $value=~ s/ \(Celsius\)//; $unit= "°C"; }
|
||||
if($reading =~ m(temp-offset)) { $value=~ s/ \(Celsius\)//; $unit= "°C"; }
|
||||
if($reading =~ m(^actuator[0-9]*)) {
|
||||
if($value eq "lime-protection") {
|
||||
$reading= "actuator-lime-protection";
|
||||
undef $value;
|
||||
}
|
||||
elsif($value =~ m(^offset:)) {
|
||||
$reading= "actuator-offset";
|
||||
@parts= split(/: /,$value);
|
||||
$value= $parts[1];
|
||||
if(defined $value) {
|
||||
$value=~ s/%//; $value= $value*1.; $unit= "%";
|
||||
}
|
||||
}
|
||||
elsif($value =~ m(^unknown_)) {
|
||||
@parts= split(/: /,$value);
|
||||
$reading= "actuator-" . $parts[0];
|
||||
$value= $parts[1];
|
||||
if(defined $value) {
|
||||
$value=~ s/%//; $value= $value*1.; $unit= "%";
|
||||
}
|
||||
}
|
||||
elsif($value eq "synctime") {
|
||||
$reading= "actuator-synctime";
|
||||
undef $value;
|
||||
}
|
||||
elsif($value eq "test") {
|
||||
$reading= "actuator-test";
|
||||
undef $value;
|
||||
}
|
||||
elsif($value eq "pair") {
|
||||
$reading= "actuator-pair";
|
||||
undef $value;
|
||||
}
|
||||
else {
|
||||
$value=~ s/%//; $value= $value*1.; $unit= "%";
|
||||
}
|
||||
}
|
||||
}
|
||||
# KS300
|
||||
elsif($type eq "KS300") {
|
||||
if($event =~ m(T:.*)) { $reading= "data"; $value= $event; }
|
||||
if($event =~ m(avg_day)) { $reading= "data"; $value= $event; }
|
||||
if($event =~ m(avg_month)) { $reading= "data"; $value= $event; }
|
||||
if($reading eq "temperature") { $value=~ s/ \(Celsius\)//; $unit= "°C"; }
|
||||
if($reading eq "wind") { $value=~ s/ \(km\/h\)//; $unit= "km/h"; }
|
||||
if($reading eq "rain") { $value=~ s/ \(l\/m2\)//; $unit= "l/m2"; }
|
||||
if($reading eq "rain_raw") { $value=~ s/ \(counter\)//; $unit= ""; }
|
||||
if($reading eq "humidity") { $value=~ s/ \(\%\)//; $unit= "%"; }
|
||||
if($reading eq "israining") {
|
||||
$value=~ s/ \(yes\/no\)//;
|
||||
$value=~ s/no/0/;
|
||||
$value=~ s/yes/1/;
|
||||
}
|
||||
}
|
||||
# HMS
|
||||
elsif($type eq "HMS") {
|
||||
if($event =~ m(T:.*)) { $reading= "data"; $value= $event; }
|
||||
if($reading eq "temperature") { $value=~ s/ \(Celsius\)//; $unit= "°C"; }
|
||||
if($reading eq "humidity") { $value=~ s/ \(\%\)//; $unit= "%"; }
|
||||
if($reading eq "battery") {
|
||||
$value=~ s/ok/1/;
|
||||
$value=~ s/replaced/1/;
|
||||
$value=~ s/empty/0/;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@result= ($reading,$value,$unit);
|
||||
return @result;
|
||||
}
|
||||
|
||||
|
||||
################################################################
|
||||
sub
|
||||
DbLog_Log($$)
|
||||
{
|
||||
# Log is my entry, Dev is the entry of the changed device
|
||||
my ($log, $dev) = @_;
|
||||
|
||||
# name and type required for parsing
|
||||
my $n= $dev->{NAME};
|
||||
my $t= $dev->{TYPE};
|
||||
|
||||
# timestamp in SQL format YYYY-MM-DD hh:mm:ss
|
||||
#my ($sec,$min,$hr,$day,$mon,$yr,$wday,$yday,$isdst)= localtime(time);
|
||||
#my $ts= sprintf("%04d-%02d-%02d %02d:%02d:%02d", $yr+1900,$mon+1,$day,$hr,$min,$sec);
|
||||
|
||||
my $re = $log->{REGEXP};
|
||||
my $max = int(@{$dev->{CHANGED}});
|
||||
for (my $i = 0; $i < $max; $i++) {
|
||||
my $s = $dev->{CHANGED}[$i];
|
||||
$s = "" if(!defined($s));
|
||||
if($n =~ m/^$re$/ || "$n:$s" =~ m/^$re$/) {
|
||||
my $ts = TimeNow();
|
||||
$ts = $dev->{CHANGETIME}[$i] if(defined($dev->{CHANGETIME}[$i]));
|
||||
# $ts is in SQL format YYYY-MM-DD hh:mm:ss
|
||||
|
||||
my @r= DbLog_ParseEvent($t, $s);
|
||||
my $reading= $r[0];
|
||||
my $value= $r[1];
|
||||
my $unit= $r[2];
|
||||
if(!defined $reading) { $reading= ""; }
|
||||
if(!defined $value) { $value= ""; }
|
||||
if(!defined $unit) { $unit= ""; }
|
||||
|
||||
|
||||
my $is= "(TIMESTAMP, DEVICE, TYPE, EVENT, READING, VALUE, UNIT) VALUES " .
|
||||
"('$ts', '$n', '$t', '$s', '$reading', '$value', '$unit')";
|
||||
DbLog_ExecSQL($log, "INSERT INTO history" . $is);
|
||||
DbLog_ExecSQL($log, "DELETE FROM current WHERE (DEVICE='$n') AND (READING='$reading')");
|
||||
DbLog_ExecSQL($log, "INSERT INTO current" . $is);
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
|
||||
################################################################
|
||||
sub
|
||||
DbLog_Connect($)
|
||||
{
|
||||
my ($hash)= @_;
|
||||
|
||||
my $configfilename= $hash->{configuration};
|
||||
if(!open(CONFIG, $configfilename)) {
|
||||
Log 1, "Cannot open database configuration file $configfilename.";
|
||||
return 0; }
|
||||
my @config=<CONFIG>;
|
||||
close(CONFIG);
|
||||
|
||||
my %dbconfig;
|
||||
eval join("", @config);
|
||||
|
||||
my $dbconn= $dbconfig{connection};
|
||||
my $dbuser= $dbconfig{user};
|
||||
my $dbpassword= $dbconfig{password};
|
||||
|
||||
Log 3, "Connecting to database $dbconn with user $dbuser";
|
||||
my $dbh = DBI->connect_cached("dbi:$dbconn", $dbuser, $dbpassword);
|
||||
if(!$dbh) {
|
||||
Log 1, "Can't connect to $dbconn: $DBI::errstr";
|
||||
return 0;
|
||||
}
|
||||
Log 3, "Connection to db $dbconn established";
|
||||
$hash->{DBH}= $dbh;
|
||||
return 1;
|
||||
}
|
||||
|
||||
################################################################
|
||||
sub
|
||||
DbLog_ExecSQL1($$)
|
||||
{
|
||||
my ($dbh,$sql)= @_;
|
||||
|
||||
my $sth = $dbh->do($sql);
|
||||
if(!$sth) {
|
||||
Log 2, "DBLog error: " . $DBI::errstr;
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub
|
||||
DbLog_ExecSQL($$)
|
||||
{
|
||||
my ($hash,$sql)= @_;
|
||||
|
||||
Log 5, "Executing $sql";
|
||||
my $dbh= $hash->{DBH};
|
||||
if(!DbLog_ExecSQL1($dbh,$sql)) {
|
||||
#retry
|
||||
$dbh->disconnect();
|
||||
if(!DbLog_Connect($hash)) {
|
||||
Log 2, "DBLog reconnect failed.";
|
||||
return 0;
|
||||
}
|
||||
$dbh= $hash->{DBH};
|
||||
if(!DbLog_ExecSQL1($dbh,$sql)) {
|
||||
Log 2, "DBLog retry failed.";
|
||||
return 0;
|
||||
}
|
||||
Log 2, "DBLog retry ok.";
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
################################################################
|
||||
1;
|
|
@ -1,13 +0,0 @@
|
|||
For usage instruction see commandref.html, section define
|
||||
2007-12-30bn
|
||||
|
||||
- 93_DbLog.pm
|
||||
copy this file into <modpath>/FHEM
|
||||
- db.conf
|
||||
sample database configuration file
|
||||
- fhemdb_create.sql
|
||||
sample sql command to create a mysql database for logging purposes
|
||||
- fhemdb_get.pl
|
||||
sample perl script for retrieving the current (latest) data from
|
||||
the logging database
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
#
|
||||
# database configuration file
|
||||
#
|
||||
#
|
||||
#
|
||||
%dbconfig= (
|
||||
connection => "mysql:database=fhem;host=db;port=3306",
|
||||
user => "fhemuser",
|
||||
password => "fhempassword",
|
||||
);
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
CREATE DATABASE `fhem` DEFAULT CHARACTER SET utf8 COLLATE utf8_bin;
|
||||
CREATE USER 'fhemuser'@'%' IDENTIFIED BY 'fhempassword';
|
||||
CREATE TABLE history (TIMESTAMP TIMESTAMP, DEVICE varchar(32), TYPE varchar(32), EVENT varchar(64), READING varchar(32), VALUE varchar(32), UNIT varchar(32));
|
||||
CREATE TABLE current (TIMESTAMP TIMESTAMP, DEVICE varchar(32), TYPE varchar(32), EVENT varchar(64), READING varchar(32), VALUE varchar(32), UNIT varchar(32));
|
||||
GRANT SELECT, INSERT, DELETE ON `fhem` .* TO 'fhemuser'@'%';
|
||||
|
||||
|
|
@ -1,93 +0,0 @@
|
|||
#!/usr/bin/perl
|
||||
#
|
||||
################################################################
|
||||
#
|
||||
# Copyright notice
|
||||
#
|
||||
# (c) 2007 Copyright: Dr. Boris Neubert (omega at online dot de)
|
||||
# All rights reserved
|
||||
#
|
||||
# This script 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.
|
||||
#
|
||||
# The GNU General Public License can be found at
|
||||
# http://www.gnu.org/copyleft/gpl.html.
|
||||
# A copy is found in the textfile GPL.txt and important notices to the license
|
||||
# from the author is found in LICENSE.txt distributed with these scripts.
|
||||
#
|
||||
# This script 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.
|
||||
#
|
||||
# This copyright notice MUST APPEAR in all copies of the script!
|
||||
#
|
||||
################################################################
|
||||
|
||||
|
||||
#
|
||||
# this script returns the current reading for a device stored in
|
||||
# the fhem logging database
|
||||
#
|
||||
|
||||
# Usage:
|
||||
# fhemdb_get.pl <device> <reading> [<reading> ...]
|
||||
# Example:
|
||||
# fhemdb_get.pl ext.ks300 temperature humidity
|
||||
#
|
||||
#
|
||||
|
||||
#
|
||||
# global configuration
|
||||
#
|
||||
my $dbconn = "mysql:database=fhem;host=db;port=3306";
|
||||
my $dbuser = "fhemuser";
|
||||
my $dbpassword = "fhempassword";
|
||||
|
||||
#
|
||||
# nothing to change below this line
|
||||
#
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use DBI;
|
||||
|
||||
(@ARGV>=2) || die "Usage: fhemdb_get.pl <device> <reading> [<reading> ... ]";
|
||||
|
||||
my $device= $ARGV[0];
|
||||
my @readings=@ARGV; shift @readings;
|
||||
my $set= join(",", map({"\'" . $_ . "\'"} @readings));
|
||||
|
||||
my $dbh= DBI->connect_cached("dbi:$dbconn", $dbuser, $dbpassword) ||
|
||||
die "Cannot connect to $dbconn: $DBI::errstr";
|
||||
my $stm= "SELECT READING, VALUE FROM current WHERE
|
||||
(DEVICE='$device') AND
|
||||
(READING IN ($set))";
|
||||
my $sth= $dbh->prepare($stm) ||
|
||||
die "Cannot prepare statement $stm: $DBI::errstr";
|
||||
my $rc= $sth->execute() ||
|
||||
die "Cannot execute statement $stm: $DBI::errstr";
|
||||
|
||||
my %rs;
|
||||
my $reading;
|
||||
my $value;
|
||||
while( ($reading,$value)= $sth->fetchrow_array) {
|
||||
$rs{$reading}= $value;
|
||||
}
|
||||
foreach $reading (@readings) {
|
||||
$value= $rs{$reading};
|
||||
$value= "NULL" if(!defined($value));
|
||||
print "$reading:$value ";
|
||||
}
|
||||
print "\n";
|
||||
die $sth->errstr if $sth->err;
|
||||
$dbh->disconnect();
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -1,474 +0,0 @@
|
|||
#!/usr/bin/perl
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use Device::SerialPort;
|
||||
|
||||
sub b($$);
|
||||
sub w($$);
|
||||
sub docrc($$);
|
||||
sub checkcrc($$);
|
||||
sub getData($);
|
||||
sub makemsg($);
|
||||
sub maketime($);
|
||||
|
||||
my %cmd = (
|
||||
"getVersion" => 1,
|
||||
"getTime" => 1,
|
||||
"getDevStatus" => 1,
|
||||
"getDevPage" => 1,
|
||||
"getDevData" => 1,
|
||||
"setPrice" => 1,
|
||||
"setAlarm" => 1,
|
||||
"setRperKW" => 1,
|
||||
"get62" => 1,
|
||||
"setTime" => 1,
|
||||
"reset" => 1,
|
||||
);
|
||||
|
||||
|
||||
if(@ARGV < 2) {
|
||||
printf("Usage: perl em1010.pl serial-device command args\n");
|
||||
exit(1);
|
||||
}
|
||||
my $ser = $ARGV[0];
|
||||
|
||||
my $fd;
|
||||
|
||||
#####################
|
||||
# Open serial port
|
||||
my $serport = new Device::SerialPort ($ser);
|
||||
die "Can't open $ser: $!\n" if(!$serport);
|
||||
$serport->reset_error();
|
||||
$serport->baudrate(38400);
|
||||
$serport->databits(8);
|
||||
$serport->parity('none');
|
||||
$serport->stopbits(1);
|
||||
$serport->handshake('none');
|
||||
|
||||
my $cmd = $ARGV[1];
|
||||
if(!defined($cmd{$cmd})) {
|
||||
printf("Unknown command $cmd, use one of " . join(" ",sort keys %cmd) . "\n");
|
||||
exit(0);
|
||||
}
|
||||
|
||||
###########################
|
||||
no strict "refs";
|
||||
&{$cmd }();
|
||||
use strict "refs";
|
||||
exit(0);
|
||||
|
||||
#########################
|
||||
sub
|
||||
maketime($)
|
||||
{
|
||||
my @l = localtime(shift);
|
||||
return sprintf("%04d-%02d-%02d_%02d:%02d:00",
|
||||
1900+$l[5],$l[4]+1,$l[3],$l[2],$l[1]-$l[1]%5);
|
||||
}
|
||||
|
||||
#########################
|
||||
sub
|
||||
b($$)
|
||||
{
|
||||
my ($t,$p) = @_;
|
||||
return ord(substr($t,$p,1));
|
||||
}
|
||||
|
||||
#########################
|
||||
sub
|
||||
w($$)
|
||||
{
|
||||
my ($t,$p) = @_;
|
||||
return b($t,$p+1)*256 + b($t,$p);
|
||||
}
|
||||
|
||||
#########################
|
||||
sub
|
||||
dw($$)
|
||||
{
|
||||
my ($t,$p) = @_;
|
||||
return w($t,$p+2)*65536 + w($t,$p);
|
||||
}
|
||||
|
||||
#########################
|
||||
sub
|
||||
docrc($$)
|
||||
{
|
||||
my ($in, $val) = @_;
|
||||
my ($crc, $bits) = (0, 8);
|
||||
my $k = (($in >> 8) ^ $val) << 8;
|
||||
while($bits--) {
|
||||
if(($crc ^ $k) & 0x8000) {
|
||||
$crc = ($crc << 1) ^ 0x8005;
|
||||
} else {
|
||||
$crc <<= 1;
|
||||
}
|
||||
$k <<= 1;
|
||||
}
|
||||
return (($in << 8) ^ $crc) & 0xffff;
|
||||
}
|
||||
|
||||
#########################
|
||||
sub
|
||||
checkcrc($$)
|
||||
{
|
||||
my ($otxt, $len) = @_;
|
||||
my $crc = 0x8c27;
|
||||
for(my $l = 2; $l < $len+4; $l++) {
|
||||
my $b = ord(substr($otxt,$l,1));
|
||||
$crc = docrc($crc, 0x10) if($b==0x02 || $b==0x03 || $b==0x10);
|
||||
$crc = docrc($crc, $b);
|
||||
}
|
||||
return ($crc == w($otxt, $len+4));
|
||||
}
|
||||
|
||||
#########################
|
||||
sub
|
||||
esc($)
|
||||
{
|
||||
my ($b) = @_;
|
||||
|
||||
my $out = "";
|
||||
$out .= chr(0x10) if($b==0x02 || $b==0x03 || $b==0x10);
|
||||
$out .= chr($b);
|
||||
}
|
||||
|
||||
#########################
|
||||
sub
|
||||
makemsg($)
|
||||
{
|
||||
my ($data) = @_;
|
||||
my $len = length($data);
|
||||
$data = chr($len&0xff) . chr(int($len/256)) . $data;
|
||||
|
||||
my $out = pack('H*', "0200");
|
||||
my $crc = 0x8c27;
|
||||
for(my $l = 0; $l < $len+2; $l++) {
|
||||
my $b = ord(substr($data,$l,1));
|
||||
$crc = docrc($crc, 0x10) if($b==0x02 || $b==0x03 || $b==0x10);
|
||||
$crc = docrc($crc, $b);
|
||||
$out .= esc($b);
|
||||
}
|
||||
$out .= esc($crc&0xff);
|
||||
$out .= esc($crc/256);
|
||||
$out .= chr(0x03);
|
||||
return $out;
|
||||
}
|
||||
|
||||
|
||||
#########################
|
||||
sub
|
||||
getData($)
|
||||
{
|
||||
my ($d) = @_;
|
||||
$d = makemsg(pack('H*', $d));
|
||||
#print "Sending: " . unpack('H*', $d) . "\n";
|
||||
|
||||
for(my $rep = 0; $rep < 3; $rep++) {
|
||||
|
||||
#printf "write (try nr $rep)\n";
|
||||
$serport->write($d);
|
||||
|
||||
my $retval = "";
|
||||
my $esc = 0;
|
||||
my $started = 0;
|
||||
my $complete = 0;
|
||||
for(;;) {
|
||||
my ($rout, $rin) = ('', '');
|
||||
vec($rin, $serport->FILENO, 1) = 1;
|
||||
my $nfound = select($rout=$rin, undef, undef, 1.0);
|
||||
|
||||
die("Select error $nfound / $!\n") if($nfound < 0);
|
||||
last if($nfound == 0);
|
||||
|
||||
my $buf = $serport->input();
|
||||
die "EOF on $ser\n" if(!defined($buf) || length($buf) == 0);
|
||||
|
||||
for(my $i = 0; $i < length($buf); $i++) {
|
||||
my $b = ord(substr($buf,$i,1));
|
||||
|
||||
if(!$started && $b != 0x02) { next; }
|
||||
$started = 1;
|
||||
if($esc) { $retval .= chr($b); $esc = 0; next; }
|
||||
if($b == 0x10) { $esc = 1; next; }
|
||||
$retval .= chr($b);
|
||||
if($b == 0x03) { $complete = 1; last; }
|
||||
}
|
||||
if($complete) {
|
||||
my $l = length($retval);
|
||||
if($l < 8) { printf("Msg too short\n"); last; }
|
||||
if(b($retval,1) != 0) { printf("Bad second byte\n"); last; }
|
||||
if(w($retval,2) != $l-7) { printf("Length mismatch\n"); last; }
|
||||
if(!checkcrc($retval,$l-7)) { printf("Bad CRC\n"); last; }
|
||||
return substr($retval, 4, $l-7);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
printf "Timeout reading the answer\n";
|
||||
exit(1);
|
||||
}
|
||||
#########################
|
||||
sub
|
||||
hexdump($)
|
||||
{
|
||||
my ($d) = @_;
|
||||
for(my $i = 0; $i < length($d); $i += 16) {
|
||||
my $h = unpack("H*", substr($d, $i, 16));
|
||||
$h =~ s/(....)/$1 /g;
|
||||
printf "RAW %-40s\n", $h;
|
||||
}
|
||||
}
|
||||
|
||||
#########################
|
||||
sub
|
||||
getVersion()
|
||||
{
|
||||
my $d = getData("76");
|
||||
printf "%d.%d\n", b($d,0), b($d,1);
|
||||
}
|
||||
|
||||
#########################
|
||||
sub
|
||||
getTime()
|
||||
{
|
||||
my $d = getData("74");
|
||||
printf("%4d-%02d-%02d %02d:%02d:%02d\n",
|
||||
b($d,5)+2006, b($d,4), b($d,3),
|
||||
b($d,0), b($d,1), b($d,2));
|
||||
}
|
||||
|
||||
#########################
|
||||
sub
|
||||
getDevStatus()
|
||||
{
|
||||
die "Usage: getDevStatus devicenumber (1-12)\n" if(@ARGV != 3);
|
||||
my $d = getData(sprintf("7a%02x",$ARGV[2]-1));
|
||||
|
||||
if($d eq ((pack('H*',"00") x 45) . pack('H*',"FF") x 6)) {
|
||||
printf(" No device no. $ARGV[2] present\n");
|
||||
return;
|
||||
}
|
||||
my $pulses=w($d,13);
|
||||
my $pulses_max=w($d,15);
|
||||
my $ec=w($d,49) / 10;
|
||||
my $cur_energy=0;
|
||||
my $cur_power=0;
|
||||
my $cur_power_max=0;
|
||||
my $sum_h_energy=0;
|
||||
my $sum_d_energy=0;
|
||||
my $sum_w_energy=0;
|
||||
my $total_energy=0;
|
||||
my $iec=0;
|
||||
|
||||
printf(" Readings (off 2): %d\n", w($d,2));
|
||||
printf(" Nr devs (off 6): %d\n", b($d,6));
|
||||
printf(" puls/5min (off 13): %d\n", $pulses);
|
||||
printf(" puls.max/5min (off 15): %d\n", $pulses_max);
|
||||
#printf(" Startblk (off 18): %d\n", b($d,18)+13);
|
||||
#for (my $lauf = 19; $lauf < 45; $lauf += 2) {
|
||||
# printf(" t wert (off $lauf): %d\n", w($d,$lauf));
|
||||
#}
|
||||
# The data must interpreted depending on the sensor type.
|
||||
# Currently we use the EC value to quess the sensor type.
|
||||
if ($ec eq 0) {
|
||||
# Sensor 5..
|
||||
$iec = 1000;
|
||||
$cur_power = $pulses / 100;
|
||||
$cur_power_max = $pulses_max / 100;
|
||||
} else {
|
||||
# Sensor 1..4
|
||||
$iec = $ec;
|
||||
$cur_energy = $pulses / $ec; # ec = U/kWh
|
||||
$cur_power = $cur_energy / 5 * 60; # 5minute interval scaled to 1h
|
||||
printf(" cur.energy(off ): %.3f kWh\n", $cur_energy);
|
||||
}
|
||||
$sum_h_energy= dw($d,33) / $iec; # 33= pulses this hour
|
||||
$sum_d_energy= dw($d,37) / $iec; # 37= pulses today
|
||||
$sum_w_energy= dw($d,41) / $iec; # 41= pulses this week
|
||||
$total_energy= dw($d, 7) / $iec; # 7= pulses total
|
||||
printf(" cur.power ( ): %.3f kW\n", $cur_power);
|
||||
printf(" cur.power max ( ): %.3f kW\n", $cur_power_max);
|
||||
printf(" energy h (off 33): %.3f kWh (h)\n", $sum_h_energy);
|
||||
printf(" energy d (off 37): %.3f kWh (d)\n", $sum_d_energy);
|
||||
printf(" energy w (off 41): %.3f kWh (w)\n", $sum_w_energy);
|
||||
printf(" total energy (off 7): %.3f kWh (total)\n", $total_energy);
|
||||
printf(" Alarm PA (off 45): %d W\n", w($d,45));
|
||||
printf(" Price CF (off 47): %0.2f EUR/kWh\n", w($d,47)/10000);
|
||||
printf(" R/kW EC (off 49): %d\n", $ec);
|
||||
hexdump($d);
|
||||
}
|
||||
|
||||
#########################
|
||||
sub
|
||||
getDevPage()
|
||||
{
|
||||
die "Usage: getDevPage pagenumber [length] (default length is 264)\n"
|
||||
if(@ARGV < 3);
|
||||
my $l = (@ARGV > 3 ? $ARGV[3] : 264);
|
||||
my $d = getData(sprintf("52%02x%02x0000%02x%02x",
|
||||
$ARGV[2]%256, int($ARGV[2]/256), $l%256, int($l/256)));
|
||||
hexdump($d);
|
||||
}
|
||||
|
||||
#########################
|
||||
sub
|
||||
getDevData()
|
||||
{
|
||||
my $smooth = 1; # Set this to 0 to get the "real" values
|
||||
|
||||
die "Usage: getDevData devicenumber (1-12)\n" if(@ARGV != 3);
|
||||
my $d = getData(sprintf("7a%02x",$ARGV[2]-1));
|
||||
|
||||
if($d eq ((pack('H*',"00") x 45) . pack('H*',"FF") x 6)) {
|
||||
printf(" No device no. $ARGV[2] present\n");
|
||||
return;
|
||||
}
|
||||
|
||||
my $nrreadings = w($d,2);
|
||||
if($nrreadings == 0) {
|
||||
printf("No data to read (yet?)\n");
|
||||
exit(0);
|
||||
}
|
||||
my $step = b($d,6);
|
||||
my $start = b($d,18)+13;
|
||||
my $end = $start + int(($nrreadings-1)/64)*$step;
|
||||
my $div = w($d,49)/10;
|
||||
if ($div eq 0) {
|
||||
$div = 1;
|
||||
}
|
||||
|
||||
#printf("Total $nrreadings, $start - $end, Nr $step\n");
|
||||
|
||||
my $tm = time()-(($nrreadings-1)*300);
|
||||
my $backlog = 0;
|
||||
for(my $p = $start; $p <= $end; $p += $step) {
|
||||
#printf("Get page $p\n");
|
||||
|
||||
$d = getData(sprintf("52%02x%02x00000801", $p%256, int($p/256)));
|
||||
|
||||
#hexdump($d);
|
||||
|
||||
my $max = (($p == $end) ? ($nrreadings%64)*4+4 : 260);
|
||||
my $step = b($d, 6);
|
||||
|
||||
for(my $off = 8; $off <= $max; $off += 4) {
|
||||
$backlog++;
|
||||
if($smooth && (w($d,$off+2) == 0xffff)) { # "smoothing"
|
||||
next;
|
||||
} else {
|
||||
my $v = w($d,$off)*12/$div/$backlog;
|
||||
my $f1 = b($d,$off+2);
|
||||
my $f2 = b($d,$off+3);
|
||||
my $f3 = w($d,$off+2);
|
||||
|
||||
while($backlog--) {
|
||||
printf("%s %0.3f kWh (%d %d %d)\n", maketime($tm), $v,
|
||||
($backlog?-1:$f1), ($backlog?-1:$f2), ($backlog?-1:$f3));
|
||||
$tm += 300;
|
||||
}
|
||||
$backlog = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub
|
||||
setPrice()
|
||||
{
|
||||
die "Usage: setPrice device value_in_cent\n"
|
||||
if(@ARGV != 4);
|
||||
my $d = $ARGV[2];
|
||||
my $v = $ARGV[3];
|
||||
|
||||
$d = getData(sprintf("79%02x2f02%02x%02x", $d-1, $v%256, int($v/256)));
|
||||
if(b($d,0) == 6) {
|
||||
print("OK");
|
||||
} else {
|
||||
print("Error occured");
|
||||
hexdump($d);
|
||||
}
|
||||
}
|
||||
|
||||
sub
|
||||
setAlarm()
|
||||
{
|
||||
die "Usage: setAlarm device value_in_kWh\n"
|
||||
if(@ARGV != 4);
|
||||
my $d = $ARGV[2];
|
||||
my $v = $ARGV[3];
|
||||
|
||||
$d = getData(sprintf("79%02x2d02%02x%02x", $d-1, $v%256, int($v/256)));
|
||||
if(b($d,0) == 6) {
|
||||
print("OK");
|
||||
} else {
|
||||
print("Error occured");
|
||||
hexdump($d);
|
||||
}
|
||||
}
|
||||
|
||||
sub
|
||||
setRperKW()
|
||||
{
|
||||
die "Usage: setRperKW device rotations_per_KW\n"
|
||||
if(@ARGV != 4);
|
||||
my $d = $ARGV[2];
|
||||
my $v = $ARGV[3];
|
||||
|
||||
$v = $v * 10;
|
||||
$d = getData(sprintf("79%02x3102%02x%02x", $d-1, $v%256, int($v/256)));
|
||||
if(b($d,0) == 6) {
|
||||
print("OK");
|
||||
} else {
|
||||
print("Error occured");
|
||||
hexdump($d);
|
||||
}
|
||||
}
|
||||
|
||||
sub
|
||||
reset()
|
||||
{
|
||||
my $d = getData("4545");
|
||||
hexdump($d);
|
||||
}
|
||||
|
||||
sub
|
||||
get62()
|
||||
{
|
||||
my $d = getData("62");
|
||||
hexdump($d);
|
||||
}
|
||||
|
||||
sub
|
||||
setTime()
|
||||
{
|
||||
my $a2 = '';
|
||||
my $a3 = '';
|
||||
|
||||
if (@ARGV == 2) {
|
||||
my @lt = localtime;
|
||||
$a2 = sprintf ("%04d-%02d-%02d", $lt[5]+1900, $lt[4]+1, $lt[3]);
|
||||
$a3 = sprintf ("%02d:%02d:%02d", $lt[2], $lt[1], $lt[0]);
|
||||
} else {
|
||||
die "Usage: setTime [time] (as YYYY-MM-DD HH:MM:SS, localtime if empty)\n"
|
||||
if(@ARGV != 4);
|
||||
$a2 = $ARGV[2];
|
||||
$a3 = $ARGV[3];
|
||||
}
|
||||
my @d = split("-", $a2);
|
||||
my @t = split(":", $a3);
|
||||
|
||||
my $s = sprintf("73%02x%02x%02x00%02x%02x%02x",
|
||||
$d[2],$d[1],$d[0]-2000+0xd0,
|
||||
$t[0],$t[1],$t[2]);
|
||||
print("-> $s\n");
|
||||
|
||||
my $d = getData($s);
|
||||
if(b($d,0) == 6) {
|
||||
print("OK");
|
||||
} else {
|
||||
print("Error occured");
|
||||
hexdump($d);
|
||||
}
|
||||
}
|
|
@ -1,45 +0,0 @@
|
|||
2009-01-11 (1.4) Martin Fischer <m_fischer@gmx.de>
|
||||
|
||||
* test for arguments added
|
||||
* new option --set to set device status added
|
||||
* documentation updated
|
||||
|
||||
2009-01-07 (1.3) Martin Fischer <m_fischer@gmx.de>
|
||||
|
||||
* Perl modul JSON::XS for communication with FHEM included. This requires
|
||||
jsonlist (fhem/contrib) for FHEM.
|
||||
* several changes for parsing the JSON result
|
||||
* buffering disabled
|
||||
* output as asterisk AGI command added
|
||||
* new subroutine for removing value units
|
||||
|
||||
2009-01-01 (1.2) Martin Fischer <m_fischer@gmx.de>
|
||||
|
||||
* Perl modul Term::ANSIColor removed
|
||||
* external command 'recode' for converting UTF8 to latin1. mbrola need
|
||||
this to speek german umlauts
|
||||
* new output format gsm
|
||||
* options for outputformat now configurable
|
||||
* translation for special chars added
|
||||
* new option --asterisk, -a to support asterisk parsing added
|
||||
* new option --prefix to flag outfiles with a user prefix added
|
||||
* new option --force to override existing files added
|
||||
* support for reading files added
|
||||
* several changes in subroutine text2speech (code cleanup)
|
||||
|
||||
2008-12-31 (1.1) Martin Fischer <m_fischer@gmx.de>
|
||||
|
||||
* command-line options now works with Getopt::Long
|
||||
* check for external files (mbrola, txt2pho, sox, etc.) included
|
||||
* include a debug routine. use it with --debug on command-line
|
||||
* documentation added
|
||||
* added support for quiet mode. use it with -q, --quiet
|
||||
* FHEM error trap added
|
||||
* new option -t "TEXT". read the given text
|
||||
* new option to support female or male voice. use it with -S, --Sex
|
||||
on command-line
|
||||
* replace unwanted chars with text, e.g. - = Minus
|
||||
* support for generating (cached) wave files
|
||||
* update the documentation
|
||||
* added cached files for all modes
|
||||
|
Binary file not shown.
|
@ -1,345 +0,0 @@
|
|||
NAME
|
||||
fhem-speech - Synthesized voice (based on MBROLA) extension for FHEM
|
||||
|
||||
SYNOPSIS
|
||||
fhem-speech -d device [-achopqS]
|
||||
|
||||
fhem-speech -d device --set state [-hp]
|
||||
|
||||
fhem-speech -f file [-acoqS]
|
||||
|
||||
fhem-speech -t "text" [-acoqS]
|
||||
|
||||
fhem-speech [-HmV?]
|
||||
|
||||
Try `fhem-speech --man` for full manual!
|
||||
|
||||
DESCRIPTION
|
||||
fhem-speech
|
||||
fhem-speech read the status of a FHEM device and talk using the MBROLA
|
||||
speech synthesizer. Furthermore it can read the content of a given file
|
||||
or text.
|
||||
|
||||
FHEM
|
||||
FHEM is used to automate some common tasks in the household like
|
||||
switching lamps/shutters/heating/etc. and to log events like
|
||||
temperature/humidity/power consumption. Visit the FHEM's homepage
|
||||
<http://www.koeniglich.de/fhem/fhem.html> for more information.
|
||||
|
||||
The MBROLA project
|
||||
Central to the MBROLA project is MBROLA, a speech synthesizer based on
|
||||
the concatenation of diphones. It takes a list of phonemes as input,
|
||||
together with prosodic information, and produces speech samples on 16
|
||||
bits (linear), at the sampling frequency of the diphone database used.
|
||||
This synthesizer is provided for free, for non commercial, non military
|
||||
applications only. Visit the MBROLA's homepage
|
||||
<http://tcts.fpms.ac.be/synthesis/> for more information.
|
||||
|
||||
Asterisk
|
||||
Optionally fhem-speech supports AGI commands to communicate with
|
||||
Asterisk. Visit the Asterisk(R) homepage <http://www.asterisk.org/> for
|
||||
more information.
|
||||
|
||||
OPTIONS
|
||||
Mandatory arguments to long options are mandatory for short options too.
|
||||
Ordering Options:
|
||||
|
||||
-d, --device device
|
||||
Run in FHEM mode. Specifies the FHEM device to be queried. The given
|
||||
device must be defined.
|
||||
|
||||
-f, --file file-name
|
||||
Run in file mode. fhem-speech will read the given file.
|
||||
|
||||
-t, --text "TEXT"
|
||||
Run in Speaker's mode. fhem-speech will read the given "TEXT".
|
||||
|
||||
Other options:
|
||||
|
||||
-a, --asterisk
|
||||
Run in Asterisk mode. fhem-speech print out AGI-commands for direct
|
||||
usage in Asterisk.
|
||||
|
||||
-c, --cache directory
|
||||
Specifies the location of where the files should be saved if
|
||||
fhem-speech started with the -o or --out argument.
|
||||
|
||||
Default location: current directory.
|
||||
|
||||
--force
|
||||
Overwrites existing files.
|
||||
|
||||
-h, --host host
|
||||
Specifies the hostaddress for FHEM.
|
||||
|
||||
Default address: "localhost".
|
||||
|
||||
-o, --out [gsm|wav]
|
||||
fhem-speech saves the output to a file with the specified output
|
||||
format.
|
||||
|
||||
Default address: "localhost".
|
||||
|
||||
-p, --port port
|
||||
Communicate with FHEM on defined port.
|
||||
|
||||
Default port: "7072".
|
||||
|
||||
--prefix prefix
|
||||
Set the given prefix in front of filename.
|
||||
|
||||
-q, --quiet
|
||||
Run in quiet mode.
|
||||
|
||||
--set state
|
||||
Send <state> to device.
|
||||
|
||||
-S, --sex [f|m]
|
||||
Specifies the sex for the voice. It depends on which voices for
|
||||
MBROLA have been installed.
|
||||
|
||||
Default: "de3" for the German female voice and "de2" for the German
|
||||
male voice.
|
||||
|
||||
-m, --man
|
||||
Show the manual page and exits.
|
||||
|
||||
-H, --help
|
||||
Show a brief help message and exits.
|
||||
|
||||
-V, --version
|
||||
Show fhem-speech's version number and exit.
|
||||
|
||||
EXAMPLES
|
||||
Get status information for device <EG.wz.HZ> in quiet mode:
|
||||
|
||||
`fhem-speech -d EG.wz.HZ -q`
|
||||
|
||||
Same as above with a male voice. FHEM runs on IP 192.168.1.100:
|
||||
|
||||
`fhem-speech -d EG.wz.HZ -S m -h 192.168.1.100`
|
||||
|
||||
Get status information for device <EG.wz.HZ> in Asterisk mode:
|
||||
|
||||
`fhem-speech -d EG.wz.HZ -a -q -o gsm -c /var/lib/asterisk/sounds/fhem/`
|
||||
|
||||
Read the file <foobar>:
|
||||
|
||||
`fhem-speech -f foobar`
|
||||
|
||||
Read the given text "Geht nicht gibt's nicht.":
|
||||
|
||||
`fhem-speech -t "Geht nicht gibt's nicht."`
|
||||
|
||||
Set the state for device <EG.wz.SD.01>:
|
||||
|
||||
`fhem-speech -d EG.wz.SD.01 --set on`
|
||||
|
||||
INSTALLATION
|
||||
Requirements
|
||||
MBROLA
|
||||
You need MBROLA synthesizer, a synthesis voice, txt2pho and sox. For
|
||||
more information visit:
|
||||
|
||||
o MBROLA project, <http://tcts.fpms.ac.be/synthesis/>
|
||||
|
||||
o hadifix, <http://www.ikp.uni-bonn.de/dt/forsch/phonetik/hadifix/>
|
||||
|
||||
FHEM
|
||||
For FHEM mode you need FHEM 4.5+ and the command extension "jsonlist".
|
||||
For more information take a look at:
|
||||
|
||||
<fhem_src_path>/contrib/JsonList/README.JsonList
|
||||
|
||||
or visit the FHEM's homepage:
|
||||
|
||||
<http://www.koeniglich.de/fhem/fhem.html>
|
||||
|
||||
JSON::XS
|
||||
The required command extension "jsonlist" send the result as a JSON
|
||||
encoded string. fhem-speech need the Perl module JSON::XS to decode the
|
||||
information.
|
||||
|
||||
There are several ways to install the module:
|
||||
|
||||
You can download the last version at:
|
||||
|
||||
<http://search.cpan.org/~mlehmann/JSON-XS-2.231/XS.pm>
|
||||
|
||||
Or you can use the package from the contrib-folder which was delivered
|
||||
with fhem-speech.
|
||||
|
||||
You can use the cpan command on bash-prompt.
|
||||
|
||||
Installation
|
||||
This describes the installation on ubuntu:
|
||||
|
||||
Make a temporarily directory for the needed files and change to the new
|
||||
directory, e.g.:
|
||||
|
||||
`mkdir /usr/local/src/mbrola; cd !$`
|
||||
|
||||
Download the required files:
|
||||
|
||||
`wget http://www.ikp.uni-bonn.de/dt/forsch/phonetik/hadifix/txt2pho.zip`
|
||||
`wget http://tcts.fpms.ac.be/synthesis/mbrola/bin/pclinux/mbrola3.0.1h_i386.deb`
|
||||
|
||||
Download at least one synthesis voice (e.g. German female voice):
|
||||
|
||||
`wget http://tcts.fpms.ac.be/synthesis/mbrola/dba/de3/de3.zip`
|
||||
|
||||
txt2pho
|
||||
Install txt2pho:
|
||||
|
||||
`unzip txt2pho.zip -d /usr/share/`
|
||||
`chmod 755 /usr/share/txt2pho/txt2pho`
|
||||
|
||||
Edit txt2phorc:
|
||||
|
||||
`vi /usr/share/txt2pho/txt2phorc`
|
||||
|
||||
and change the path for DATAPATH and INVPATH:
|
||||
|
||||
DATAPATH=/usr/share/txt2pho/data/
|
||||
INVPATH=/usr/share/txt2pho/data/
|
||||
|
||||
Copy txt2phorc to /etc/txt2pho:
|
||||
|
||||
`cp /usr/share/txt2pho/txt2phorc /etc/txt2pho`
|
||||
|
||||
Synthesis Voice
|
||||
Install the synthesis voice (e.g. German female voice):
|
||||
|
||||
`unzip de7.zip -d /usr/share/mbrola/de7`
|
||||
|
||||
fhem-speech use "de2" and "de3" as default voices. You can change this
|
||||
if you like.
|
||||
|
||||
MBROLA
|
||||
Install MBROLA:
|
||||
|
||||
`dpkg -i mbrola3.0.1h_i386.deb`
|
||||
|
||||
sox
|
||||
Install sox:
|
||||
|
||||
`apt-get install sox libsox-fmt-all`
|
||||
|
||||
Test
|
||||
Test your installation:
|
||||
|
||||
`echo "Test" | /usr/share/txt2pho/txt2pho |\
|
||||
mbrola /usr/share/mbrola/de7/de7 - -.au | play -q -t au -`
|
||||
|
||||
fhem-speech
|
||||
Copy the script fhem-speech to a directory of your choice, e.g.:
|
||||
|
||||
`cp fhem-speech /usr/local/bin`
|
||||
|
||||
and make it executable:
|
||||
|
||||
`chmod 775 /usr/local/bin/fhem-speech`
|
||||
|
||||
Perl
|
||||
If you use the delivered module contrib/JSON-XS-2.231.tar.gz:
|
||||
|
||||
`tar xzf JSON-XS-2.231.tar.gz`
|
||||
`cd JSON-XS-2.231`
|
||||
`perl Makefile.pl`
|
||||
`make`
|
||||
`make test`
|
||||
|
||||
and as root:
|
||||
|
||||
`make install`
|
||||
|
||||
CONFIGURATION
|
||||
Open fhem-speech with your prefered editor.
|
||||
|
||||
FHEM host settings
|
||||
Change the default host, if you like:
|
||||
|
||||
###########################
|
||||
# FHEM
|
||||
$sys{fhem}{host} = "localhost";
|
||||
$sys{fhem}{port} = "7072";
|
||||
|
||||
External commands
|
||||
Change the paths depending on the installed distribution:
|
||||
|
||||
###########################
|
||||
# Mandatory external Files
|
||||
$sys{file}{mbrola} = "/usr/local/bin/mbrola";
|
||||
$sys{file}{pipefilt} = "/usr/local/bin/pipefilt";
|
||||
$sys{file}{play} = "/usr/bin/play";
|
||||
$sys{file}{preproc} = "/usr/local/bin/preproc";
|
||||
[...]
|
||||
|
||||
Change the default settings for synthesis voice:
|
||||
|
||||
###########################
|
||||
# mbrola / txt2pho options
|
||||
$sys{speech}{sex} = "f";
|
||||
$sys{speech}{male} = "-f0.8 -t0.9 -l 15000";
|
||||
$sys{speech}{female} = "-f1.2 -t1.0 -l 22050";
|
||||
|
||||
Translation
|
||||
fhem-speech need the $lang{} settings to decide what messages from FHEM
|
||||
to be spoken. For example take a look at the FHT part:
|
||||
|
||||
###########################
|
||||
# FHEM Translation
|
||||
|
||||
[...]
|
||||
|
||||
###########################
|
||||
# FHT
|
||||
# keys:
|
||||
$lang{'actuator'} = "Ventilstellung: %s Prozent";
|
||||
$lang{'day-temp'} = "Temperatur Tag: %s Grad";
|
||||
$lang{'desired-temp'} = "Angeforderte Temperatur: %s Grad";
|
||||
$lang{'measured-temp'} = "Gemessene Temperatur: %s Grad";
|
||||
$lang{'mode'} = "Modus: %s";
|
||||
$lang{'night-temp'} = "Temperatur Nacht: %s Grad";
|
||||
$lang{'windowopen-temp'} = "Temperatur Fenster offen: %s Grad";
|
||||
[...]
|
||||
|
||||
On every FHEM response all of the defined $lang{} status information
|
||||
will be spoken. If you don't like status information for e.g.
|
||||
'windowopen-temp' then comment this out:
|
||||
|
||||
# $lang{'windowopen-temp'} = "Temperatur Fenster offen: %s Grad";
|
||||
|
||||
If you like to know the status for e.g. 'lowtemp-offset' add a line like
|
||||
this:
|
||||
|
||||
$lang{'lowtemp-offset'} = "Versatz Temperatur %s Grad";
|
||||
|
||||
The '%s' stands as a placeholder for the value.
|
||||
|
||||
OPTIONAL
|
||||
Asterisk
|
||||
fhem-speech support AGI commands for direct output in Asterisk.
|
||||
|
||||
Wrapper
|
||||
If you like fhem-speech for use in Asterisk, you have to install a
|
||||
wrapper around fhem-speech. You can use the example from
|
||||
contrib/fhem-speech.agi.
|
||||
|
||||
Copy the wrapper to your asterisk-environment, e.g:
|
||||
|
||||
`cp contrib/fhem-speech.agi /var/lib/asterisk/agi-bin/`
|
||||
|
||||
extension.conf
|
||||
Take a look at the example from contrib/extension.conf.
|
||||
|
||||
LEGALESE
|
||||
License GPLv3+: GNU GPL version 3 or later
|
||||
<http://gnu.org/licenses/gpl.html>.
|
||||
|
||||
This is free software: you are free to change and redistribute it. There
|
||||
is NO WARRANTY, to the extent permitted by law.
|
||||
|
||||
AUTHOR
|
||||
Copyright (C) 2008 Martin Fischer <m_fischer@gmx.de>
|
||||
|
|
@ -1,163 +0,0 @@
|
|||
; extensions.conf - the Asterisk dial plan
|
||||
;
|
||||
|
||||
[myHCE]
|
||||
; houseautomation
|
||||
exten => 5000,1(myhce),Answer()
|
||||
exten => 5000,n,Set(TIMEOUT(digit)=5)
|
||||
exten => 5000,n,Set(TIMEOUT(response)=10)
|
||||
; skip authentication for known numbers
|
||||
exten => 5000,n,GotoIf($["${CALLERID(num)}" = "01601234567"]?5000,main)
|
||||
exten => 5000,n,GotoIf($["${CALLERID(num)}" = "01701234567"]?5000,main)
|
||||
; authentication
|
||||
exten => 5000,n,Authenticate(1137)
|
||||
exten => 5000,n,Wait(1)
|
||||
; main menu
|
||||
exten => 5000,n(main),NoOp(Main Menu)
|
||||
exten => 5000,n,Set(GLOBAL(myHCE_ext)=${EXTEN})
|
||||
exten => 5000,n,Set(GLOBAL(myHCE_pExt)=5000)
|
||||
exten => 5000,n,Set(GLOBAL(myHCE_pCon)=myHCE)
|
||||
include => myHCE-default
|
||||
exten => 5000,n(menu),AGI(fhem-speech.agi,t,"Hauptmenü")
|
||||
exten => 5000,n(choice),AGI(fhem-speech.agi,t,"Bitte wählen Sie")
|
||||
exten => 5000,n,AGI(fhem-speech.agi,t,"1 für Statusabfrage")
|
||||
exten => 5000,n,AGI(fhem-speech.agi,t,"2 für Steuerung")
|
||||
exten => 5000,n,AGI(fhem-speech.agi,t,"5 für Hilfe")
|
||||
exten => 5000,n,Background(silence/3)
|
||||
exten => 5000,n,Goto(choice)
|
||||
; help
|
||||
exten => 5000,n(help),AGI(fhem-speech.agi,t,"Menüsteuerung für alle Menüs")
|
||||
exten => 5000,n,AGI(fhem-speech.agi,t,"8 zurück zum letzten Menü")
|
||||
exten => 5000,n,AGI(fhem-speech.agi,t,"9 zurück zum Hauptmenü")
|
||||
exten => 5000,n,AGI(fhem-speech.agi,t,"0 zum Beenden")
|
||||
exten => 5000,n,Background(silence/3)
|
||||
exten => 5000,n,Goto(menu)
|
||||
; selection
|
||||
exten => 1,1,Goto(myHCE-status,5100,status)
|
||||
exten => 2,1,Goto(myHCE-control,5200,control)
|
||||
exten => 5,1,Goto(5000,help)
|
||||
|
||||
[myHCE-default]
|
||||
; global menu navigation
|
||||
exten => 8,1,Goto(${myHCE_pCon},${myHCE_pExt},menu)
|
||||
exten => 9,1,Goto(myHCE,5000,main)
|
||||
exten => 0,1,Goto(myHCE-exit,5099,exit)
|
||||
; wrong input
|
||||
exten => i,1,AGI(fhem-speech.agi,t,"Falsche Eingabe.")
|
||||
exten => i,2,Goto(${myHCE_ext},menu)
|
||||
|
||||
[myHCE-exit]
|
||||
; exit
|
||||
exten => 5099,n(exit),AGI(fhem-speech.agi,t,"Verbindung wird getrennt. Vielen Dank!")
|
||||
exten => 5099,n,Hangup()
|
||||
|
||||
[myHCE-status]
|
||||
exten => 5100,1(status),NoOp(Status Menu)
|
||||
exten => 5100,n,Set(GLOBAL(myHCE_ext)=${EXTEN})
|
||||
exten => 5100,n,Set(GLOBAL(myHCE_pExt)=5000)
|
||||
exten => 5100,n,Set(GLOBAL(myHCE_pCon)=myHCE)
|
||||
include => myHCE-default
|
||||
; submenu device status
|
||||
exten => 5100,n(menu),AGI(fhem-speech.agi,t,"Menü Statusabfrage")
|
||||
exten => 5100,n(choice),AGI(fhem-speech.agi,t,"Bitte wählen Sie")
|
||||
exten => 5100,n,AGI(fhem-speech.agi,t,"1 für Wetterstation")
|
||||
exten => 5100,n,AGI(fhem-speech.agi,t,"2 für Rauchmelder")
|
||||
exten => 5100,n,AGI(fhem-speech.agi,t,"5 für Raumthermostate")
|
||||
exten => 5100,n,Background(silence/3)
|
||||
exten => 5100,n,Goto(choice)
|
||||
; selection
|
||||
exten => 1,1,Playback(beep)
|
||||
exten => 1,n,AGI(fhem-speech.agi,d,GH.ga.WE.01)
|
||||
exten => 1,n,Playback(beep)
|
||||
exten => 1,n,Goto(5100,status)
|
||||
exten => 2,1,Playback(beep)
|
||||
exten => 2,n,AGI(fhem-speech.agi,d,NN.xx.RM.01)
|
||||
exten => 2,n,Playback(beep)
|
||||
exten => 2,n,Goto(5100,status)
|
||||
exten => 5,1,Goto(myHCE-status_fht,5110,menu)
|
||||
|
||||
[myHCE-status_fht]
|
||||
exten => 5110,1(status),NoOp(Status Menu)
|
||||
exten => 5110,n,Set(GLOBAL(myHCE_ext)=${EXTEN})
|
||||
exten => 5110,n,Set(GLOBAL(myHCE_pExt)=5100)
|
||||
exten => 5110,n,Set(GLOBAL(myHCE_pCon)=myHCE-status)
|
||||
include => myHCE-default
|
||||
; submenu fht devices
|
||||
exten => 5110,n(menu),AGI(fhem-speech.agi,t,"Menü Raumthermostate")
|
||||
exten => 5110,n(choice),AGI(fhem-speech.agi,t,"Bitte wählen Sie")
|
||||
exten => 5110,n,AGI(fhem-speech.agi,t,"1 für Wohnzimmer")
|
||||
exten => 5110,n,AGI(fhem-speech.agi,t,"2 für Schlafzimmer")
|
||||
exten => 5110,n,AGI(fhem-speech.agi,t,"3 für Büro")
|
||||
exten => 5110,n,AGI(fhem-speech.agi,t,"4 für Badezimmer")
|
||||
exten => 5110,n,Background(silence/3)
|
||||
exten => 5110,n,Goto(choice)
|
||||
; selection
|
||||
exten => 1,1,Playback(beep)
|
||||
exten => 1,n,AGI(fhem-speech.agi,d,EG.wz.HZ)
|
||||
exten => 1,n,Playback(beep)
|
||||
exten => 1,n,Goto(5110,status)
|
||||
exten => 2,1,Playback(beep)
|
||||
exten => 2,n,AGI(fhem-speech.agi,d,EG.sz.HZ)
|
||||
exten => 2,n,Playback(beep)
|
||||
exten => 2,n,Goto(5110,status)
|
||||
exten => 3,1,Playback(beep)
|
||||
exten => 3,n,AGI(fhem-speech.agi,d,EG.bu.HZ)
|
||||
exten => 3,n,Playback(beep)
|
||||
exten => 3,n,Goto(5110,status)
|
||||
exten => 4,1,Playback(beep)
|
||||
exten => 4,n,AGI(fhem-speech.agi,d,EG.bz.HZ)
|
||||
exten => 4,n,Playback(beep)
|
||||
exten => 4,n,Goto(5110,status)
|
||||
|
||||
[myHCE-control]
|
||||
include => myHCE-default
|
||||
exten => 5200,1(control),AGI(fhem-speech.agi,t,"Menü Steuerung")
|
||||
exten => 5200,n(menu),AGI(fhem-speech.agi,t,"Bitte wählen Sie")
|
||||
exten => 5200,n,AGI(fhem-speech.agi,t,"1 für Wohnzimmer")
|
||||
exten => 5200,n,AGI(fhem-speech.agi,t,"2 für Schlafzimmer")
|
||||
exten => 5200,n,AGI(fhem-speech.agi,t,"3 für Büro")
|
||||
exten => 5200,n,AGI(fhem-speech.agi,t,"4 für Badezimmer")
|
||||
exten => 5200,n,Background(silence/3)
|
||||
exten => 5200,n,Goto(menu)
|
||||
|
||||
exten => 1,1,Goto(myHCE-control_wohnen,5210,menu)
|
||||
|
||||
exten => i,1,AGI(fhem-speech.agi,t,"Falsche Eingabe.")
|
||||
exten => i,2,Goto(5200,menu)
|
||||
|
||||
[myHCE-control_wohnen]
|
||||
include => myHCE-default
|
||||
exten => 5210,1(control),AGI(fhem-speech.agi,t,"Menü Steuerung")
|
||||
exten => 5210,n,AGI(fhem-speech.agi,t,"Wohnzimmer")
|
||||
exten => 5210,n(menu),AGI(fhem-speech.agi,t,"Bitte wählen Sie")
|
||||
exten => 5210,n,AGI(fhem-speech.agi,t,"1 für Lampen")
|
||||
exten => 5210,n,Background(silence/3)
|
||||
exten => 5210,n,Goto(menu)
|
||||
|
||||
exten => 1,1,Goto(myHCE-control_wohnen-lampen,5211,set)
|
||||
|
||||
exten => 8,1,Goto(myHCE-control,5200,menu)
|
||||
|
||||
exten => i,1,AGI(fhem-speech.agi,t,"Falsche Eingabe.")
|
||||
exten => i,2,Goto(5200,menu)
|
||||
|
||||
[myHCE-control_wohnen-lampen]
|
||||
include => myHCE-default
|
||||
exten => 5211,1(set),AGI(fhem-speech.agi,t,"Steuerung Lampen")
|
||||
exten => 5211,n,AGI(fhem-speech.agi,d,EG.wz.SD.Licht.grp)
|
||||
exten => 5211,n(menu),AGI(fhem-speech.agi,t,"1 für an")
|
||||
exten => 5211,n,AGI(fhem-speech.agi,t,"2 für aus")
|
||||
exten => 5211,n,Background(silence/3)
|
||||
exten => 5211,n,Goto(menu)
|
||||
|
||||
exten => 1,1,AGI(fhem-speech.agi,s,EG.wz.SD.Licht.grp,on)
|
||||
exten => 1,n,Goto(5211,set)
|
||||
|
||||
exten => 2,1,AGI(fhem-speech.agi,s,EG.wz.SD.Licht.grp,off)
|
||||
exten => 2,n,Goto(5211,set)
|
||||
|
||||
exten => 8,1,Goto(myHCE-control_wohnen,5210,menu)
|
||||
|
||||
exten => i,1,AGI(fhem-speech.agi,t,"Falsche Eingabe.")
|
||||
exten => i,2,Goto(5211,menu)
|
||||
|
File diff suppressed because it is too large
Load Diff
|
@ -1,55 +0,0 @@
|
|||
#!/usr/bin/perl
|
||||
################################################################
|
||||
#
|
||||
# $Id: fhem-speech.agi,v 1.1 2009-01-12 10:26:50 rudolfkoenig Exp $
|
||||
#
|
||||
|
||||
use strict;
|
||||
|
||||
$|=1;
|
||||
|
||||
# Setup some variables
|
||||
my $sounds = "/var/lib/asterisk/sounds/fhem/";
|
||||
|
||||
my %AGI;
|
||||
my $tests = 0;
|
||||
my $fail = 0;
|
||||
my $pass = 0;
|
||||
|
||||
while(<STDIN>) {
|
||||
chomp;
|
||||
last unless length($_);
|
||||
if (/^agi_(\w+)\:\s+(.*)$/) {
|
||||
$AGI{$1} = $2;
|
||||
}
|
||||
}
|
||||
|
||||
print STDERR "AGI Environment Dump:\n";
|
||||
foreach my $i (sort keys %AGI) {
|
||||
print STDERR " -- $i = $AGI{$i}\n";
|
||||
}
|
||||
|
||||
sub checkresult {
|
||||
my ($res) = @_;
|
||||
my $retval;
|
||||
$tests++;
|
||||
chomp $res;
|
||||
if ($res =~ /^200/) {
|
||||
$res =~ /result=(-?\d+)/;
|
||||
if (!length($1)) {
|
||||
print STDERR "FAIL ($res)\n";
|
||||
$fail++;
|
||||
} else {
|
||||
print STDERR "PASS ($1)\n";
|
||||
$pass++;
|
||||
}
|
||||
} else {
|
||||
print STDERR "FAIL (unexpected result '$res')\n";
|
||||
$fail++;
|
||||
}
|
||||
}
|
||||
|
||||
system("fhem-speech -d $ARGV[1] -a -q -o gsm -c $sounds") if ($ARGV[0] eq "d");
|
||||
system("fhem-speech -t $ARGV[1] -a -q -o gsm -c $sounds") if ($ARGV[0] eq "t");
|
||||
system("fhem-speech -d $ARGV[1] --set $ARGV[2]") if ($ARGV[0] eq "s");
|
||||
|
|
@ -1,34 +0,0 @@
|
|||
############################
|
||||
# Display the measured temperature and actuator data logged
|
||||
# as described in the 04_log config file.
|
||||
# Copy your logfile to fht.log and then call
|
||||
# gnuplot fht.gnuplot
|
||||
# (i.e. this file)
|
||||
# Note: The webfrontend pgm2 and pgm3 does this for you.
|
||||
# More examples can be found in the webfrontend/pgm2 directory.
|
||||
|
||||
|
||||
###########################
|
||||
# Uncomment the following if you want to create a postscript file
|
||||
# and comment out the pause at the end
|
||||
#set terminal postscript color "Helvetica" 11
|
||||
#set output 'fht.ps'
|
||||
|
||||
set xdata time
|
||||
set timefmt "%Y-%m-%d_%H:%M:%S"
|
||||
set xlabel " "
|
||||
|
||||
set ylabel "Temperature (Celsius)"
|
||||
set y2label "Actuator (%)"
|
||||
set ytics nomirror
|
||||
set y2tics
|
||||
set y2label "Actuator (%)"
|
||||
|
||||
set title 'FHT log'
|
||||
plot \
|
||||
"< awk '/measured/{print $1, $4}' fht.log"\
|
||||
using 1:2 axes x1y1 title 'Measured temperature' with lines,\
|
||||
"< awk '/actuator/{print $1, $4+0}' fht.log"\
|
||||
using 1:2 axes x1y2 title 'Actuator (%)' with lines\
|
||||
|
||||
pause 100000
|
|
@ -1,6 +0,0 @@
|
|||
CC=gcc
|
||||
|
||||
four2hex : four2hex.c
|
||||
|
||||
install : four2hex
|
||||
install -m 0755 four2hex /usr/local/bin/four2hex
|
|
@ -1,23 +0,0 @@
|
|||
Four2hex was written to convert the housecode based on digits ranging from 1
|
||||
to 4 into hex code and vica versa.
|
||||
Four2hex is freeware based on the GNU Public license.
|
||||
|
||||
To built it:
|
||||
$ make four2hex
|
||||
|
||||
Install it to /usr/local/bin:
|
||||
$ su
|
||||
# make install
|
||||
|
||||
|
||||
Here an example from "four"-based to hex:
|
||||
$ four2hex 12341234
|
||||
1b1b
|
||||
|
||||
Here an example in the other (reverse) direction:
|
||||
$ four2hex -r 1b1b
|
||||
12341234
|
||||
|
||||
Enjoy.
|
||||
Peter Stark, (Peter dot stark at t-online dot de)
|
||||
|
Binary file not shown.
|
@ -1,94 +0,0 @@
|
|||
/*
|
||||
Four2hex was written to convert the housecode based on digits ranging from 1
|
||||
to 4 into hex code and vica versa.
|
||||
Four2hex is freeware based on the GNU Public license.
|
||||
|
||||
To built it:
|
||||
$ make four2hex
|
||||
|
||||
Install it to /usr/local/bin:
|
||||
$ su
|
||||
# make install
|
||||
|
||||
|
||||
Here an example from "four"-based to hex:
|
||||
$ four2hex 12341234
|
||||
1b1b
|
||||
|
||||
Here an example in the other (reverse) direction:
|
||||
$ four2hex -r 1b1b
|
||||
12341234
|
||||
|
||||
Enjoy.
|
||||
Peter Stark, (Peter dot stark at t-online dot de)
|
||||
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <ctype.h>
|
||||
|
||||
int atoh (const char c)
|
||||
{
|
||||
int ret=0;
|
||||
|
||||
ret = (int) (c - '0');
|
||||
if (ret > 9) {
|
||||
ret = (int) (c - 'a' + 10);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
int strlen(const char *);
|
||||
|
||||
main (int argc, char **argv)
|
||||
{
|
||||
char c, *s, *four;
|
||||
long int result;
|
||||
int b, i, h;
|
||||
|
||||
if (argc < 2 || argc >3) {
|
||||
fprintf (stderr, "usage: four2hex four-string\n");
|
||||
fprintf (stderr, " or: four2hex -r hex-string\n");
|
||||
return (1);
|
||||
}
|
||||
result = 0L;
|
||||
if (strcmp(argv[1], "-r") == 0) {
|
||||
/* reverse (hex->4) */
|
||||
for (s = argv[2]; *s != '\0'; s++) {
|
||||
c = tolower(*s);
|
||||
b = atoh(c);
|
||||
for (i = 0; i < 2; i++) {
|
||||
h = ((b & 0xc) >> 2) + 1;
|
||||
b = (b & 0x3) << 2;
|
||||
printf ("%d", h);
|
||||
}
|
||||
}
|
||||
printf ("\n");
|
||||
} else {
|
||||
/* normal (4->hex) */
|
||||
four = argv[1];
|
||||
if (strlen(four) == 4 || strlen(four) == 8) {
|
||||
for (s = four; *s != '\0'; s++) {
|
||||
result = result << 2;
|
||||
switch (*s) {
|
||||
case '1' : result = result + 0; break;
|
||||
case '2' : result = result + 1; break;
|
||||
case '3' : result = result + 2; break;
|
||||
case '4' : result = result + 3; break;
|
||||
default :
|
||||
fprintf (stderr, "four-string may contain '1' to '4' only\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (strlen(four) == 8) {
|
||||
printf ("%04x\n", result);
|
||||
} else {
|
||||
printf ("%02x\n", result);
|
||||
}
|
||||
} else {
|
||||
fprintf (stderr, "four-string must be of length 4 or 8\n");
|
||||
return (1);
|
||||
}
|
||||
}
|
||||
return (0);
|
||||
}
|
|
@ -1,94 +0,0 @@
|
|||
#!/bin/sh
|
||||
#
|
||||
# script to generate a random number of on/off events to simulate presence eg.
|
||||
# while on holidays. normally this script would be executed by an event like a
|
||||
# dawn-sensor (you wouldn't want light during the day...:-)
|
||||
#
|
||||
# Copyright STefan Mayer <stefan@clumsy.ch>
|
||||
|
||||
################## configuration ###########################
|
||||
#number of events (min - max)
|
||||
event_min=5
|
||||
event_max=20
|
||||
|
||||
#maximum delay in minutes
|
||||
delay_max=240
|
||||
|
||||
#minimum and maximum ontime in minutes
|
||||
ontime_min=5
|
||||
ontime_max=60
|
||||
|
||||
#devices to consider
|
||||
declare -a devices='("dg.gang" "dg.wand" "dg.dusche" "dg.bad" "dg.reduit")'
|
||||
|
||||
#output variant [oft|onoff]
|
||||
#oft: use one at with on-for-timer of system
|
||||
#onoff: use two at, one for on one for off
|
||||
variant="onoff"
|
||||
|
||||
#command to execute
|
||||
#command_start="/opt/fhem/fhem.pl 7072 \""
|
||||
command_start="echo /opt/fhem/fhem.pl 7072 \""
|
||||
command_end="\""
|
||||
|
||||
|
||||
##################### Shouldnt need any changes below here #####################
|
||||
|
||||
# count number of devices
|
||||
count=0
|
||||
for i in ${devices[*]}
|
||||
do
|
||||
((count++))
|
||||
done
|
||||
# echo $count
|
||||
|
||||
# maximum random in bash: 32768
|
||||
random_max=32768
|
||||
|
||||
#number of events
|
||||
event=$(($RANDOM * (($event_max - $event_min)) / $random_max +$event_min))
|
||||
|
||||
#initialize command
|
||||
command=$command_start
|
||||
|
||||
for ((i=0; i<$event; i++))
|
||||
do
|
||||
#calculate starttime
|
||||
starttime=$(($RANDOM * $delay_max / $random_max))
|
||||
hour=$(($starttime / 60))
|
||||
minute=$(($starttime % 60))
|
||||
second=$(($RANDOM * 60 / $random_max))
|
||||
|
||||
#calculate ontime
|
||||
ontime=$(($RANDOM * (($ontime_max - $ontime_min)) / $random_max +$ontime_min))
|
||||
|
||||
#choose device
|
||||
dev=$(($RANDOM * $count / $random_max))
|
||||
|
||||
case $variant in
|
||||
oft)
|
||||
printf "event %02d: define at.random.%02d at +%02d:%02d:%02d set %s on-for-timer %d\n" $i $i $hour $minute $second ${devices[$dev]} $ontime
|
||||
command=`printf "$command define at.random.%02d at +%02d:%02d:%02d set %s on-for-timer %d;;" $i $hour $minute $second ${devices[$dev]} $ontime`
|
||||
;;
|
||||
onoff)
|
||||
offtime=$(($starttime + $ontime))
|
||||
hour_off=$(($offtime / 60))
|
||||
minute_off=$(($offtime % 60))
|
||||
second_off=$(($RANDOM * 60 / $random_max))
|
||||
printf "event %02d/on : define at.random.on.%02d at +%02d:%02d:%02d set %s on\n" $i $i $hour $minute $second ${devices[$dev]}
|
||||
printf "event %02d/off: define at.random.off.%02d at +%02d:%02d:%02d set %s off\n" $i $i $hour_off $minute_off $second_off ${devices[$dev]}
|
||||
command=`printf "$command define at.random.on.%02d at +%02d:%02d:%02d set %s on;;" $i $hour $minute $second ${devices[$dev]}`
|
||||
command=`printf "$command define at.random.off.%02d at +%02d:%02d:%02d set %s off;;" $i $hour_off $minute_off $second_off ${devices[$dev]}`
|
||||
;;
|
||||
*)
|
||||
echo "no variant specifieno variant specified!!"
|
||||
;;
|
||||
esac
|
||||
|
||||
done
|
||||
command="$command $command_end"
|
||||
|
||||
#execute command
|
||||
eval "$command"
|
||||
|
||||
|
|
@ -1,212 +0,0 @@
|
|||
#!/usr/bin/perl
|
||||
|
||||
use warnings;
|
||||
use strict;
|
||||
use IO::Socket::INET;
|
||||
use IO::Handle;
|
||||
|
||||
STDOUT->autoflush(1);
|
||||
|
||||
#################
|
||||
# Formula:
|
||||
# Compute for the last <navg> days + today the avarage temperature and the
|
||||
# sum of rain, then compute the multiplier: (temp/20)^2 - rain/5
|
||||
# Now multiply the duration of each vent with this multiplier
|
||||
# If the value is less than a minimum, then store the value and add it
|
||||
# the next day
|
||||
#################
|
||||
|
||||
my $test = 0; # Test only, do not switch anything
|
||||
my $fhzport = 7072; # Where to contact it
|
||||
my $avg = "/home/rudi/log/avg.log"; # KS300 avarage log file
|
||||
my $navg = 2; # Number of last avg_days to consider
|
||||
my $min = 300; # If the duration is < min (sec) then collect
|
||||
my $col = "/home/rudi/log/gardencoll.log"; # File where it will be collected
|
||||
my $pmp = "GPumpe"; # Name of the water pump, will be switched in first
|
||||
my $maxmult = 4; # Maximum factor (corresponds to 40 degree avg.
|
||||
# temp over $navg days, no rain)
|
||||
|
||||
if(@ARGV) {
|
||||
if($ARGV[0] eq "test") {
|
||||
$test = 1;
|
||||
} else {
|
||||
print "Usage: garden.pl [test]\n";
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
my %list = (
|
||||
GVent1 => { Nr => 1, Dur => 720 },
|
||||
GVent2 => { Nr => 2, Dur => 480 },
|
||||
GVent3 => { Nr => 3, Dur => 720 },
|
||||
GVent4 => { Nr => 4, Dur => 720 },
|
||||
GVent6 => { Nr => 5, Dur => 720 },
|
||||
GVent7 => { Nr => 6, Dur => 480 },
|
||||
GVent8 => { Nr => 7, Dur => 480 },
|
||||
);
|
||||
|
||||
##############################
|
||||
# End of config
|
||||
|
||||
sub fhzcommand($);
|
||||
sub doswitch($$);
|
||||
sub donext($$);
|
||||
|
||||
my ($nlines, $temp, $rain) = (0, 0, 0);
|
||||
my ($KS300name, $server, $last);
|
||||
|
||||
my @t = localtime;
|
||||
printf("%04d-%02d-%02d %02d:%02d:%02d\n",
|
||||
$t[5]+1900, $t[4]+1, $t[3], $t[2], $t[1], $t[0]);
|
||||
|
||||
###########################
|
||||
# First read in the last avg_days
|
||||
open(FH, $avg) || die("$avg: $!\n");
|
||||
my @avg = <FH>;
|
||||
close(FH);
|
||||
|
||||
my @tarr; # Want the printout in the right order
|
||||
while(my $l = pop(@avg)) {
|
||||
next if($l !~ m/avg_day/);
|
||||
my @v = split(" ", $l);
|
||||
push(@tarr, "$v[0]: T: $v[4], R: $v[10]") if($test);
|
||||
$temp += $v[4]; $rain += $v[10];
|
||||
$KS300name = $v[1];
|
||||
$nlines++;
|
||||
last if($nlines >= $navg);
|
||||
}
|
||||
|
||||
###########################
|
||||
# Now get the current day
|
||||
foreach my $l (split("\n", fhzcommand("list $KS300name"))) {
|
||||
next if($l !~ m/avg_day/);
|
||||
my @v = split(" ", $l);
|
||||
print("$v[0] $v[1]: T: $v[4], R: $v[10]\n") if($test);
|
||||
$temp += $v[4]; $rain += $v[10];
|
||||
$nlines++;
|
||||
last;
|
||||
}
|
||||
|
||||
if($test) {
|
||||
foreach my $l (@tarr) {
|
||||
print "$l\n";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
###########################
|
||||
# the collected data
|
||||
my %coll;
|
||||
if(open(FH, $col)) {
|
||||
while(my $l = <FH>) {
|
||||
my ($k, $v) = split("[ \n]", $l);
|
||||
$coll{$k} = $v;
|
||||
}
|
||||
close(FH);
|
||||
}
|
||||
|
||||
###########################
|
||||
# The formula
|
||||
$temp /= $nlines;
|
||||
$rain /= $nlines;
|
||||
|
||||
# safety measures
|
||||
$rain = 0 if($rain < 0);
|
||||
$temp = 0 if($temp < 0);
|
||||
$temp = 40 if($temp > 40);
|
||||
my $mult = exp( 2.0 * log( $temp / 20 )) - $rain/5;
|
||||
$mult = $maxmult if($mult > $maxmult);
|
||||
|
||||
if($mult <= 0) {
|
||||
print("Multiplier is not positive ($mult), exiting\n");
|
||||
exit(0);
|
||||
}
|
||||
|
||||
printf("Multiplier is %.2f (T: $temp, R: $rain)\n", $mult, $temp, $rain);
|
||||
|
||||
my $have = 0;
|
||||
if(!$test) {
|
||||
open(FH, ">$col") || die("Can't open $col: $!\n");
|
||||
}
|
||||
foreach my $a (sort { $list{$a}{Nr} <=> $list{$b}{Nr} } keys %list) {
|
||||
my $dur = int($list{$a}{Dur} * $mult);
|
||||
|
||||
if(defined($coll{$a})) {
|
||||
$dur += $coll{$a};
|
||||
printf(" $a: $dur ($coll{$a})\n");
|
||||
} else {
|
||||
printf(" $a: $dur\n");
|
||||
}
|
||||
|
||||
if($dur > $min) {
|
||||
$list{$a}{Act} = $dur;
|
||||
$have += $dur;
|
||||
} else {
|
||||
print FH "$a $dur\n" if(!$test);
|
||||
}
|
||||
}
|
||||
|
||||
print("Total time is $have\n");
|
||||
exit(0) if($test);
|
||||
close(FH);
|
||||
|
||||
if($have) {
|
||||
doswitch($pmp, "on") if($pmp);
|
||||
sleep(3) if(!$test);
|
||||
foreach my $a (sort { $list{$a}{Nr} <=> $list{$b}{Nr} } keys %list) {
|
||||
next if(!$list{$a}{Act});
|
||||
donext($a, $list{$a}{Act});
|
||||
}
|
||||
donext("", 0);
|
||||
doswitch($pmp, "off") if($pmp);
|
||||
}
|
||||
|
||||
###########################
|
||||
# Switch the next dev on and the last one off
|
||||
sub
|
||||
donext($$)
|
||||
{
|
||||
my ($dev, $sl) = @_;
|
||||
doswitch($dev, "on");
|
||||
doswitch($last, "off");
|
||||
$last = $dev;
|
||||
if($test) {
|
||||
print "sleeping $sl\n";
|
||||
} else {
|
||||
sleep($sl);
|
||||
}
|
||||
}
|
||||
|
||||
###########################
|
||||
# Paranoid setting.
|
||||
sub
|
||||
doswitch($$)
|
||||
{
|
||||
my ($dev, $how) = @_;
|
||||
return if(!$dev || !$how);
|
||||
|
||||
if($test) {
|
||||
print "set $dev $how\n";
|
||||
return;
|
||||
}
|
||||
fhzcommand("set $dev $how");
|
||||
sleep(1);
|
||||
fhzcommand("set $dev $how");
|
||||
}
|
||||
|
||||
###########################
|
||||
sub
|
||||
fhzcommand($)
|
||||
{
|
||||
my $cmd = shift;
|
||||
|
||||
my ($ret, $buf) = ("", "");
|
||||
$server = IO::Socket::INET->new(PeerAddr => "localhost:$fhzport");
|
||||
die "Can't connect to the server at port $fhzport\n" if(!$server);
|
||||
syswrite($server, "$cmd;quit\n");
|
||||
while(sysread($server, $buf, 256) > 0) {
|
||||
$ret .= $buf;
|
||||
}
|
||||
close($server);
|
||||
return $ret;
|
||||
}
|
|
@ -1,132 +0,0 @@
|
|||
################################################################
|
||||
#
|
||||
# $Id: 99_getstate.pm,v 1.2 2009-01-12 09:21:53 rudolfkoenig Exp $
|
||||
#
|
||||
# Copyright notice
|
||||
#
|
||||
# (c) 2008 Copyright: Martin Fischer (m_fischer at gmx dot de)
|
||||
# All rights reserved
|
||||
#
|
||||
# This script 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.
|
||||
#
|
||||
# The GNU General Public License can be found at
|
||||
# http://www.gnu.org/copyleft/gpl.html.
|
||||
# A copy is found in the textfile GPL.txt and important notices to the license
|
||||
# from the author is found in LICENSE.txt distributed with these scripts.
|
||||
#
|
||||
# This script 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.
|
||||
#
|
||||
################################################################
|
||||
|
||||
package main;
|
||||
use strict;
|
||||
use warnings;
|
||||
use POSIX;
|
||||
|
||||
sub CommandGetState($);
|
||||
sub stringToNumber($);
|
||||
sub stripNumber($);
|
||||
sub isNumber;
|
||||
sub isInteger;
|
||||
sub isFloat;
|
||||
|
||||
#####################################
|
||||
sub
|
||||
GetState_Initialize($$)
|
||||
{
|
||||
my %lhash = ( Fn=>"CommandGetState",
|
||||
Hlp=>"<devspec>,list short status info" );
|
||||
$cmds{getstate} = \%lhash;
|
||||
}
|
||||
|
||||
|
||||
#####################################
|
||||
sub
|
||||
CommandGetState($)
|
||||
{
|
||||
|
||||
my ($cl, $param) = @_;
|
||||
|
||||
return "Usage: getstate <devspec>" if(!$param);
|
||||
|
||||
my $str;
|
||||
my $sdev = $param;
|
||||
|
||||
if(!defined($defs{$sdev})) {
|
||||
$str = "Please define $sdev first";
|
||||
} else {
|
||||
|
||||
my $r = $defs{$sdev}{READINGS};
|
||||
my $val;
|
||||
my $v;
|
||||
|
||||
if($r) {
|
||||
foreach my $c (sort keys %{$r}) {
|
||||
undef($v);
|
||||
$val = $r->{$c}{VAL};
|
||||
$val =~ s/\s+$//g;
|
||||
$val = stringToNumber($val);
|
||||
$val = stripNumber($val);
|
||||
$v = $val if (isNumber($val) && !$v);
|
||||
$v = $val if (isInteger($val) && !$v);
|
||||
$v = $val if (isFloat($val) && !$v);
|
||||
$str .= sprintf("%s:%s ",$c,$v) if(defined($v));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return $str;
|
||||
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub stringToNumber($)
|
||||
{
|
||||
my $s = shift;
|
||||
|
||||
$s = "0" if($s =~ m/^(off|no \(yes\/no\))$/);
|
||||
$s = "1" if($s =~ m/^(on|yes \(yes\/no\))$/);
|
||||
|
||||
return $s;
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub stripNumber($)
|
||||
{
|
||||
my $s = shift;
|
||||
my @strip = (" (Celsius)", " (l/m2)", " (counter)", " (%)", " (km/h)" , "%");
|
||||
|
||||
foreach my $pattern (@strip) {
|
||||
$s =~ s/\Q$pattern\E//gi;
|
||||
}
|
||||
|
||||
return $s;
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub isNumber
|
||||
{
|
||||
$_[0] =~ /^\d+$/
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub isInteger
|
||||
{
|
||||
$_[0] =~ /^[+-]?\d+$/
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub isFloat
|
||||
{
|
||||
$_[0] =~ /^[+-]?\d+\.?\d*$/
|
||||
}
|
||||
|
||||
1;
|
|
@ -1,50 +0,0 @@
|
|||
NAME
|
||||
getstate.pm - Copyright (c)2008 Martin Fischer <m_fischer@gmx.de>
|
||||
|
||||
SYNOPSIS
|
||||
getstate <devspec>
|
||||
|
||||
DESCRIPTION
|
||||
The module getstate.pm extends FHEM to support a short status output of
|
||||
a device. It is useful for monitoring the device in e.g. Cacti.
|
||||
|
||||
INSTALLATION
|
||||
Copy the script 99_getstate.pm to FHEM modules directory, e.g.
|
||||
|
||||
'cp 99_getstate.pm /usr/local/lib/FHEM'
|
||||
|
||||
and restart FHEM.
|
||||
|
||||
EXAMPLES
|
||||
Output a short string of the "READINGS" for <devspec>.
|
||||
|
||||
Example for a FS20-Device:
|
||||
FHZ> getstate EG.sz.SD.Tv
|
||||
state:0
|
||||
|
||||
Example for a FHT-Device:
|
||||
FHZ> getstate EG.wz.HZ
|
||||
actuator:0 day-temp:21.5 desired-temp:21.5 lowtemp-offset:4.0 [...]
|
||||
|
||||
Example for a KS300/555-Device:
|
||||
FHZ> getstate GH.ga.WE.01
|
||||
humidity:93 israining:0 rain:207.8 rain_raw:815 temperature:5.1 [...]
|
||||
|
||||
Example for a HMS-Device:
|
||||
FHZ> getstate NN.xx.RM.01
|
||||
smoke_detect:0
|
||||
|
||||
CONTRIB
|
||||
You can use the example script contrib/fhem-getstate as a "Data Input
|
||||
Method" for your Cacti graphs.
|
||||
|
||||
LEGALESE
|
||||
License GPLv3+: GNU GPL version 3 or later
|
||||
<http://gnu.org/licenses/gpl.html>.
|
||||
|
||||
This is free software: you are free to change and redistribute it. There
|
||||
is NO WARRANTY, to the extent permitted by law.
|
||||
|
||||
AUTHOR
|
||||
Copyright (C) 2008 Martin Fischer <m_fischer@gmx.de>
|
||||
|
|
@ -1,91 +0,0 @@
|
|||
#!/bin/bash
|
||||
#
|
||||
# $Id: fhem-getstate,v 1.2 2009-01-12 09:21:53 rudolfkoenig Exp $
|
||||
#
|
||||
# Copyright notice
|
||||
#
|
||||
# (c) 2008 Copyright: Martin Fischer (m_fischer at gmx dot de)
|
||||
# All rights reserved
|
||||
#
|
||||
# This script 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.
|
||||
#
|
||||
# The GNU General Public License can be found at
|
||||
# http://www.gnu.org/copyleft/gpl.html.
|
||||
# A copy is found in the textfile GPL.txt and important notices to the license
|
||||
# from the author is found in LICENSE.txt distributed with these scripts.
|
||||
#
|
||||
# This script 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.
|
||||
#
|
||||
################################################################
|
||||
|
||||
NCAT=`which netcat`
|
||||
|
||||
HOST="localhost"
|
||||
PORT="7072"
|
||||
VERS="$Revision: 1.2 $"
|
||||
|
||||
# Functions
|
||||
function version {
|
||||
echo "fhem-getstate, Version$VERS
|
||||
Copyright (C) 2008 Martin Fischer <m_fischer@gmx.de>
|
||||
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
|
||||
This is free software: you are free to change and redistribute it.
|
||||
There is NO WARRANTY, to the extent permitted by law.
|
||||
|
||||
Written by Martin Fischer"
|
||||
exit $1
|
||||
}
|
||||
|
||||
function longhelp {
|
||||
echo "\
|
||||
Usage: fhem-getstate [OPTION] DEVICE
|
||||
|
||||
Connect to a FHEM-Server running on 'localhost 7072' and print the status for
|
||||
the given DEVICE as a space seperated list for use in e.g. Cacti.
|
||||
|
||||
Mandatory arguments:
|
||||
-d DEVICE print the status for DEVICE as defined in FHEM
|
||||
|
||||
Optional:
|
||||
-s SERVER Resolvable Hostname or IP address of FHEM (default: localhost)
|
||||
-p PORT Listening Port of FHEM (default: 7072)
|
||||
-q quiet mode
|
||||
-h show this help
|
||||
-v show version
|
||||
|
||||
Reports bugs to <m_fischer@gmx.de>.
|
||||
"
|
||||
exit $1
|
||||
}
|
||||
|
||||
function usage {
|
||||
echo >&2 "Usage: fhem-getstate [-s <server>] [-p <port>] -d <devspec> [-h] [-v]" && exit $1;
|
||||
}
|
||||
|
||||
# check for arguments
|
||||
if (( $# <= 0 )); then
|
||||
usage 1;
|
||||
fi
|
||||
|
||||
# get options
|
||||
while getopts "s:p:d:hv" option; do
|
||||
case $option in
|
||||
d) DEV=$OPTARG;;
|
||||
h) longhelp 0;;
|
||||
p) PORT=$OPTARG;;
|
||||
s) HOST="$OPTARG";;
|
||||
v) version 0;;
|
||||
?) usage 1;;
|
||||
esac
|
||||
done
|
||||
|
||||
(echo "getstate ${DEV}" | $NCAT -w1 ${HOST} ${PORT})
|
||||
|
||||
exit 0;
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
# Siehe auch
|
||||
# http://de.wikipedia.org/wiki/Feiertage_in_Deutschland
|
||||
|
||||
1 01-01 Neujahr
|
||||
1 05-01 Tag der Arbeit
|
||||
1 10-03 Tag der deutschen Einheit
|
||||
1 12-25 1. Weihnachtstag
|
||||
1 12-26 2. Weihnachtstag
|
||||
|
||||
2 -2 Karfreitag
|
||||
2 1 Ostermontag
|
||||
2 39 Christi Himmelfahrt
|
||||
2 50 Pfingsten
|
||||
2 60 Fronleichnam
|
|
@ -1,46 +0,0 @@
|
|||
#! /bin/sh -e
|
||||
#
|
||||
#
|
||||
#
|
||||
# Written by Stefan Manteuffel
|
||||
|
||||
PATH=/bin:/usr/bin:/sbin:/usr/sbin:/usr/local/bin
|
||||
DAEMON=/usr/local/bin/fhem.pl
|
||||
PIDFILE=/var/run/fhem.pid
|
||||
|
||||
# Arguments to atd
|
||||
#
|
||||
ARGS="/etc/FHZ/fhem.cfg"
|
||||
|
||||
test -x $DAEMON || exit 0
|
||||
|
||||
. /lib/lsb/init-functions
|
||||
|
||||
case "$1" in
|
||||
start)
|
||||
echo "Starting deferred execution scheduler..."
|
||||
start-stop-daemon -b --start --quiet --pidfile $PIDFILE --startas $DAEMON -- $ARGS
|
||||
log_end_msg $?
|
||||
;;
|
||||
stop)
|
||||
log_begin_msg "Stopping deferred execution scheduler..."
|
||||
start-stop-daemon --oknodo --stop --quiet --retry 30 --pidfile $PIDFILE --name fhem.pl
|
||||
|
||||
log_end_msg $?
|
||||
;;
|
||||
force-reload|restart)
|
||||
log_begin_msg "Restarting deferred execution scheduler..."
|
||||
if start-stop-daemon --stop --quiet --retry 30 --pidfile $PIDFILE --name fhem.pl; then
|
||||
start-stop-daemon -b --start --quiet --pidfile $PIDFILE --startas $DAEMON -- $ARGS
|
||||
log_end_msg $?
|
||||
else
|
||||
log_end_msg 1
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
echo "Usage: /etc/init.d/fhem.pl {start|stop|restart|force-reload|reload}"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
exit 0
|
|
@ -1,25 +0,0 @@
|
|||
#!/bin/sh
|
||||
# by Matthias Bauer
|
||||
|
||||
case "$1" in
|
||||
start)
|
||||
echo "Starting $0"
|
||||
fhem.pl /etc/fhem/fhem.conf
|
||||
;;
|
||||
stop)
|
||||
echo "Stopping $0"
|
||||
killall fhem.pl
|
||||
;;
|
||||
status)
|
||||
cnt=`ps -ef | grep "fhem.pl" | grep -v grep | wc -l`
|
||||
if [ "$cnt" -eq "0" ] ; then
|
||||
echo "$0 is not running"
|
||||
else
|
||||
echo "$0 is running"
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
echo "Usage: $0 {start|stop|status}"
|
||||
exit 1
|
||||
esac
|
||||
exit 0
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user