First open source version.
commit
7ce577bb2f
@ -0,0 +1,8 @@
|
|||||||
|
build
|
||||||
|
dist
|
||||||
|
dist_installer
|
||||||
|
docs/*.html
|
||||||
|
docs/index.htm
|
||||||
|
*.pyc
|
||||||
|
#*#
|
||||||
|
*~
|
@ -0,0 +1,88 @@
|
|||||||
|
Net2Scripting
|
||||||
|
===
|
||||||
|
|
||||||
|
Python scripting for the Paxton Net2 SDK.
|
||||||
|
|
||||||
|
This project builds a setup file, that will install a Python scripting environment
|
||||||
|
that can communicate with Paxton Net2, using the SDK (or a direct DB connection).
|
||||||
|
|
||||||
|
|
||||||
|
### **Read this first**
|
||||||
|
The scripts in this package are currently quite inflexible, in that they assume
|
||||||
|
that packages are installed in particular directories. For future versions this
|
||||||
|
should be changed to maybe an environment setting or a 'user' file containg the
|
||||||
|
definitions. (this file should be in .gitignore)
|
||||||
|
|
||||||
|
For now, the install directories are specificly marked in the requirements.
|
||||||
|
|
||||||
|
### Requirements
|
||||||
|
The NetScripting project relies on the following:
|
||||||
|
* An x86 Windows environment (64-bit did not work). Windows7-32bit should be ok.
|
||||||
|
* DotNet 4.0
|
||||||
|
- Installing Paxton Net2 V5.x should take care of that
|
||||||
|
- The Paxton Net2 OEMClientLibrary dlls (v5.01) are included in this package.
|
||||||
|
* Python x86 3.4.4
|
||||||
|
- Is the Python environment
|
||||||
|
- Download from http://www.python.org
|
||||||
|
- Install in C:\WinPrg\Python34\ !!!
|
||||||
|
* pythonnet-2.3.0-cp34-cp34m-win32.whl
|
||||||
|
- Is Python .net extension
|
||||||
|
- Download from http://pythonnet.sourceforge.net
|
||||||
|
- Install with pip install <wheel file>
|
||||||
|
* py2exe-0.9.2.2-py33.py34-none-any.whl
|
||||||
|
- To create a python executable
|
||||||
|
- Download from http://www.py2exe.org
|
||||||
|
- Install with pip install <wheel file>
|
||||||
|
- Remove 'clr' in c:/WinPrg/Python34/lib/site-packages/py2exe/hooks.py !!!
|
||||||
|
- Note that py2exe does/did not support 3.5 and up yet
|
||||||
|
* Inno Setup 5.4.2 (higher versions most likely also work)
|
||||||
|
- To create an installer (uses the inno_script.iss)
|
||||||
|
- Download from http://www.jrsoftware.org
|
||||||
|
- Install in C:\Program Files\Inno Setup 5\ !!!
|
||||||
|
|
||||||
|
The following convenience batch files are available:
|
||||||
|
* run.bat
|
||||||
|
- To run de scripting tool while testing sample code
|
||||||
|
- It will wait after execution and run again after pressing enter (ctrl C=exit)
|
||||||
|
- Do no just click it in explorer; this will not work
|
||||||
|
- Create a sendTo shortcut to it, to execute python scripts from the explorer.
|
||||||
|
+ type "shell:sento" in the file explorer address bar to access the
|
||||||
|
SentTo folder
|
||||||
|
+ Create shortcut to run.bat and call it "Test with Net2Scripting"
|
||||||
|
* make.bat
|
||||||
|
- Creates an exe-based distro in ./dist
|
||||||
|
- You can click it in explorer
|
||||||
|
* build_installer.bat
|
||||||
|
- Requires make.bat to run first
|
||||||
|
- Creates a 'SetupNet2Scripting.exe' in ./dist_installer
|
||||||
|
- You can click it in explorer
|
||||||
|
|
||||||
|
Beware of the following:
|
||||||
|
* That the doc's generated, need to be defined in src/docgen.py
|
||||||
|
* That for py2exe, new modules must be defined in 'Net2Scripting.py',
|
||||||
|
to have py2exe include them in the exe generation.
|
||||||
|
* That the makefile.bat will show warnings (which we ignore for now):
|
||||||
|
```
|
||||||
|
8 missing Modules
|
||||||
|
------------------
|
||||||
|
? Paxton imported from net2xs
|
||||||
|
? _dbm imported from dbm.ndbm
|
||||||
|
? log4net imported from pylog4net
|
||||||
|
? multiprocessing.SimpleQueue imported from concurrent.futures.process
|
||||||
|
? netbios imported from uuid
|
||||||
|
? win32evtlog imported from logging.handlers
|
||||||
|
? win32evtlogutil imported from logging.handlers
|
||||||
|
? win32wnet imported from uuid
|
||||||
|
```
|
||||||
|
|
||||||
|
### Configuration
|
||||||
|
Runtime configuration file is: src/Net2Scripting.exe.config
|
||||||
|
(complies to the dotnet standard)
|
||||||
|
|
||||||
|
### Version updates
|
||||||
|
To update the version number, adjust the following files:
|
||||||
|
* inno_script.iss
|
||||||
|
* src/settings.py
|
||||||
|
|
||||||
|
### License
|
||||||
|
MIT
|
@ -0,0 +1,10 @@
|
|||||||
|
@echo off
|
||||||
|
del dist_installer\* /s/q > nul
|
||||||
|
|
||||||
|
rem taskkill /IM explorer.exe /F
|
||||||
|
rem cd /d %userprofile%\AppData\Local
|
||||||
|
rem del IconCache.db /a
|
||||||
|
rem start explorer.exe
|
||||||
|
|
||||||
|
"C:\Program Files\Inno Setup 5\ISCC.exe" inno_script.iss
|
||||||
|
pause
|
Binary file not shown.
After Width: | Height: | Size: 1.2 KiB |
@ -0,0 +1,121 @@
|
|||||||
|
Net2Scripting
|
||||||
|
=============
|
||||||
|
|
||||||
|
The Net2Scripting program is an add-on for the Paxton Net2 software.
|
||||||
|
It offers the possibility to run Python scripts against the Net2 SDK or
|
||||||
|
straight against the Net2 database.
|
||||||
|
Using the SDK, you can access remote Net2 servers. Using the direct database
|
||||||
|
interface, only local access is possible.
|
||||||
|
|
||||||
|
Scripting allows ad-hoc functionality to be created, without compilation.
|
||||||
|
Useful for proof of concepts, filling Net2 with test data, simple door
|
||||||
|
control, Net2 data exports, etc.
|
||||||
|
|
||||||
|
REQUIREMENTS
|
||||||
|
* A PC running a recent Windows version. The software has been tested on
|
||||||
|
WinXP(SP3), Win7 and Win8.
|
||||||
|
* Administrator rights.
|
||||||
|
* The Net2 V5.1 or higher software package.
|
||||||
|
|
||||||
|
INSTALLATION
|
||||||
|
* Run the installer program, which will create an IntoAccess folder in the
|
||||||
|
Programs menu with a shortcut to Net2Scripting, install the executable,
|
||||||
|
libraries, samples and documentation.
|
||||||
|
* Optionally edit the Net2Scripting.exe.config file that can be found in the
|
||||||
|
program installation folder.
|
||||||
|
(by default it contains a reference to a sample script
|
||||||
|
'samples/user_script.py')
|
||||||
|
|
||||||
|
USAGE
|
||||||
|
* Option 1: Edit the Net2Scripting.exe.config file, changing the
|
||||||
|
'user_script' setting to point to your script. Run the Net2Scripting
|
||||||
|
program.
|
||||||
|
* Option 2: Edit the Net2Scripting.exe.config file, adding more entries to
|
||||||
|
the 'appSetting' section. Run the Net2Scripting program adding the parameter
|
||||||
|
/k=<keyword>, where <keyword> points to the script you want to execute.
|
||||||
|
* Option 3: Run the Net2Scripting program, adding the path to the script you
|
||||||
|
wish to execute as parameter.
|
||||||
|
* If you run the Net2Scripting using the explorer, you may want the window
|
||||||
|
containing the script result to remain open. Set the appSetting
|
||||||
|
'confirm_wait' to 'true' to have it wait for the <enter> key before closing.
|
||||||
|
If you want to run a script using a scheduler, set this option to 'false'.
|
||||||
|
* By default, the application logs fairly much and both to the console as
|
||||||
|
well as to a (rolling) file. Change the root 'level' value from DEBUG to
|
||||||
|
INFO, WARN, ERROR or FATAL to make it less verbose. You can add or remove
|
||||||
|
any log4net conpatible log appenders to suit your needs.
|
||||||
|
Beware that the scripting program locks the log file, so if you run more
|
||||||
|
than one instance, it will complain about not being able to log.
|
||||||
|
|
||||||
|
DOCUMENTATION / HELP
|
||||||
|
* Some pydoc generated 'help' for the API is placed in the 'docs'
|
||||||
|
subdirectory of the installation folder. It does not hurt however to also
|
||||||
|
keep the Net2 API doc at hand while scripting.
|
||||||
|
* A number of script samples are provided, meant for those with some basic
|
||||||
|
knowledge of Python. For a full Python language reference, check out
|
||||||
|
http://www.python.org/doc/
|
||||||
|
Not all Net2 SDK functions are available through the python api yet.
|
||||||
|
If you would like to add stuff however, check out the'inheritance.py'
|
||||||
|
sample to get you started.
|
||||||
|
* The (free) Net2Query tool, available at http://www.intoaccess.com, can
|
||||||
|
also of use to get some insight on available tables, column names and
|
||||||
|
data types.
|
||||||
|
|
||||||
|
BACKGROUND
|
||||||
|
* Originally created by CRC Value.
|
||||||
|
* Maintained (sort of) by IntoAccess from July 2017 - May 2018.
|
||||||
|
* Open source from May 2018 onward.
|
||||||
|
* The package websites:
|
||||||
|
- http://www.net2scripting.nl
|
||||||
|
- https://github.com/cor-kalis/Net2Scripting
|
||||||
|
|
||||||
|
ACKNOWLEDGEMENTS
|
||||||
|
* The Net2Scripting program is created using the following libraries/tools:
|
||||||
|
- Python => http://www.python.org
|
||||||
|
- Python.Net => http://pythonnet.sourceforge.net
|
||||||
|
- Py2exe => http://www.py2exe.org
|
||||||
|
- Paxton.Net2.OEMClientLibrary => http://www.paxton.co.uk
|
||||||
|
- Inno Setup => http://www.jrsoftware.org
|
||||||
|
|
||||||
|
LICENSE
|
||||||
|
* MIT
|
||||||
|
|
||||||
|
DISCLAIMER
|
||||||
|
* You will use this software at your own risk. No responsibility is accepted for
|
||||||
|
any damage it may accidentally cause.
|
||||||
|
|
||||||
|
HISTORY
|
||||||
|
* V1.0 was published in April 2013. Initial version
|
||||||
|
* V1.1 was published in July 2013.
|
||||||
|
- Fixes a module scope issue (imported modules not recognized in functions).
|
||||||
|
- User image manipulation.
|
||||||
|
- Confirm wait, even on a sys.exit().
|
||||||
|
- Allow remote native db access.
|
||||||
|
- Net2Plus module detection using udp broadcasts.
|
||||||
|
- SQLServer detection using udp broadcasts.
|
||||||
|
- Default values for add_user and modify_user (less hassle).
|
||||||
|
* V1.2 was published in July 2013.
|
||||||
|
- Enable linecache.getline by default, so 'list' in pdb.set_trace() shows
|
||||||
|
something useful. (disable in config by setting 'linecache_enable' to false)
|
||||||
|
- Added __scripting_version__ var, to test script compatibility.
|
||||||
|
- Fixed bug in Net2XS.deactivate_user fuction.
|
||||||
|
* V1.3 was published in September 2013.
|
||||||
|
- Fix / implement acu monitoring
|
||||||
|
- Fix get_users / get_user_by_id bug
|
||||||
|
* V1.4 was published in October 2013.
|
||||||
|
- Fix password encryption on remote native database connection
|
||||||
|
* V2.0 was published in April 2014.
|
||||||
|
- Net2 V5 compatible.
|
||||||
|
* V2.1 was published in July 2014.
|
||||||
|
- Fix hard coded event and subtype bug in add_event_record.
|
||||||
|
* V2.2 was published in October 2014.
|
||||||
|
- Removed misplaced delete_image parameter in add_user
|
||||||
|
* V2.3 was published in December 2015.
|
||||||
|
- Upgraded to Python 2.7.10 and included most standard Python modules.
|
||||||
|
* V2.4 was published in August 2016.
|
||||||
|
- Added the option to use either a dotnet or python date time objects for
|
||||||
|
the add_user and modify_user methods.
|
||||||
|
- Added delete_card and get_cards methods.
|
||||||
|
* V3.0 was published in August 2017.
|
||||||
|
- Upgrade to Python 3.4.4
|
||||||
|
* V4.0 was published in May 2018.
|
||||||
|
- Made open source.
|
@ -0,0 +1,47 @@
|
|||||||
|
; Script generated by the Inno Setup Script Wizard.
|
||||||
|
; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
|
||||||
|
|
||||||
|
#define MyAppName "Net2Scripting"
|
||||||
|
#define MyAppVersion "4.0"
|
||||||
|
#define MyAppPublisher "Net2Scripting"
|
||||||
|
#define MyAppURL "http://www.net2scripting.nl/"
|
||||||
|
|
||||||
|
[Setup]
|
||||||
|
; NOTE: The value of AppId uniquely identifies this application.
|
||||||
|
; Do not use the same AppId value in installers for other applications.
|
||||||
|
; (To generate a new GUID, click Tools | Generate GUID inside the IDE.)
|
||||||
|
AppId={{3940C8D4-5D5F-43CF-B92E-4086ED2198B2}
|
||||||
|
AppName={#MyAppName}
|
||||||
|
AppVersion={#MyAppVersion}
|
||||||
|
;AppVerName={#MyAppName} {#MyAppVersion}
|
||||||
|
AppPublisher={#MyAppPublisher}
|
||||||
|
AppPublisherURL={#MyAppURL}
|
||||||
|
AppSupportURL={#MyAppURL}
|
||||||
|
AppUpdatesURL={#MyAppURL}
|
||||||
|
DefaultDirName={pf}\Paxton Access/{#MyAppName}
|
||||||
|
DefaultGroupName=Paxton Access
|
||||||
|
InfoAfterFile=.\dist\docs\README.txt
|
||||||
|
OutputDir=.\dist_installer
|
||||||
|
OutputBaseFilename=SetupNet2Scripting
|
||||||
|
SetupIconFile=.\dist\resources\Net2Scripting.ico
|
||||||
|
Compression=lzma
|
||||||
|
SolidCompression=yes
|
||||||
|
|
||||||
|
[Languages]
|
||||||
|
Name: "english"; MessagesFile: "compiler:Default.isl"
|
||||||
|
|
||||||
|
[Tasks]
|
||||||
|
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
|
||||||
|
|
||||||
|
[Files]
|
||||||
|
Source: ".\dist\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
|
||||||
|
; NOTE: Don't use "Flags: ignoreversion" on any shared system files
|
||||||
|
|
||||||
|
[Icons]
|
||||||
|
Name: "{group}\Net2Scripting"; Filename: "{app}\{#MyAppName}.exe"
|
||||||
|
Name: "{group}\Net2Scripting API"; Filename: "{app}\docs\index.htm"
|
||||||
|
Name: "{commondesktop}\Net2Scripting"; Filename: "{app}\{#MyAppName}.exe"; Tasks: desktopicon
|
||||||
|
|
||||||
|
[Run]
|
||||||
|
|
||||||
|
[UninstallRun]
|
@ -0,0 +1,21 @@
|
|||||||
|
@echo off
|
||||||
|
|
||||||
|
echo --- Cleaning...
|
||||||
|
del build\* /s/q > nul
|
||||||
|
del dist\* /s/q > nul
|
||||||
|
del docs\*.html /s/q > nul
|
||||||
|
echo --- Cleaning done
|
||||||
|
|
||||||
|
echo --- Generating docs
|
||||||
|
cd src
|
||||||
|
c:/WinPrg/Python34/python docgen.py
|
||||||
|
cd ..
|
||||||
|
echo --- Generating docs done
|
||||||
|
|
||||||
|
echo --- Building executable
|
||||||
|
cd src
|
||||||
|
c:/WinPrg/Python34/python setup.py py2exe
|
||||||
|
cd ..
|
||||||
|
echo --- Building executable done
|
||||||
|
|
||||||
|
pause
|
Binary file not shown.
After Width: | Height: | Size: 320 KiB |
@ -0,0 +1,6 @@
|
|||||||
|
@echo off
|
||||||
|
cd src
|
||||||
|
:again
|
||||||
|
c:\WinPrg\Python34\python Net2Scripting.py %1
|
||||||
|
pause
|
||||||
|
goto again
|
Binary file not shown.
@ -0,0 +1,43 @@
|
|||||||
|
<?xml version='1.0' encoding='utf-8'?>
|
||||||
|
<configuration>
|
||||||
|
<configSections>
|
||||||
|
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net" />
|
||||||
|
</configSections>
|
||||||
|
<appSettings>
|
||||||
|
<add key="user_script" value="samples/user_script.py" />
|
||||||
|
<add key="confirm_wait" value="true" />
|
||||||
|
<add key="log_stacktrace" value="true" />
|
||||||
|
<add key="enable_linecache" value="true" />
|
||||||
|
</appSettings>
|
||||||
|
<log4net>
|
||||||
|
<appender name="RollingFileAppender" type="log4net.Appender.RollingFileAppender">
|
||||||
|
<param name="File" value="${ALLUSERSPROFILE}/Application Data/IntoAccess/Net2Scripting/Net2Scripting.log"/>
|
||||||
|
<param name="AppendToFile" value="true"/>
|
||||||
|
<param name="rollingStyle" value="Size"/>
|
||||||
|
<param name="maxSizeRollBackups" value="10"/>
|
||||||
|
<param name="maximumFileSize" value="10MB"/>
|
||||||
|
<layout type="log4net.Layout.PatternLayout">
|
||||||
|
<param name="Header" value=""/>
|
||||||
|
<param name="Footer" value=""/>
|
||||||
|
<param name="ConversionPattern" value="%d [%c,%t] %-5p %m%n"/>
|
||||||
|
</layout>
|
||||||
|
</appender>
|
||||||
|
<appender name="ConsoleAppender" type="log4net.Appender.ConsoleAppender">
|
||||||
|
<layout type="log4net.Layout.PatternLayout">
|
||||||
|
<param name="Header" value="[Header]\r\n" />
|
||||||
|
<param name="Footer" value="[Footer]\r\n" />
|
||||||
|
<param name="ConversionPattern" value="%d [%c,%t] %-5p %m%n" />
|
||||||
|
</layout>
|
||||||
|
</appender>
|
||||||
|
<root>
|
||||||
|
<level value="DEBUG" />
|
||||||
|
<appender-ref ref="RollingFileAppender" />
|
||||||
|
<appender-ref ref="ConsoleAppender" />
|
||||||
|
</root>
|
||||||
|
<logger name="Paxton">
|
||||||
|
<level value="ERROR" />
|
||||||
|
<appender-ref ref="RollingFileAppender" />
|
||||||
|
<appender-ref ref="ConsoleAppender" />
|
||||||
|
</logger>
|
||||||
|
</log4net>
|
||||||
|
</configuration>
|
@ -0,0 +1,329 @@
|
|||||||
|
"""
|
||||||
|
Main program for Net2Scripting.
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import settings
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
# This is just to force py2exe to include these packages
|
||||||
|
if False == True:
|
||||||
|
import net2xs
|
||||||
|
import net2dbxs
|
||||||
|
import network
|
||||||
|
# Not required but nice to have
|
||||||
|
import abc
|
||||||
|
import aifc
|
||||||
|
import antigravity
|
||||||
|
import argparse
|
||||||
|
import ast
|
||||||
|
import asynchat
|
||||||
|
import asyncio
|
||||||
|
import asyncore
|
||||||
|
import base64
|
||||||
|
import bdb
|
||||||
|
import binhex
|
||||||
|
import bisect
|
||||||
|
import bz2
|
||||||
|
import cProfile
|
||||||
|
import calendar
|
||||||
|
import cgi
|
||||||
|
import cgitb
|
||||||
|
import chunk
|
||||||
|
import cmd
|
||||||
|
import code
|
||||||
|
import codecs
|
||||||
|
import codeop
|
||||||
|
import collections
|
||||||
|
import colorsys
|
||||||
|
import compileall
|
||||||
|
import concurrent
|
||||||
|
import configparser
|
||||||
|
import contextlib
|
||||||
|
import copy
|
||||||
|
import copyreg
|
||||||
|
# import crypt
|
||||||
|
import csv
|
||||||
|
import ctypes
|
||||||
|
# import curses
|
||||||
|
import datetime
|
||||||
|
# import dbm
|
||||||
|
import decimal
|
||||||
|
import difflib
|
||||||
|
import dis
|
||||||
|
import distutils
|
||||||
|
import doctest
|
||||||
|
import threading
|
||||||
|
import email
|
||||||
|
import encodings
|
||||||
|
# import ensurepip
|
||||||
|
import enum
|
||||||
|
import filecmp
|
||||||
|
import fileinput
|
||||||
|
import fnmatch
|
||||||
|
import formatter
|
||||||
|
import fractions
|
||||||
|
import ftplib
|
||||||
|
import functools
|
||||||
|
import genericpath
|
||||||
|
import getopt
|
||||||
|
import getpass
|
||||||
|
import gettext
|
||||||
|
import glob
|
||||||
|
import gzip
|
||||||
|
import hashlib
|
||||||
|
import heapq
|
||||||
|
import hmac
|
||||||
|
import html
|
||||||
|
import http
|
||||||
|
import idlelib
|
||||||
|
import imaplib
|
||||||
|
import imghdr
|
||||||
|
import imp
|
||||||
|
import importlib
|
||||||
|
import inspect
|
||||||
|
import io
|
||||||
|
import ipaddress
|
||||||
|
import json
|
||||||
|
import keyword
|
||||||
|
import lib2to3
|
||||||
|
import linecache
|
||||||
|
import locale
|
||||||
|
import logging
|
||||||
|
import lzma
|
||||||
|
# import macpath
|
||||||
|
# import macurl2path
|
||||||
|
import mailbox
|
||||||
|
import mailcap
|
||||||
|
import mimetypes
|
||||||
|
import modulefinder
|
||||||
|
import msilib
|
||||||
|
# import multiprocessing
|
||||||
|
import netrc
|
||||||
|
import nntplib
|
||||||
|
import ntpath
|
||||||
|
import nturl2path
|
||||||
|
import numbers
|
||||||
|
import opcode
|
||||||
|
import operator
|
||||||
|
import optparse
|
||||||
|
import os
|
||||||
|
import pathlib
|
||||||
|
import pdb
|
||||||
|
import pickle
|
||||||
|
import pickletools
|
||||||
|
import pipes
|
||||||
|
import pkgutil
|
||||||
|
import platform
|
||||||
|
import plistlib
|
||||||
|
import poplib
|
||||||
|
import posixpath
|
||||||
|
import pprint
|
||||||
|
import profile
|
||||||
|
import pstats
|
||||||
|
import pty
|
||||||
|
import pyclbr
|
||||||
|
import pydoc
|
||||||
|
import pydoc_data
|
||||||
|
import queue
|
||||||
|
import quopri
|
||||||
|
import random
|
||||||
|
import re
|
||||||
|
import reprlib
|
||||||
|
import rlcompleter
|
||||||
|
import runpy
|
||||||
|
import sched
|
||||||
|
import selectors
|
||||||
|
import shelve
|
||||||
|
import shlex
|
||||||
|
import shutil
|
||||||
|
import site
|
||||||
|
# import smtpd
|
||||||
|
import smtplib
|
||||||
|
import sndhdr
|
||||||
|
import socket
|
||||||
|
import socketserver
|
||||||
|
import sqlite3
|
||||||
|
# import compile
|
||||||
|
# import constants
|
||||||
|
# import parse
|
||||||
|
import ssl
|
||||||
|
import stat
|
||||||
|
import statistics
|
||||||
|
import string
|
||||||
|
import stringprep
|
||||||
|
import struct
|
||||||
|
import subprocess
|
||||||
|
import sunau
|
||||||
|
import symbol
|
||||||
|
import symtable
|
||||||
|
import sysconfig
|
||||||
|
import tabnanny
|
||||||
|
import tarfile
|
||||||
|
import telnetlib
|
||||||
|
import tempfile
|
||||||
|
import test
|
||||||
|
import textwrap
|
||||||
|
import this
|
||||||
|
import threading
|
||||||
|
import timeit
|
||||||
|
import tkinter
|
||||||
|
import token
|
||||||
|
import tokenize
|
||||||
|
import trace
|
||||||
|
import traceback
|
||||||
|
import tracemalloc
|
||||||
|
import tty
|
||||||
|
import turtle
|
||||||
|
import turtledemo
|
||||||
|
import types
|
||||||
|
import unittest
|
||||||
|
import urllib
|
||||||
|
import uu
|
||||||
|
import uuid
|
||||||
|
import venv
|
||||||
|
import warnings
|
||||||
|
import wave
|
||||||
|
import weakref
|
||||||
|
import webbrowser
|
||||||
|
import wsgiref
|
||||||
|
import xdrlib
|
||||||
|
import xml
|
||||||
|
import xmlrpc
|
||||||
|
import zipfile
|
||||||
|
|
||||||
|
|
||||||
|
# Setup logging first
|
||||||
|
from pylog4net import Log4Net
|
||||||
|
try:
|
||||||
|
Log4Net.read_config(settings.CONFIG_FILE)
|
||||||
|
except Exception as e:
|
||||||
|
print('Log error: %s' % (str(e)))
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
from config import Config
|
||||||
|
try:
|
||||||
|
cfg = Config(settings.CONFIG_FILE)
|
||||||
|
except Exception as e:
|
||||||
|
print('Config error: %s' % (str(e)))
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Running as py2exe: check if linecache should be enabled
|
||||||
|
# if hasattr(sys, 'frozen'):
|
||||||
|
# if cfg.get("enable_linecache", True, bool):
|
||||||
|
# # Monkey patch it back to the original value
|
||||||
|
# import linecache
|
||||||
|
# linecache.getline = linecache.orig_getline
|
||||||
|
# del linecache
|
||||||
|
|
||||||
|
# Log4net logger
|
||||||
|
logger = Log4Net.get_logger('Net2Scripting')
|
||||||
|
|
||||||
|
|
||||||
|
def run(script_file):
|
||||||
|
"""Run script file
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
if not os.path.isfile(script_file):
|
||||||
|
raise Exception("User script '%s' does not exist!" % (script_file))
|
||||||
|
|
||||||
|
# Get script dir and add it to the module search path
|
||||||
|
sys.path.insert(0, os.path.dirname(script_file))
|
||||||
|
|
||||||
|
print("Press '<ctrl> C' to quit...")
|
||||||
|
logger.Debug("Calling user script '%s'" % (script_file))
|
||||||
|
try:
|
||||||
|
# Strip Net2Scripting param from arguments
|
||||||
|
sys.argv = sys.argv[1:]
|
||||||
|
# Explicitly provide dict, to prevent scope issues
|
||||||
|
with open(script_file) as f:
|
||||||
|
code = compile(f.read(), script_file, 'exec')
|
||||||
|
exec(code,
|
||||||
|
{"__name__": "__main__",
|
||||||
|
"__file__": script_file,
|
||||||
|
"__scripting_version__": settings.VERSION})
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
logger.Debug("User interrupt")
|
||||||
|
|
||||||
|
except Exception as ex:
|
||||||
|
|
||||||
|
msg = "Fatal error: " + str(ex)
|
||||||
|
if cfg.get("log_stacktrace", default=False, vtype=bool):
|
||||||
|
msg += "\n" + traceback.format_exc()
|
||||||
|
logger.Fatal(msg)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
|
||||||
|
def run_key(script_key):
|
||||||
|
"""Run file referenced by setting keyword
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
script_key = script_key.strip()
|
||||||
|
if not script_key:
|
||||||
|
raise "The provided script keyword is empty!"
|
||||||
|
|
||||||
|
# Fetch setting from config
|
||||||
|
cfg.check_required([(script_key, str)])
|
||||||
|
|
||||||
|
return run(cfg.get(script_key))
|
||||||
|
|
||||||
|
except Exception as ex:
|
||||||
|
logger.Fatal("Fatal error: " + str(ex))
|
||||||
|
return 1
|
||||||
|
|
||||||
|
|
||||||
|
def confirm_wait():
|
||||||
|
"""Wait for user confirmation
|
||||||
|
"""
|
||||||
|
# Fetch setting from config
|
||||||
|
wait = cfg.get("confirm_wait", default=False, vtype=bool)
|
||||||
|
if wait:
|
||||||
|
print()
|
||||||
|
input("Press enter to continue: ")
|
||||||
|
|
||||||
|
|
||||||
|
# Main program entry
|
||||||
|
if __name__ == '__main__':
|
||||||
|
|
||||||
|
ret_val = 0
|
||||||
|
|
||||||
|
try:
|
||||||
|
if len(sys.argv) > 1:
|
||||||
|
arg = sys.argv[1]
|
||||||
|
|
||||||
|
if arg == "/?":
|
||||||
|
print()
|
||||||
|
print("Net2Scripting V%s, by Net2 Solutions" % (settings.VERSION))
|
||||||
|
print()
|
||||||
|
print(" /? = help")
|
||||||
|
print(" /k:keyword = run script referenced by appSettings keyword")
|
||||||
|
print(" <script file path> = run given script")
|
||||||
|
print()
|
||||||
|
print(" By default, the script referenced by the 'user_script' setting is run.")
|
||||||
|
print(" Settings are located in the 'Net2Scripting.exe.config' file.")
|
||||||
|
print(" Also modify this file if you prefer different log settings.")
|
||||||
|
print
|
||||||
|
ret_val = 1
|
||||||
|
|
||||||
|
# Run by keyword
|
||||||
|
elif arg.startswith("/k:"):
|
||||||
|
ret_val = run_key(arg.split(":", 1)[1])
|
||||||
|
|
||||||
|
# Run as file
|
||||||
|
elif not arg.startswith("/"):
|
||||||
|
ret_val = run(arg)
|
||||||
|
|
||||||
|
# Unknown option
|
||||||
|
else:
|
||||||
|
print("Unknown parameter: " + arg)
|
||||||
|
ret_val = 1
|
||||||
|
else:
|
||||||
|
# Run as default
|
||||||
|
ret_val = run_key("user_script")
|
||||||
|
|
||||||
|
finally:
|
||||||
|
# Wait for user confirmation when required
|
||||||
|
confirm_wait()
|
||||||
|
|
||||||
|
# Return with value
|
||||||
|
sys.exit(ret_val)
|
@ -0,0 +1,99 @@
|
|||||||
|
"""
|
||||||
|
Module to read dot type config settings from python
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
|
from xml.etree import ElementTree as ET
|
||||||
|
|
||||||
|
|
||||||
|
class ConfigError(Exception):
|
||||||
|
"""Config exception class
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Config(object):
|
||||||
|
"""Class for reading and writing to the config file
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, config_file):
|
||||||
|
"""Read config file
|
||||||
|
"""
|
||||||
|
self.config_file = config_file
|
||||||
|
|
||||||
|
self._app_settings = {}
|
||||||
|
|
||||||
|
if not os.path.isfile(config_file):
|
||||||
|
raise ConfigError('Failed to find config file "%s"' % config_file)
|
||||||
|
|
||||||
|
# Read document
|
||||||
|
tree = ET.ElementTree()
|
||||||
|
|
||||||
|
tree.parse(config_file)
|
||||||
|
|
||||||
|
# Obtain appSettings
|
||||||
|
element = tree.find('appSettings')
|
||||||
|
if element is None:
|
||||||
|
raise ConfigError(
|
||||||
|
'Required "appSettings" section is missing in config file' %
|
||||||
|
self.config_file)
|
||||||
|
for item in element.findall('add'):
|
||||||
|
if 'key' in item.attrib and 'value' in item.attrib:
|
||||||
|
key = item.attrib['key']
|
||||||
|
val = item.attrib['value']
|
||||||
|
self._app_settings[key] = val
|
||||||
|
else:
|
||||||
|
raise ConfigError(
|
||||||
|
'Encountered appSetting with missing attributes')
|
||||||
|
|
||||||
|
def _parse_as_datetime(self, val):
|
||||||
|
"""Attempt to parse val as datetime
|
||||||
|
"""
|
||||||
|
if re.match(r'\d+:\d+:\d+', val):
|
||||||
|
fmt = '%H:%M:%S'
|
||||||
|
elif re.match(r'\d\d\d\d-\d+-\d+', val):
|
||||||
|
fmt = '%Y-%m-%d'
|
||||||
|
elif re.match(r'\d\d\d\d-\d+-\d+ \d+:\d+:\d+', val):
|
||||||
|
fmt = '%Y-%m-%d %H:%M:%S'
|
||||||
|
else:
|
||||||
|
raise ValueError('Illegal datetime format')
|
||||||
|
|
||||||
|
return datetime.strptime(val, fmt)
|
||||||
|
|
||||||
|
def check_required(self, item_list):
|
||||||
|
for key, vtype in item_list:
|
||||||
|
val = self._app_settings.get(key)
|
||||||
|
if val is None:
|
||||||
|
raise ConfigError('Required setting for "%s" is missing' % key)
|
||||||
|
if vtype != str:
|
||||||
|
try:
|
||||||
|
if vtype == datetime:
|
||||||
|
self._parse_as_datetime(val)
|
||||||
|
else:
|
||||||
|
vtype(val)
|
||||||
|
except ValueError:
|
||||||
|
raise ConfigError(
|
||||||
|
'Required setting for "%s" (%s), is no %s type' %
|
||||||
|
(key, val, vtype.__name__))
|
||||||
|
|
||||||
|
def get(self, key, default=None, vtype=str):
|
||||||
|
"""Obtain typecasted value
|
||||||
|
"""
|
||||||
|
val = self._app_settings.get(key, default)
|
||||||
|
if val is None:
|
||||||
|
return None
|
||||||
|
if vtype == str:
|
||||||
|
return val
|
||||||
|
if vtype == datetime:
|
||||||
|
try:
|
||||||
|
return self._parse_as_datetime(val)
|
||||||
|
except ValueError:
|
||||||
|
return default
|
||||||
|
if vtype == bool:
|
||||||
|
return val.lower() == 'true'
|
||||||
|
try:
|
||||||
|
return vtype(val)
|
||||||
|
except ValueError:
|
||||||
|
return default
|
@ -0,0 +1,78 @@
|
|||||||
|
"""
|
||||||
|
Helper module to generate the pydoc based
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
import settings
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from glob import glob
|
||||||
|
from pydoc import writedoc
|
||||||
|
|
||||||
|
# So we can find the libs
|
||||||
|
sys.path.append('libs')
|
||||||
|
|
||||||
|
PACKAGES = ["net2base",
|
||||||
|
"net2xs", "net2xs.conversions", "net2xs.deftypes",
|
||||||
|
"net2dbxs",
|
||||||
|
"network", "network.net2plus", "network.sqlserver"]
|
||||||
|
|
||||||
|
INDEX = "index.htm"
|
||||||
|
|
||||||
|
|
||||||
|
def read_file(file_name):
|
||||||
|
"""Read file content
|
||||||
|
"""
|
||||||
|
with open(file_name) as f:
|
||||||
|
return f.read()
|
||||||
|
|
||||||
|
|
||||||
|
def alter_doc(content):
|
||||||
|
"""Change doc content
|
||||||
|
- Strip reference to local python file
|
||||||
|
- Replace index with our own index file
|
||||||
|
"""
|
||||||
|
pos1 = content.find("<a href=\"file:///")
|
||||||
|
if pos1 >= 0:
|
||||||
|
pos2 = content.find("</a>", pos1)
|
||||||
|
content = content[:pos1] + content[pos2 + len("</a>"):]
|
||||||
|
|
||||||
|
content = content.replace('<a href=".">index</a>',
|
||||||
|
'<a href="index.htm">index</a>')
|
||||||
|
return content
|
||||||
|
|
||||||
|
|
||||||
|
def alter_index(content):
|
||||||
|
"""Change index content
|
||||||
|
- Replace version place holder with actual version
|
||||||
|
"""
|
||||||
|
content = content.replace('#VERSION#', settings.VERSION)
|
||||||
|
return content
|
||||||
|
|
||||||
|
|
||||||
|
def write_file(file_name, content):
|
||||||
|
"""Write content as file
|
||||||
|
"""
|
||||||
|
with open(file_name, "w") as f:
|
||||||
|
f.write(content)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
|
||||||
|
# Create docs
|
||||||
|
for p in PACKAGES:
|
||||||
|
writedoc(p)
|
||||||
|
|
||||||
|
# For each html, generated into src, write altered to docs
|
||||||
|
for src in glob("*.html"):
|
||||||
|
dest = '../docs/' + src
|
||||||
|
content = read_file(src)
|
||||||
|
content = alter_doc(content)
|
||||||
|
|
||||||
|
write_file(dest, content)
|
||||||
|
os.remove(src)
|
||||||
|
|
||||||
|
# Write index with correct version to docs
|
||||||
|
content = read_file(INDEX)
|
||||||
|
dest = '../docs/' + INDEX
|
||||||
|
content = alter_index(content)
|
||||||
|
write_file(dest, content)
|
@ -0,0 +1,29 @@
|
|||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<style type="text/css">
|
||||||
|
body {
|
||||||
|
color: #142313;
|
||||||
|
font-family: Tahoma, sans-serif;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: normal;
|
||||||
|
text-align: left;
|
||||||
|
color: darkSlateGrey;
|
||||||
|
background-color: #f0f0f8;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<center>
|
||||||
|
<h2>Net2Scripting V#VERSION# API documentation</h2>
|
||||||
|
<br>
|
||||||
|
<img src="Net2Scripting.png">
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
<p><a href="net2xs.html">Net2XS for access through the SDK</a></p>
|
||||||
|
<p><a href="net2dbxs.html">Net2DBXS for direct database access</a></p>
|
||||||
|
<p><a href="network.html">Network tools related to Net2 access</a></p>
|
||||||
|
<p><a href="README.txt">Application README</a></p>
|
||||||
|
<br>
|
||||||
|
<p><h5><a href="http://www.net2scripting.nl">www.net2scripting.nl</a></h5></p>
|
||||||
|
</body>
|
||||||
|
</html>
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,43 @@
|
|||||||
|
"""
|
||||||
|
Module offering base class for net2 access
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class Net2Base(object):
|
||||||
|
"""Net2 access base class
|
||||||
|
"""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def safe_get_row_val(cls, row, col):
|
||||||
|
"""Null aware fetching of row data
|
||||||
|
|
||||||
|
Returns None if the database column is NULL
|
||||||
|
"""
|
||||||
|
|
||||||
|
if row.IsNull(col):
|
||||||
|
return None
|
||||||
|
return row[col]
|
||||||
|
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def dataset_to_str(cls, dataset):
|
||||||
|
"""Convert dataset to string presentation
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not dataset:
|
||||||
|
return "None"
|
||||||
|
if dataset.Tables.Count < 1:
|
||||||
|
return "Empty"
|
||||||
|
|
||||||
|
res = []
|
||||||
|
for table in dataset.Tables:
|
||||||
|
res.append("Table: %s" % (table.TableName))
|
||||||
|
cols = []
|
||||||
|
for col in table.Columns:
|
||||||
|
cols.append(col.ColumnName)
|
||||||
|
for row in table.Rows:
|
||||||
|
row_res = []
|
||||||
|
for i in range(len(cols)):
|
||||||
|
row_res.append("%s=%s" % (cols[i], row.get_Item(i)))
|
||||||
|
res.append(", ".join(row_res))
|
||||||
|
return "\n".join(res)
|
@ -0,0 +1,146 @@
|
|||||||
|
"""
|
||||||
|
The module that offers direct access to the Paxton Net2 database,
|
||||||
|
not using the SDK.
|
||||||
|
"""
|
||||||
|
import clr
|
||||||
|
|
||||||
|
from threading import RLock
|
||||||
|
|
||||||
|
clr.AddReference('System.Data')
|
||||||
|
from System.Data.SqlClient import SqlConnection, SqlDataAdapter, SqlCommand
|
||||||
|
from System.Data import DataSet, CommandType, ParameterDirection
|
||||||
|
|
||||||
|
from net2base import Net2Base
|
||||||
|
from pylog4net import Log4Net
|
||||||
|
|
||||||
|
|
||||||
|
class Net2DBXSException(Exception):
|
||||||
|
"""Exception class for net2 database xs
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class DBCommand(object):
|
||||||
|
"""With wrapper for SqlCommand
|
||||||
|
"""
|
||||||
|
def __init__(self, query, con):
|
||||||
|
self._cmd = SqlCommand(query, con)
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
return self._cmd
|
||||||
|
|
||||||
|
def __exit__(self, type, value, traceback):
|
||||||
|
if (self._cmd):
|
||||||
|
self._cmd.Dispose()
|
||||||
|
|
||||||
|
|
||||||
|
class Net2DBXS(Net2Base):
|
||||||
|
"""Net2 direct database access class
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Class variables
|
||||||
|
_logger = Log4Net.get_logger('Net2DBXS')
|
||||||
|
_lock = RLock()
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
"""Class constructor
|
||||||
|
"""
|
||||||
|
self._con = None
|
||||||
|
self._connected = False
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
"""With enter
|
||||||
|
"""
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __exit__(self, type, value, traceback):
|
||||||
|
"""With exit
|
||||||
|
"""
|
||||||
|
self.dispose()
|
||||||
|
|
||||||
|
def _check_connect(self):
|
||||||
|
"""Check database connection
|
||||||
|
Raises Net2DBXSException if not connected.
|
||||||
|
"""
|
||||||
|
if not self._connected:
|
||||||
|
raise Net2DBXSException("Not connected to the database")
|
||||||
|
|
||||||
|
def connect(self,
|
||||||
|
user_name="sdk_user",
|
||||||
|
password="E56ABED4-2918-44F9-A110-71B61B47142A",
|
||||||
|
db='Net2',
|
||||||
|
server=None):
|
||||||
|
"""Connect to database
|
||||||
|
|
||||||
|
If the server parameter is None, named pipes are used.
|
||||||
|
For remote connections, the server parameter should contain the
|
||||||
|
windows name.
|
||||||
|
"""
|
||||||
|
self.dispose()
|
||||||
|
|
||||||
|
with Net2DBXS._lock:
|
||||||
|
if not server:
|
||||||
|
Net2DBXS._logger.Debug('Connecting through named pipes')
|
||||||
|
self._con = SqlConnection(
|
||||||
|
r"server=\\.\pipe\MSSQL$NET2\sql\query;database=%s;uid=%s;password=%s" %
|
||||||
|
(db, user_name, password))
|
||||||
|
else:
|
||||||
|
Net2DBXS._logger.Debug('Connecting over the network')
|
||||||
|
self._con = SqlConnection(
|
||||||
|
r"server=%s\NET2;database=%s;uid=%s;password=%s" %
|
||||||
|
(server, db, user_name, password))
|
||||||
|
self._con.Open()
|
||||||
|
self._connected = True
|
||||||
|
Net2DBXS._logger.Debug('Connected')
|
||||||
|
|
||||||
|
def query_db(self, query):
|
||||||
|
"""Perform database query and return data in dataset
|
||||||
|
|
||||||
|
Returns a dataset.
|
||||||
|
"""
|
||||||
|
self._check_connect()
|
||||||
|
|
||||||
|
with DBCommand(query, self._con) as cmd:
|
||||||
|
adapter = SqlDataAdapter()
|
||||||
|
adapter.SelectCommand = cmd
|
||||||
|
data = DataSet()
|
||||||
|
adapter.Fill(data)
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
def run_stored_procedure(self, sp_name, **params):
|
||||||
|
"""Run stored procedured
|
||||||
|
|
||||||
|
Returns the stored procedure result value.
|
||||||
|
"""
|
||||||
|
with DBCommand(sp_name, self._con) as cmd:
|
||||||
|
cmd.CommandType = CommandType.StoredProcedure
|
||||||
|
# Add parameters
|
||||||
|
for name, value in params.iteritems():
|
||||||
|
cmd.Parameters.AddWithValue("@%s" % name, value)
|
||||||
|
# Add return code
|
||||||
|
cmd.Parameters.AddWithValue("@result", -1)
|
||||||
|
cmd.Parameters["@result"].Direction = ParameterDirection.ReturnValue
|
||||||
|
# Execute
|
||||||
|
cmd.ExecuteNonQuery()
|
||||||
|
return cmd.Parameters["@result"].Value
|
||||||
|
|
||||||
|
def get_all_tables(self):
|
||||||
|
"""Convenience function to retrieve all known tables / views
|
||||||
|
|
||||||
|
Returns a dataset.
|
||||||
|
"""
|
||||||
|
return self.query_db(
|
||||||
|
"select * from INFORMATION_SCHEMA.Tables order by TABLE_NAME")
|
||||||
|
|
||||||
|
def dispose(self):
|
||||||
|
"""Dispose database connection
|
||||||
|
"""
|
||||||
|
with Net2DBXS._lock:
|
||||||
|
if self._con:
|
||||||
|
try:
|
||||||
|
self._con.Close()
|
||||||
|
self._con = None
|
||||||
|
Net2DBXS._logger.Debug('Disposed')
|
||||||
|
except:
|
||||||
|
pass
|
@ -0,0 +1,999 @@
|
|||||||
|
"""
|
||||||
|
The module that offers access to the Paxton Net2 server
|
||||||
|
"""
|
||||||
|
import clr
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import settings
|
||||||
|
from net2xs.conversions import date_time_to_net, flex_date_time_to_net, \
|
||||||
|
time_zones_to_py, access_levels_to_py, access_level_detail_to_py
|
||||||
|
from datetime import datetime
|
||||||
|
from net2base import Net2Base
|
||||||
|
from pylog4net import Log4Net
|
||||||
|
from threading import RLock
|
||||||
|
from System import Array
|
||||||
|
from System.Data import DataSet
|
||||||
|
from System.Reflection import Assembly
|
||||||
|
|
||||||
|
# Minimal required net2 version
|
||||||
|
MIN_NET2_VERSION = 501
|
||||||
|
# Paxton assembly
|
||||||
|
PAXTON_ASSEMBLY = 'Paxton.Net2.OEMClientLibrary'
|
||||||
|
|
||||||
|
|
||||||
|
def readable_min_version():
|
||||||
|
"""Show a user readable minimum version
|
||||||
|
"""
|
||||||
|
major = MIN_NET2_VERSION / 100
|
||||||
|
minor = MIN_NET2_VERSION - major * 100
|
||||||
|
return "%d.%d" % (major, minor)
|
||||||
|
|
||||||
|
|
||||||
|
class Net2XSException(Exception):
|
||||||
|
"""Exception class for net2 xs
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
# The code below is required to determine if the already installed
|
||||||
|
# Net2 version can be used, or that the packaged version is required.
|
||||||
|
try:
|
||||||
|
# Obtain paxton assembly reference
|
||||||
|
asm = Assembly.LoadWithPartialName(PAXTON_ASSEMBLY)
|
||||||
|
|
||||||
|
# Found: check the version
|
||||||
|
if asm:
|
||||||
|
ver = asm.GetName().Version
|
||||||
|
if ver.Major * 100 + ver.Minor < MIN_NET2_VERSION:
|
||||||
|
raise Net2XSException(
|
||||||
|
'Only Net2 V%s or higher is supported' %
|
||||||
|
readable_min_version())
|
||||||
|
|
||||||
|
# Not found: enable the packaged paxton libs
|
||||||
|
else:
|
||||||
|
# Add path lib path to search path
|
||||||
|
PAXTON_LIB_DIR = os.path.join(settings.LIB_DIR, 'paxton')
|
||||||
|
if PAXTON_LIB_DIR not in sys.path:
|
||||||
|
sys.path.append(PAXTON_LIB_DIR)
|
||||||
|
|
||||||
|
try:
|
||||||
|
Assembly.LoadWithPartialName(PAXTON_ASSEMBLY)
|
||||||
|
clr.AddReference(PAXTON_ASSEMBLY)
|
||||||
|
from Paxton.Net2.OemClientLibrary import OemClient as OC
|
||||||
|
from Paxton.Net2.OemClientLibrary import AccessLevelDetailSet
|
||||||
|
from Paxton.Net2.OemClientLibrary import TimezonesSet
|
||||||
|
from Paxton.Net2.OemClientLibrary import EventViewEnums
|
||||||
|
except:
|
||||||
|
raise Net2XSException('Failed to load the library')
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
Log4Net.get_logger('Net2XS').Fatal('Paxton error: %s' % str(e))
|
||||||
|
sys.exit(1)
|
||||||
|
# end of paxton loading
|
||||||
|
|
||||||
|
|
||||||
|
class Net2XS(Net2Base):
|
||||||
|
"""Net2 Access class
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Class variables
|
||||||
|
_logger = Log4Net.get_logger('Net2XS')
|
||||||
|
_lock = RLock()
|
||||||
|
|
||||||
|
def __init__(self, host='localhost', port=8025):
|
||||||
|
"""Class constructor
|
||||||
|
"""
|
||||||
|
self._client = None
|
||||||
|
self._host = host
|
||||||
|
self._port = port
|
||||||
|
self._connected = False
|
||||||
|
self._on_acu_event = None
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
"""With enter handler
|
||||||
|
"""
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __exit__(self, type, value, traceback):
|
||||||
|
"""With exit handler
|
||||||
|
"""
|
||||||
|
self.dispose()
|
||||||
|
|
||||||
|
def _check_client(self):
|
||||||
|
"""Check is client connection is valid
|
||||||
|
"""
|
||||||
|
if not self._client or not self._connected:
|
||||||
|
raise Net2XSException('Not connected')
|
||||||
|
|
||||||
|
def authenticate(self, user_id, password):
|
||||||
|
"""Authenticate to Net2
|
||||||
|
"""
|
||||||
|
with Net2XS._lock:
|
||||||
|
# Save for re authentication
|
||||||
|
self._user_id = user_id
|
||||||
|
self._password = password
|
||||||
|
|
||||||
|
self.dispose()
|
||||||
|
|
||||||
|
Net2XS._logger.Debug('Connecting to net2 server on %s:%d' %
|
||||||
|
(self._host, self._port))
|
||||||
|
self._client = OC(self._host, self._port)
|
||||||
|
methods = self._client.AuthenticateUser(user_id, password)
|
||||||
|
if not methods:
|
||||||
|
raise Net2XSException('Authentication failed: ' +
|
||||||
|
self._client.LastErrorMessage)
|
||||||
|
else:
|
||||||
|
self._connected = True
|
||||||
|
|
||||||
|
# Add disconnect reconnect handlers
|
||||||
|
self._client.Net2ServerReconnected += (
|
||||||
|
OC.Net2ServerReconnectedHandler(self._reconnected))
|
||||||
|
self._client.Net2ServerDisconnected += (
|
||||||
|
OC.Net2ServerDisconnectedHandler(self._disconnected))
|
||||||
|
|
||||||
|
# Add acu event handler
|
||||||
|
self._client.Net2AccessEvent += (
|
||||||
|
OC.Net2AcuEventHandler(self._acu_event))
|
||||||
|
|
||||||
|
Net2XS._logger.Debug('Authenticated')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def client_version(self):
|
||||||
|
"""Client version
|
||||||
|
"""
|
||||||
|
asm = Assembly.GetAssembly(OC)
|
||||||
|
ver = asm.GetName().Version
|
||||||
|
return (ver.Major, ver.Minor)
|
||||||
|
|
||||||
|
def query_db(self, query):
|
||||||
|
"""Perform a db query
|
||||||
|
|
||||||
|
Returns a dataset
|
||||||
|
"""
|
||||||
|
with Net2XS._lock:
|
||||||
|
self._check_client()
|
||||||
|
return self._client.QueryDb(query)
|
||||||
|
|
||||||
|
def are_doors_being_synchronised(self):
|
||||||
|
"""Return if synchronization is taking place
|
||||||
|
|
||||||
|
Returns a boolean
|
||||||
|
"""
|
||||||
|
with Net2XS._lock:
|
||||||
|
self._check_client()
|
||||||
|
return self._client.AreDoorsBeingSynchronised()
|
||||||
|
|
||||||
|
def get_doors(self, device_address=-1):
|
||||||
|
"""Get all doors (ViewDoors)
|
||||||
|
|
||||||
|
Device_address is an optional integer
|
||||||
|
Returns a dataset
|
||||||
|
"""
|
||||||
|
with Net2XS._lock:
|
||||||
|
self._check_client()
|
||||||
|
if device_address < 0:
|
||||||
|
return self._client.ViewDoors().DoorsDataSource
|
||||||
|
else:
|
||||||
|
return self._client.ViewDoors(device_address).DoorsDataSource
|
||||||
|
|
||||||
|
def get_door_name(self, device_address):
|
||||||
|
"""Obtain door name
|
||||||
|
|
||||||
|
Returns a string
|
||||||
|
"""
|
||||||
|
with Net2XS._lock:
|
||||||
|
self._check_client()
|
||||||
|
dataset = self._client.ViewDoors(device_address).DoorsDataSource
|
||||||
|
if (not dataset or
|
||||||
|
dataset.Tables.Count < 1 or
|
||||||
|
dataset.Tables[0].Rows.Count == 0):
|
||||||
|
return None
|
||||||
|
return dataset.Tables[0].Rows[0].Name
|
||||||
|
|
||||||
|
def get_departments(self):
|
||||||
|
"""Get all departments (ViewDepartments)
|
||||||
|
|
||||||
|
Returns a dataset
|
||||||
|
"""
|
||||||
|
with Net2XS._lock:
|
||||||
|
self._check_client()
|
||||||
|
return self._client.ViewDepartments().DepartmentsDataSource
|
||||||
|
|
||||||
|
def close_door(self, device_address):
|
||||||
|
"""Close a door
|
||||||
|
|
||||||
|
Returns True on success
|
||||||
|
"""
|
||||||
|
with Net2XS._lock:
|
||||||
|
self._check_client()
|
||||||
|
return self._client.CloseDoor(device_address)
|
||||||
|
|
||||||
|
def hold_door_open(self, device_address):
|
||||||
|
"""Hold a door open
|
||||||
|
|
||||||
|
Returns True on success
|
||||||
|
"""
|
||||||
|
with Net2XS._lock:
|
||||||
|
self._check_client()
|
||||||
|
return self._client.HoldDoorOpen(device_address)
|
||||||
|
|
||||||
|
def control_door(self, device_address, relay, function, door_open_time, led_flash):
|
||||||
|
"""Control a door
|
||||||
|
|
||||||
|
Relay is 0 or 1, for relay 1 or 2.
|
||||||
|
Function 0 Close, 1 Timed open, 2 Hold Open.
|
||||||
|
Door_open_time in ms.
|
||||||
|
Led_flash see Net2 API.
|
||||||
|
Returns True on success.
|
||||||
|
"""
|
||||||
|
with Net2XS._lock:
|
||||||
|
self._check_client()
|
||||||
|
return self._client.ControlDoorEx(
|
||||||
|
device_address, relay, function, door_open_time, led_flash)
|
||||||
|
|
||||||
|
def get_department_name(self, department_id):
|
||||||
|
"""Get department name by id
|
||||||
|
|
||||||
|
Returns a string or None if department was not found
|
||||||
|
"""
|
||||||
|
dataset = self.query_db(
|
||||||
|
'select DepartmentName from sdk.Departments'
|
||||||
|
' where DepartmentID=%d' % department_id)
|
||||||
|
if (not dataset or
|
||||||
|
dataset.Tables.Count < 1 or
|
||||||
|
dataset.Tables[0].Rows.Count == 0):
|
||||||
|
return None
|
||||||
|
return dataset.Tables[0].Rows[0][0]
|
||||||
|
|
||||||
|
def get_users_ex(self, name=None):
|
||||||
|
"""Get users, from the UsersEx view.
|
||||||
|
|
||||||
|
Name param is optional tuple (first_name, sur_name)
|
||||||
|
Returns a dataset
|
||||||
|
"""
|
||||||
|
query = 'select * from sdk.UsersEx'
|
||||||
|
if name and len(name) == 2:
|
||||||
|
first_name, sur_name = name
|
||||||
|
query = ("%s where FirstName='%s' and Surname='%s'" %
|
||||||
|
(query, first_name, sur_name))
|
||||||
|
return self.query_db(query)
|
||||||
|
|
||||||
|
def get_user_id_by_name(self, name):
|
||||||
|
"""Get user id by name
|
||||||
|
|
||||||
|
Name is a (first_name, sur_name) tuple
|
||||||
|
Returns the id or -1 if not found.
|
||||||
|
"""
|
||||||
|
|
||||||
|
dataset = self.get_users(name)
|
||||||
|
if (not dataset or
|
||||||
|
dataset.Tables.Count < 1 or
|
||||||
|
dataset.Tables[0].Rows.Count == 0):
|
||||||
|
return -1
|
||||||
|
return dataset.Tables[0].Rows[0].get_Item("UserID")
|
||||||
|
|
||||||
|
def get_users(self, name=None):
|
||||||
|
"""Get users, from the ViewUserRecords call
|
||||||
|
|
||||||
|
Name param is optional tuple (first_name, sur_name)
|
||||||
|
Returns a dataset
|
||||||
|
"""
|
||||||
|
with Net2XS._lock:
|
||||||
|
self._check_client()
|
||||||
|
wheres = ['Active=1']
|
||||||
|
if name and len(name) == 2:
|
||||||
|
first_name, sur_name = name
|
||||||
|
if first_name:
|
||||||
|
wheres.append("FirstName='%s'" % first_name)
|
||||||
|
if sur_name:
|
||||||
|
wheres.append("Surname='%s'" % sur_name)
|
||||||
|
|
||||||
|
return self._client.ViewUserRecords(
|
||||||
|
' and '.join(wheres)).UsersDataSource
|
||||||
|
|
||||||
|
def get_user_record(self, user_id):
|
||||||
|
"""Get user record, from the ViewUserRecords call
|
||||||
|
|
||||||
|
Returns an IUserView user record or None if not found
|
||||||
|
"""
|
||||||
|
with Net2XS._lock:
|
||||||
|
self._check_client()
|
||||||
|
|
||||||
|
# Fetch current user info
|
||||||
|
users = self._client.ViewUserRecords(
|
||||||
|
'UserID=%d' % user_id).UsersList()
|
||||||
|
|
||||||
|
if users.Count != 1 or not users.ContainsKey(user_id):
|
||||||
|
return None
|
||||||
|
|
||||||
|
return users[user_id]
|
||||||
|
|
||||||
|
def get_user_name(self, user_id):
|
||||||
|
"""Get user name
|
||||||
|
|
||||||
|
Returns a string or None when user is not found
|
||||||
|
"""
|
||||||
|
dataset = self.query_db(
|
||||||
|
'select Username from sdk.UsersEx where UserID=%d' % user_id)
|
||||||
|
if (not dataset or
|
||||||
|
dataset.Tables.Count < 1 or
|
||||||
|
dataset.Tables[0].Rows.Count == 0):
|
||||||
|
return None
|
||||||
|
return dataset.Tables[0].Rows[0][0]
|
||||||
|
|
||||||
|
def add_user(
|
||||||
|
self,
|
||||||
|
access_level_id=1,
|
||||||
|
department_id=0,
|
||||||
|
anti_passback_ind=False,
|
||||||
|
alarm_user_ind=False,
|
||||||
|
first_name=None,
|
||||||
|
middle_name=None,
|
||||||
|
sur_name=None,
|
||||||
|
telephone_no=None,
|
||||||
|
telephone_extension=None,
|
||||||
|
pin_code=None,
|
||||||
|
activation_date=None,
|
||||||
|
active=True,
|
||||||
|
fax_no=None,
|
||||||
|
expiry_date=None,
|
||||||
|
custom_fields=None,
|
||||||
|
user_picture=None):
|
||||||
|
"""Add user record
|
||||||
|
|
||||||
|
DateTime fields can be either python or dotnet objects.
|
||||||
|
If activation date is None, the current date will be used.
|
||||||
|
If expiry date is None, the user entry will not expire.
|
||||||
|
Custom_fields is a string array (15) of which the first element is
|
||||||
|
ignored.
|
||||||
|
Returns True on success.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# If no user name is given at all: create one (required by Net2)
|
||||||
|
if not first_name and not sur_name and not middle_name:
|
||||||
|
first_name = "New"
|
||||||
|
sur_name = "User"
|
||||||
|
|
||||||
|
with Net2XS._lock:
|
||||||
|
self._check_client()
|
||||||
|
return self._client.AddUserRecord(
|
||||||
|
access_level_id,
|
||||||
|
department_id,
|
||||||
|
anti_passback_ind,
|
||||||
|
alarm_user_ind,
|
||||||
|
first_name,
|
||||||
|
middle_name,
|
||||||
|
sur_name,
|
||||||
|
telephone_no,
|
||||||
|
telephone_extension,
|
||||||
|
pin_code,
|
||||||
|
None,
|
||||||
|
flex_date_time_to_net(activation_date) or self.now_date,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
active,
|
||||||
|
fax_no,
|
||||||
|
flex_date_time_to_net(expiry_date) or self.no_expiration_date,
|
||||||
|
custom_fields,
|
||||||
|
user_picture)
|
||||||
|
|
||||||
|
def modify_user(
|
||||||
|
self,
|
||||||
|
user_id,
|
||||||
|
access_level_id=None,
|
||||||
|
department_id=None,
|
||||||
|
anti_passback_ind=None,
|
||||||
|
alarm_user_ind=None,
|
||||||
|
first_name=None,
|
||||||
|
middle_name=None,
|
||||||
|
sur_name=None,
|
||||||
|
telephone_no=None,
|
||||||
|
telephone_extension=None,
|
||||||
|
pin_code=None,
|
||||||
|
activation_date=None,
|
||||||
|
active=None,
|
||||||
|
fax_no=None,
|
||||||
|
expiry_date=None,
|
||||||
|
custom_fields=None,
|
||||||
|
user_picture=None,
|
||||||
|
delete_image=False):
|
||||||
|
"""Modify user record
|
||||||
|
|
||||||
|
Fields omitted keep their original value.
|
||||||
|
Custom_fields is a string array (15) of which the first element is
|
||||||
|
ignored.
|
||||||
|
Providing None custom values in the array, leaves the original value
|
||||||
|
unchanged.
|
||||||
|
Returns True on success.
|
||||||
|
"""
|
||||||
|
# Fetch current user info
|
||||||
|
uview = self.get_user_record(user_id)
|
||||||
|
if not uview:
|
||||||
|
return False
|
||||||
|
|
||||||
|
with Net2XS._lock:
|
||||||
|
self._check_client()
|
||||||
|
return self._client.UpdateUserRecord(
|
||||||
|
user_id,
|
||||||
|
uview.AccessLevelId if access_level_id is None else access_level_id,
|
||||||
|
uview.DepartmentId if department_id is None else department_id,
|
||||||
|
uview.AntiPassbackUser if anti_passback_ind is None else anti_passback_ind,
|
||||||
|
uview.AlarmUser if alarm_user_ind is None else alarm_user_ind,
|
||||||
|
first_name,
|
||||||
|
middle_name,
|
||||||
|
sur_name,
|
||||||
|
telephone_no,
|
||||||
|
telephone_extension,
|
||||||
|
pin_code,
|
||||||
|
None,
|
||||||
|
flex_date_time_to_net(activation_date) or uview.ActivationDate,
|
||||||
|
uview.Active if active is None else active,
|
||||||
|
fax_no,
|
||||||
|
flex_date_time_to_net(expiry_date) or uview.ExpiryDate,
|
||||||
|
custom_fields,
|
||||||
|
user_picture,
|
||||||
|
delete_image)
|
||||||
|
|
||||||
|
def delete_user(self, user_id):
|
||||||
|
"""Delete user record
|
||||||
|
|
||||||
|
Returns True on success
|
||||||
|
"""
|
||||||
|
with Net2XS._lock:
|
||||||
|
self._check_client()
|
||||||
|
return self._client.PurgeUser(user_id)
|
||||||
|
|
||||||
|
def deactivate_user(self, user_id):
|
||||||
|
"""Deactivate user. Preferred method iso delete.
|
||||||
|
|
||||||
|
Returns True on success
|
||||||
|
"""
|
||||||
|
return self.modify_user(
|
||||||
|
user_id=user_id,
|
||||||
|
access_level_id=0,
|
||||||
|
department_id=0,
|
||||||
|
pin_code="",
|
||||||
|
active=False)
|
||||||
|
|
||||||
|
def modify_user_access_level(self, user_id, access_level_id):
|
||||||
|
"""Alter user access level
|
||||||
|
|
||||||
|
Returns True on success
|
||||||
|
"""
|
||||||
|
return self.modify_user(
|
||||||
|
user_id=user_id,
|
||||||
|
access_level_id=access_level_id)
|
||||||
|
|
||||||
|
def modify_user_picture(self, user_id, user_picture):
|
||||||
|
"""Alter user picture. If user_picture is None, remove the picture.
|
||||||
|
|
||||||
|
Returns True on success
|
||||||
|
"""
|
||||||
|
return self.modify_user(
|
||||||
|
user_id=user_id,
|
||||||
|
user_picture=user_picture,
|
||||||
|
delete_image=True if not user_picture else False)
|
||||||
|
|
||||||
|
def get_area_ids(self, device_address):
|
||||||
|
"""Get area ids of a device
|
||||||
|
|
||||||
|
Returns a tuple with area id's (in, out)
|
||||||
|
"""
|
||||||
|
dataset = self.query_db(
|
||||||
|
'select b.ToAreaID from sdk.PeripheralNames a'
|
||||||
|
' inner join sdk.AreaGateways b on'
|
||||||
|
' a.PeripheralID=b.PeripheralID and'
|
||||||
|
' a.SerialNumber=%d order by a.SubAddress' %
|
||||||
|
(device_address))
|
||||||
|
if (not dataset or
|
||||||
|
dataset.Tables.Count < 1 or
|
||||||
|
dataset.Tables[0].Rows.Count != 2):
|
||||||
|
return None
|
||||||
|
return (dataset.Tables[0].Rows[0][0], dataset.Tables[0].Rows[1][0])
|
||||||
|
|
||||||
|
def get_device_addr_info(self):
|
||||||
|
"""Obtain all relevant device address info
|
||||||
|
|
||||||
|
Returns a dataset
|
||||||
|
"""
|
||||||
|
return self.query_db(
|
||||||
|
'select a.SerialNumber, a.SubAddress, a.PeripheralID, b.ToAreaID'
|
||||||
|
' from sdk.PeripheralNames a'
|
||||||
|
' inner join AreaGateways b on a.PeripheralID=b.PeripheralID'
|
||||||
|
' order by a.SerialNumber')
|
||||||
|
|
||||||
|
def get_time_slots(self, access_level_id, area_id):
|
||||||
|
"""Obtain allocated timeslots
|
||||||
|
|
||||||
|
Returns a dataset
|
||||||
|
"""
|
||||||
|
return self.query_db(
|
||||||
|
'select * from sdk.Timeslots'
|
||||||
|
' where TimezoneID='
|
||||||
|
'(select TimezoneID from AccessLevelMembers'
|
||||||
|
' where AccessLevelID=%d and AreaID=%d)' %
|
||||||
|
(access_level_id, area_id or -1))
|
||||||
|
|
||||||
|
def get_time_zones(self, time_zone_id=-1):
|
||||||
|
"""Obtain timezones, including slot data
|
||||||
|
|
||||||
|
Time_zone_id is optional. If not provided, all timezones are returned.
|
||||||
|
Returns a dataset
|
||||||
|
"""
|
||||||
|
with Net2XS._lock:
|
||||||
|
self._check_client()
|
||||||
|
if time_zone_id < 0:
|
||||||
|
return self._client.ViewTimezones().TimezonesDataSource
|
||||||
|
else:
|
||||||
|
return self._client.ViewTimezones(
|
||||||
|
time_zone_id, False).TimezonesDataSource
|
||||||
|
|
||||||
|
def get_py_time_zones(self, time_zone_id=-1):
|
||||||
|
"""Like get_time_zones, but returns a python array of TimeZone objects
|
||||||
|
|
||||||
|
Returns an array of TimeZone objects
|
||||||
|
"""
|
||||||
|
dataset = self.get_time_zones(time_zone_id)
|
||||||
|
return time_zones_to_py(dataset)
|
||||||
|
|
||||||
|
def add_time_zone(self, time_zone):
|
||||||
|
"""Add new time zone
|
||||||
|
|
||||||
|
Time_zone is a python TimeZone object type.
|
||||||
|
Returns True on success.
|
||||||
|
"""
|
||||||
|
with Net2XS._lock:
|
||||||
|
self._check_client()
|
||||||
|
tz_set = TimezonesSet()
|
||||||
|
for slot in time_zone.slots:
|
||||||
|
tz_set.Tables[1].AddTimeslotsRow(
|
||||||
|
slot.id,
|
||||||
|
time_zone.id,
|
||||||
|
date_time_to_net(slot.start_time),
|
||||||
|
date_time_to_net(slot.end_time),
|
||||||
|
slot.day)
|
||||||
|
|
||||||
|
# To array
|
||||||
|
ts_array = Array[TimezonesSet.TimeslotsRow](
|
||||||
|
len(time_zone.slots) * [None])
|
||||||
|
for i, row in enumerate(tz_set.Tables[1].Rows):
|
||||||
|
ts_array[i] = row
|
||||||
|
|
||||||
|
# Dispose timezones set
|
||||||
|
tz_set.Dispose()
|
||||||
|
|
||||||
|
return self._client.AddTimezone(time_zone.name, ts_array)
|
||||||
|
|
||||||
|
def delete_time_zone(self, time_zone_id):
|
||||||
|
"""Remove time zone with given id
|
||||||
|
|
||||||
|
Returns True on success.
|
||||||
|
"""
|
||||||
|
with Net2XS._lock:
|
||||||
|
self._check_client()
|
||||||
|
return self._client.DeleteTimezone(time_zone_id)
|
||||||
|
|
||||||
|
def update_time_zone(self, time_zone):
|
||||||
|
"""Update existing time zone
|
||||||
|
|
||||||
|
Time_zone is a python TimeZone object type.
|
||||||
|
Returns True on success.
|
||||||
|
"""
|
||||||
|
with Net2XS._lock:
|
||||||
|
self._check_client()
|
||||||
|
tz_set = TimezonesSet()
|
||||||
|
nr_slots = len(time_zone.slots)
|
||||||
|
|
||||||
|
if nr_slots:
|
||||||
|
for slot in time_zone.slots:
|
||||||
|
tz_set.Tables[1].AddTimeslotsRow(
|
||||||
|
slot.id,
|
||||||
|
time_zone.id,
|
||||||
|
date_time_to_net(slot.start_time),
|
||||||
|
date_time_to_net(slot.end_time),
|
||||||
|
slot.day)
|
||||||
|
|
||||||
|
# Work around for bug "Update Timezone with empty timeslot" (forum)
|
||||||
|
# At least 1 row is required for the update to work correctly.
|
||||||
|
# Should be fixed in V4.21
|
||||||
|
# TODO: only perform for lower versions?
|
||||||
|
else:
|
||||||
|
nr_slots = 1
|
||||||
|
tz_set.Tables[1].AddTimeslotsRow(
|
||||||
|
-1,
|
||||||
|
time_zone.id,
|
||||||
|
date_time_to_net(datetime(1899, 12, 30, 23, 59, 59)),
|
||||||
|
date_time_to_net(datetime(1899, 12, 30, 23, 59, 59)),
|
||||||
|
0)
|
||||||
|
|
||||||
|
# To array (bit awkward in Python.Net)
|
||||||
|
ts_array = Array[TimezonesSet.TimeslotsRow](nr_slots * [None])
|
||||||
|
for i, row in enumerate(tz_set.Tables[1].Rows):
|
||||||
|
ts_array[i] = row
|
||||||
|
|
||||||
|
# Dispose timezones set
|
||||||
|
tz_set.Dispose()
|
||||||
|
|
||||||
|
return self._client.UpdateTimezone(time_zone.id, ts_array)
|
||||||
|
|
||||||
|
def get_time_zone_id_by_name(self, name):
|
||||||
|
"""Get time zone id of time zone with given name
|
||||||
|
|
||||||
|
Returns a string or None when the timezone could not be found.
|
||||||
|
"""
|
||||||
|
dataset = self.query_db(
|
||||||
|
"select TimezoneID from sdk.Timezones"
|
||||||
|
" where Name='%s'" % name)
|
||||||
|
if (not dataset or
|
||||||
|
dataset.Tables.Count < 1 or
|
||||||
|
dataset.Tables[0].Rows.Count == 0):
|
||||||
|
return None
|
||||||
|
return dataset.Tables[0].Rows[0][0]
|
||||||
|
|
||||||
|
def get_access_levels(self, prefix=None):
|
||||||
|
"""Obtain access levels, optinally profiding a name prefix filter
|
||||||
|
|
||||||
|
Prefix is an optional parameter to obtain only access levels with the
|
||||||
|
given prefix.
|
||||||
|
Details need to be fetched separately.
|
||||||
|
Returns a dataset.
|
||||||
|
"""
|
||||||
|
query = 'select * from sdk.AccessLevels'
|
||||||
|
if prefix:
|
||||||
|
query = "%s where AccessLevelName like '%s%%'" % (query, prefix)
|
||||||
|
return self.query_db(query)
|
||||||
|
|
||||||
|
def get_access_level_details(self, access_level_id):
|
||||||
|
"""Obtain access level details
|
||||||
|
|
||||||
|
Returns a dataset.
|
||||||
|
"""
|
||||||
|
query = 'select * from sdk.AccessLevelMembers where AccessLevelID=%d' % (access_level_id)
|
||||||
|
return self.query_db(query)
|
||||||
|
|
||||||
|
def get_py_access_levels(self, prefix=None):
|
||||||
|
"""Like get_access_level, but returning a python array of AccessLevel
|
||||||
|
objects including details!
|
||||||
|
|
||||||
|
Returns an array of AccessLevel objects.
|
||||||
|
"""
|
||||||
|
dataset = self.get_access_levels(prefix)
|
||||||
|
if not dataset:
|
||||||
|
return []
|
||||||
|
access_levels = access_levels_to_py(dataset)
|
||||||
|
for al in access_levels:
|
||||||
|
dataset = self.get_access_level_details(al.id)
|
||||||
|
if dataset:
|
||||||
|
access_level_detail_to_py(al, dataset)
|
||||||
|
|
||||||
|
return access_levels
|
||||||
|
|
||||||
|
def add_access_level(self, access_level):
|
||||||
|
"""Add new access level
|
||||||
|
|
||||||
|
Access_level is a python AccessLevel object type.
|
||||||
|
Returns True on success.
|
||||||
|
"""
|
||||||
|
with Net2XS._lock:
|
||||||
|
self._check_client()
|
||||||
|
detail_set = AccessLevelDetailSet()
|
||||||
|
if access_level.details:
|
||||||
|
for detail in access_level.details:
|
||||||
|
detail_set.Tables[0].AddAccessLevelDetailRow(
|
||||||
|
None,
|
||||||
|
detail[0], # time zone id
|
||||||
|
None,
|
||||||
|
detail[1], # area id
|
||||||
|
None,
|
||||||
|
-1, # address
|
||||||
|
-1, # sub address
|
||||||
|
-1) # access level id
|
||||||
|
|
||||||
|
return self._client.AddAccessLevel(access_level.name, detail_set)
|
||||||
|
|
||||||
|
def delete_access_level(self, access_level_id):
|
||||||
|
"""Delete access level
|
||||||
|
|
||||||
|
Returns True on success.
|
||||||
|
"""
|
||||||
|
with Net2XS._lock:
|
||||||
|
self._check_client()
|
||||||
|
return self._client.DeleteAccessLevel(access_level_id)
|
||||||
|
|
||||||
|
def update_access_level(self, access_level):
|
||||||
|
"""Update access level
|
||||||
|
|
||||||
|
Access_level is a python AccessLevel object type.
|
||||||
|
Returns True on success.
|
||||||
|
"""
|
||||||
|
with Net2XS._lock:
|
||||||
|
self._check_client()
|
||||||
|
|
||||||
|
if access_level.details:
|
||||||
|
detail_set = AccessLevelDetailSet()
|
||||||
|
for detail in access_level.details:
|
||||||
|
detail_set.Tables[0].AddAccessLevelDetailRow(
|
||||||
|
None,
|
||||||
|
detail[0], # time zone id
|
||||||
|
None,
|
||||||
|
detail[1], # area id
|
||||||
|
None,
|
||||||
|
-1, # address
|
||||||
|
-1, # sub address
|
||||||
|
-1) # access level id
|
||||||
|
|
||||||
|
# Work around for similar bug as with the time zone slots
|
||||||
|
# When details are empty, existing data will not be cleared
|
||||||
|
# Therefore set TimezoneID to 0 for any existing entries
|
||||||
|
else:
|
||||||
|
ald = self._client.ViewAccessLevelDetail(access_level.id)
|
||||||
|
detail_set = ald.AccessLevelDetailsDataSource
|
||||||
|
for row in detail_set.Tables[0].Rows:
|
||||||
|
row.TimezoneID = 0
|
||||||
|
|
||||||
|
return self._client.UpdateAccessLevel(
|
||||||
|
access_level.id, access_level.name, detail_set)
|
||||||
|
|
||||||
|
def get_access_level_id_by_user(self, user_id):
|
||||||
|
"""Get access level id of given user
|
||||||
|
|
||||||
|
Returns an integer or None if the user could not be found
|
||||||
|
"""
|
||||||
|
with Net2XS._lock:
|
||||||
|
self._check_client()
|
||||||
|
dataset = self._client.ViewUserRecords(
|
||||||
|
'UserID=%d' % (user_id)).UsersDataSource
|
||||||
|
if (not dataset or
|
||||||
|
dataset.Tables.Count < 1 or
|
||||||
|
dataset.Tables[0].Rows.Count == 0):
|
||||||
|
return None
|
||||||
|
return dataset.Tables[0].Rows[0].AccessLevelID
|
||||||
|
|
||||||
|
def get_access_level_id_by_name(self, name):
|
||||||
|
"""Get access level id of level with given name
|
||||||
|
|
||||||
|
Returns a string or None if the accesslevel could not be found.
|
||||||
|
"""
|
||||||
|
dataset = self.query_db(
|
||||||
|
"select AccessLevelID from sdk.AccessLevels"
|
||||||
|
" where AccessLevelName='%s'" % name)
|
||||||
|
if (not dataset or
|
||||||
|
dataset.Tables.Count < 1 or
|
||||||
|
dataset.Tables[0].Rows.Count == 0):
|
||||||
|
return None
|
||||||
|
return dataset.Tables[0].Rows[0][0]
|
||||||
|
|
||||||
|
def get_operator_level(self, user_id):
|
||||||
|
"""Get operator level of the given user id
|
||||||
|
|
||||||
|
Returns an integer.
|
||||||
|
"""
|
||||||
|
with Net2XS._lock:
|
||||||
|
self._check_client()
|
||||||
|
return self._client.GetOperatorLevel(user_id)
|
||||||
|
|
||||||
|
def add_card(self, card_nr, type_id, user_id):
|
||||||
|
"""Add a new card to the given user
|
||||||
|
|
||||||
|
Returns True on success.
|
||||||
|
"""
|
||||||
|
with Net2XS._lock:
|
||||||
|
self._check_client()
|
||||||
|
return self._client.AddCard(card_nr, type_id, user_id)
|
||||||
|
|
||||||
|
def delete_card(self, card_nr):
|
||||||
|
"""Delete a card with the given card_nr
|
||||||
|
|
||||||
|
Returns True on success (even if card_nr did not exist).
|
||||||
|
"""
|
||||||
|
with Net2XS._lock:
|
||||||
|
self._check_client()
|
||||||
|
return self._client.DeleteCard(card_nr)
|
||||||
|
|
||||||
|
def get_cards(self, user_id=None):
|
||||||
|
"""Obtain all 'cards' of the given user id
|
||||||
|
|
||||||
|
If no user_id is given, all cards are returned.
|
||||||
|
Returns a dataset.
|
||||||
|
"""
|
||||||
|
with Net2XS._lock:
|
||||||
|
self._check_client()
|
||||||
|
# Setup dataset to return
|
||||||
|
dataset = DataSet()
|
||||||
|
table = dataset.Tables.Add()
|
||||||
|
table.Columns.Add("UserID")
|
||||||
|
table.Columns.Add("CardNumber")
|
||||||
|
table.Columns.Add("LostCard")
|
||||||
|
table.Columns.Add("CardTypeID")
|
||||||
|
|
||||||
|
# Fetch normal cards
|
||||||
|
query = ("select * from sdk.Cards" +
|
||||||
|
" where CardTypeID is not null" +
|
||||||
|
" and CardTypeID <> 7" +
|
||||||
|
" and CardNumber <> 100000000 + UserID")
|
||||||
|
if type(user_id) is int:
|
||||||
|
query += " and UserID=%d" % (user_id)
|
||||||
|
card_dataset = self.query_db(query)
|
||||||
|
if card_dataset and card_dataset.Tables.Count > 0:
|
||||||
|
for row in card_dataset.Tables[0].Rows:
|
||||||
|
table.Rows.Add(row['UserID'],
|
||||||
|
row['CardNumber'],
|
||||||
|
row['LostCard'],
|
||||||
|
row['CardTypeID'])
|
||||||
|
|
||||||
|
# Fetch vehicle registrations
|
||||||
|
query = "select * from sdk.UserVehicleIndex"
|
||||||
|
if type(user_id) is int:
|
||||||
|
query += " where UserID=%d" % (user_id)
|
||||||
|
vehicle_dataset = self.query_db(query)
|
||||||
|
if vehicle_dataset and vehicle_dataset.Tables.Count > 0:
|
||||||
|
for row in vehicle_dataset.Tables[0].Rows:
|
||||||
|
table.Rows.Add(row['UserID'],
|
||||||
|
row['VehicleIndex'],
|
||||||
|
False,
|
||||||
|
7)
|
||||||
|
return dataset
|
||||||
|
|
||||||
|
def add_event_record(
|
||||||
|
self,
|
||||||
|
#CK: check!
|
||||||
|
#event_type, event_subtype=EventViewEnums.Net2EventSubTypes.None,
|
||||||
|
event_type, event_subtype=None,
|
||||||
|
address=0, subaddress=0, user_id=None, card_nr=0,
|
||||||
|
event_detail=None,
|
||||||
|
linked_event_id=0, ioboard_id=0, input_id=0, output_id=0):
|
||||||
|
"""Add an event record
|
||||||
|
|
||||||
|
Returns True on success.
|
||||||
|
"""
|
||||||
|
with Net2XS._lock:
|
||||||
|
self._check_client()
|
||||||
|
return self._client.AddEventRecord(
|
||||||
|
event_type,
|
||||||
|
event_subtype,
|
||||||
|
address, subaddress, user_id, card_nr, event_detail,
|
||||||
|
linked_event_id, ioboard_id, input_id, output_id)
|
||||||
|
|
||||||
|
def add_acu(self, address, acu_type):
|
||||||
|
"""Add new acu device
|
||||||
|
|
||||||
|
Acu_types: Classic = 1, Nano = 2, Plus = 3, EasyProxNano = 4, PaxLoc = 7, PaxLocMifare = 8
|
||||||
|
Returns True on success. (up till 4.27, there is a bug in the SDK causing a False on success)
|
||||||
|
"""
|
||||||
|
with Net2XS._lock:
|
||||||
|
self._check_client()
|
||||||
|
return self._client.AddACU(address, acu_type)
|
||||||
|
|
||||||
|
def delete_acu(self, address):
|
||||||
|
"""Delete acu device
|
||||||
|
|
||||||
|
Return True on success.
|
||||||
|
"""
|
||||||
|
with Net2XS._lock:
|
||||||
|
self._check_client()
|
||||||
|
return self._client.DeleteACU(address)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def last_error_message(self):
|
||||||
|
"""Last error message
|
||||||
|
"""
|
||||||
|
with Net2XS._lock:
|
||||||
|
self._check_client()
|
||||||
|
return self._client.LastErrorMessage
|
||||||
|
|
||||||
|
@property
|
||||||
|
def no_expiration_date(self):
|
||||||
|
"""The 'special' no expiration date of Net2 (1753-01-01)
|
||||||
|
|
||||||
|
Returns dotnet DateTime object
|
||||||
|
"""
|
||||||
|
return date_time_to_net(datetime(year=1753, month=1, day=1))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def now_date(self):
|
||||||
|
"""The now datetime
|
||||||
|
|
||||||
|
Returns dotnet DateTime object
|
||||||
|
"""
|
||||||
|
return date_time_to_net(datetime.now())
|
||||||
|
|
||||||
|
def dispose(self):
|
||||||
|
"""Dispose Net2 client
|
||||||
|
"""
|
||||||
|
with Net2XS._lock:
|
||||||
|
if self._client:
|
||||||
|
try:
|
||||||
|
self._client.Dispose()
|
||||||
|
self._client = None
|
||||||
|
Net2XS._logger.Debug('Disposed')
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _reconnected(self, sender, event_args):
|
||||||
|
"""Reconnect handler
|
||||||
|
"""
|
||||||
|
if event_args.ReAuthenticationRequired:
|
||||||
|
try:
|
||||||
|
self.authenticate(self._user_id, self._password)
|
||||||
|
self._connected = True
|
||||||
|
Net2XS._logger.Debug('Reconnected')
|
||||||
|
except Exception as e:
|
||||||
|
Net2XS._logger.Debug('Reconnect error: %s' % str(e))
|
||||||
|
else:
|
||||||
|
self._connected = True
|
||||||
|
|
||||||
|
def _disconnected(self, sender, event_args):
|
||||||
|
"""Disconnect handler
|
||||||
|
"""
|
||||||
|
Net2XS._logger.Debug('Disconnected')
|
||||||
|
self._connected = False
|
||||||
|
|
||||||
|
@property
|
||||||
|
def on_acu_event(self):
|
||||||
|
return self._on_acu_event
|
||||||
|
|
||||||
|
@on_acu_event.setter
|
||||||
|
def on_acu_event(self, value):
|
||||||
|
self._on_acu_event = value
|
||||||
|
|
||||||
|
def _acu_event(self, sender, event_view):
|
||||||
|
"""Acu event handler
|
||||||
|
"""
|
||||||
|
if self.on_acu_event:
|
||||||
|
try:
|
||||||
|
self.on_acu_event(sender, event_view)
|
||||||
|
except Exception as e:
|
||||||
|
Net2XS._logger.Debug('Acu even handler error: %s' % str(e))
|
||||||
|
|
||||||
|
def monitor_acu(self, address):
|
||||||
|
"""Monitor events of given acu
|
||||||
|
|
||||||
|
Returns True on success
|
||||||
|
"""
|
||||||
|
with Net2XS._lock:
|
||||||
|
self._check_client()
|
||||||
|
return self._client.MonitorAcu(address)
|
||||||
|
|
||||||
|
def stop_monitoring_acu(self, address):
|
||||||
|
"""Stop monitoring events of given acu
|
||||||
|
|
||||||
|
Returns True on success
|
||||||
|
"""
|
||||||
|
with Net2XS._lock:
|
||||||
|
self._check_client()
|
||||||
|
return self._client.StopMonitoringAcu(address)
|
||||||
|
|
||||||
|
def activate_ioboard_relays(self, ioboard_id, settings):
|
||||||
|
"""Control ioboard relays
|
||||||
|
|
||||||
|
Returns True on success
|
||||||
|
"""
|
||||||
|
with Net2XS._lock:
|
||||||
|
self._check_client()
|
||||||
|
return self._client.ActivateIoBoardRelays(
|
||||||
|
ioboard_id, settings)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def door_status_str(cls, status):
|
||||||
|
"""Translate door status to human readable string
|
||||||
|
|
||||||
|
Returns a csv string with the combined status bits.
|
||||||
|
"""
|
||||||
|
ret = []
|
||||||
|
if status & OC.DoorStatusFlags.PSUIsOK:
|
||||||
|
ret.append('psu ok')
|
||||||
|
if status & OC.DoorStatusFlags.IntruderAlarm:
|
||||||
|
ret.append('intruder')
|
||||||
|
if not status & OC.DoorStatusFlags.TamperStatusGood:
|
||||||
|
ret.append('tampered')
|
||||||
|
if status & OC.DoorStatusFlags.DoorContactClosed:
|
||||||
|
ret.append('door contact closed')
|
||||||
|
if status & OC.DoorStatusFlags.AlarmTripped:
|
||||||
|
ret.append('alarm')
|
||||||
|
if status & OC.DoorStatusFlags.DoorOpen:
|
||||||
|
ret.append('door open')
|
||||||
|
# When psu not ok, other state bits are irrelevant
|
||||||
|
else:
|
||||||
|
ret.append('psu not ok')
|
||||||
|
return ', '.join(ret)
|
@ -0,0 +1,151 @@
|
|||||||
|
"""
|
||||||
|
Module holding conversion definitions
|
||||||
|
"""
|
||||||
|
from datetime import datetime
|
||||||
|
from System import DateTime
|
||||||
|
from . deftypes import TimeZone, TimeSlot, AccessLevel
|
||||||
|
|
||||||
|
|
||||||
|
def date_time_to_py(val):
|
||||||
|
"""Convert dotnet date time format to python presentation
|
||||||
|
|
||||||
|
Returns python datetime object.
|
||||||
|
"""
|
||||||
|
return datetime(
|
||||||
|
year=val.Year, month=val.Month, day=val.Day,
|
||||||
|
hour=val.Hour, minute=val.Minute, second=val.Second)
|
||||||
|
|
||||||
|
|
||||||
|
def date_time_to_net(val):
|
||||||
|
"""Convert python to dotnet date time presentation
|
||||||
|
|
||||||
|
Returns dotnet DateTime object.
|
||||||
|
"""
|
||||||
|
return DateTime(
|
||||||
|
val.year, val.month, val.day,
|
||||||
|
val.hour, val.minute, val.second)
|
||||||
|
|
||||||
|
|
||||||
|
def flex_date_time_to_net(val):
|
||||||
|
"""Convert python to dotnet date time presentation when required
|
||||||
|
|
||||||
|
Returns dotnet DateTime object or None.
|
||||||
|
"""
|
||||||
|
if val is None:
|
||||||
|
return val
|
||||||
|
|
||||||
|
if type(val) is datetime:
|
||||||
|
return date_time_to_net(val)
|
||||||
|
|
||||||
|
if not type(val) is DateTime:
|
||||||
|
raise TypeError(
|
||||||
|
("The time value supplied (%s), " +
|
||||||
|
"can not be interpreted as a dotnet DateTime object") %
|
||||||
|
(str(val)))
|
||||||
|
return val
|
||||||
|
|
||||||
|
|
||||||
|
def time_zones_to_py(dataset):
|
||||||
|
"""Convert Net2 time zones dataset to a python presentation
|
||||||
|
|
||||||
|
Returns array of TimeZone objects.
|
||||||
|
"""
|
||||||
|
if not dataset or dataset.Tables.Count != 2:
|
||||||
|
raise Exception('Timezone dataset is invalid')
|
||||||
|
|
||||||
|
result = []
|
||||||
|
|
||||||
|
for tz_row in dataset.Tables[0].Rows:
|
||||||
|
time_zone = TimeZone(
|
||||||
|
id=tz_row.TimezoneID,
|
||||||
|
name=tz_row.Name)
|
||||||
|
|
||||||
|
ts_rows = dataset.Tables[1].Select('TimezoneID=%d' % time_zone.id)
|
||||||
|
for ts_row in ts_rows:
|
||||||
|
time_slot = TimeSlot(
|
||||||
|
id=ts_row.SlotID,
|
||||||
|
day=ts_row.Day,
|
||||||
|
start_time=date_time_to_py(ts_row.StartTime),
|
||||||
|
end_time=date_time_to_py(ts_row.EndTime))
|
||||||
|
time_zone.slots.append(time_slot)
|
||||||
|
|
||||||
|
result.append(time_zone)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def access_levels_to_py(dataset):
|
||||||
|
"""Convert Net2 access_level dataset to a python presentation
|
||||||
|
|
||||||
|
Returns array of AccessLevel objects.
|
||||||
|
"""
|
||||||
|
if not dataset or dataset.Tables.Count != 1:
|
||||||
|
raise Exception('AccessLevel dataset is invalid')
|
||||||
|
|
||||||
|
result = []
|
||||||
|
|
||||||
|
for al_row in dataset.Tables[0].Rows:
|
||||||
|
access_level = AccessLevel(
|
||||||
|
id=al_row.get_Item("AccessLevelID"),
|
||||||
|
name=al_row.get_Item("AccessLevelName"))
|
||||||
|
result.append(access_level)
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def access_level_detail_to_py(access_level, dataset):
|
||||||
|
"""Convert Net2 access_level detail dataset
|
||||||
|
time_zones is a net2-id key based lookup dict.
|
||||||
|
"""
|
||||||
|
if not dataset or dataset.Tables.Count != 1:
|
||||||
|
raise Exception('AccessLevelDetail dataset is invalid')
|
||||||
|
|
||||||
|
# Clear existing
|
||||||
|
access_level.details = []
|
||||||
|
# Add details
|
||||||
|
for ald_row in dataset.Tables[0].Rows:
|
||||||
|
time_zone_id = ald_row.get_Item("TimezoneID")
|
||||||
|
area_id = ald_row.get_Item("AreaID")
|
||||||
|
access_level.details.append((time_zone_id, area_id))
|
||||||
|
|
||||||
|
|
||||||
|
def user_view_to_py(user_view):
|
||||||
|
"""Convert a Net2 UserView object (partially) to a dict
|
||||||
|
"""
|
||||||
|
res = {}
|
||||||
|
res["user_id"] = user_view.UserId
|
||||||
|
res["access_level_id"] = user_view.AccessLevelId
|
||||||
|
res["department_id"] = user_view.DepartmentId
|
||||||
|
res["anti_passback_user"] = user_view.AntiPassbackUser
|
||||||
|
res["alarm_user"] = user_view.AlarmUser
|
||||||
|
res["first_name"] = user_view.FirstName
|
||||||
|
res["middle_name"] = user_view.MiddleName
|
||||||
|
res["sur_name"] = user_view.Surname
|
||||||
|
res["telephone"] = user_view.Telephone
|
||||||
|
res["extension"] = user_view.Extension
|
||||||
|
res["pin"] = user_view.PIN
|
||||||
|
res["activation_date"] = date_time_to_py(user_view.ActivationDate)
|
||||||
|
res["active"] = user_view.Active
|
||||||
|
res["fax"] = user_view.Fax
|
||||||
|
res["expiry_date"] = date_time_to_py(user_view.ExpiryDate)
|
||||||
|
res["custom_fields"] = [
|
||||||
|
None,
|
||||||
|
user_view.Field1_100,
|
||||||
|
user_view.Field2_100,
|
||||||
|
user_view.Field3_50,
|
||||||
|
user_view.Field4_50,
|
||||||
|
user_view.Field5_50,
|
||||||
|
user_view.Field6_50,
|
||||||
|
user_view.Field7_50,
|
||||||
|
user_view.Field8_50,
|
||||||
|
user_view.Field9_50,
|
||||||
|
user_view.Field10_50,
|
||||||
|
user_view.Field11_50,
|
||||||
|
user_view.Field12_50,
|
||||||
|
user_view.Field13_Memo,
|
||||||
|
user_view.Field14_50]
|
||||||
|
res["user_guid"] = user_view.UserGuid.ToString()
|
||||||
|
res["last_area_id"] = user_view.LastAreaId
|
||||||
|
res["last_access_time"] = date_time_to_py(user_view.LastAccessTime)
|
||||||
|
res["last_updated"] = date_time_to_py(user_view.LastUpdated)
|
||||||
|
|
||||||
|
return res
|
@ -0,0 +1,120 @@
|
|||||||
|
"""
|
||||||
|
Module holding definition data types
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class TimeSlot(object):
|
||||||
|
"""Time zone slot definition
|
||||||
|
|
||||||
|
Member id: Timeslot id
|
||||||
|
Member day: Timeslot day number
|
||||||
|
Member start_time: Start time of slot
|
||||||
|
Member start_time: End time of slot
|
||||||
|
"""
|
||||||
|
def __init__(self, id=-1, day=-1, start_time=None, end_time=None):
|
||||||
|
self.id = id
|
||||||
|
self.start_time = start_time
|
||||||
|
self.end_time = end_time
|
||||||
|
self.day = day
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
"""Compare
|
||||||
|
"""
|
||||||
|
return (
|
||||||
|
self.start_time == other.start_time and
|
||||||
|
self.end_time == other.end_time and
|
||||||
|
self.day == other.day)
|
||||||
|
|
||||||
|
def __ne__(self, other):
|
||||||
|
"""Compare negative
|
||||||
|
"""
|
||||||
|
return not self.__eq__(other)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return ('%s id=%d, day=%d, start_time=%s, end_time=%s' %
|
||||||
|
(self.__class__.__name__,
|
||||||
|
self.id, self.day, self.start_time, self.end_time))
|
||||||
|
|
||||||
|
|
||||||
|
class TimeZone(object):
|
||||||
|
"""Time zone definition
|
||||||
|
|
||||||
|
Member id: Timezone id
|
||||||
|
Member name: Timezone name
|
||||||
|
Member slots: Array of TimeSlot objects
|
||||||
|
"""
|
||||||
|
def __init__(self, id=-1, name=None):
|
||||||
|
self.id = id
|
||||||
|
self.name = name
|
||||||
|
self.slots = []
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
"""Compare
|
||||||
|
"""
|
||||||
|
if (type(self) != type(other) or
|
||||||
|
len(self.slots) != len(other.slots)):
|
||||||
|
return False
|
||||||
|
|
||||||
|
for s_slot in self.slots:
|
||||||
|
for o_slot in other.slots:
|
||||||
|
if s_slot == o_slot:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def __ne__(self, other):
|
||||||
|
"""Compare negative
|
||||||
|
"""
|
||||||
|
return not self.__eq__(other)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
out = []
|
||||||
|
out.append('%s id=%d, name=%s' %
|
||||||
|
(self.__class__.__name__, self.id, self.name))
|
||||||
|
for slot in self.slots:
|
||||||
|
out.append('-%s' % slot)
|
||||||
|
return '\n'.join(out)
|
||||||
|
|
||||||
|
|
||||||
|
class AccessLevel(object):
|
||||||
|
"""Access level definition
|
||||||
|
|
||||||
|
Member id: Accesslevel id
|
||||||
|
Member name: Accesslevel name
|
||||||
|
Member details: Array of (timezone id, area id) tuples
|
||||||
|
"""
|
||||||
|
def __init__(self, id, name):
|
||||||
|
self.id = id
|
||||||
|
self.name = name
|
||||||
|
self.details = []
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
"""Compare
|
||||||
|
"""
|
||||||
|
if (type(self) != type(other) or
|
||||||
|
len(self.details) != len(other.details)):
|
||||||
|
return False
|
||||||
|
|
||||||
|
for s_detail in self.details:
|
||||||
|
for o_detail in other.details:
|
||||||
|
if s_detail == o_detail:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def __ne__(self, other):
|
||||||
|
"""Compare negative
|
||||||
|
"""
|
||||||
|
return not self.__eq__(other)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
out = []
|
||||||
|
out.append('%s id=%d, name=%s' % (
|
||||||
|
self.__class__.__name__, self.id, self.name))
|
||||||
|
for tz_id, area_id in self.details:
|
||||||
|
out.append('-tz=%d, area=%d' % (tz_id, area_id))
|
||||||
|
return '\n'.join(out)
|
@ -0,0 +1,8 @@
|
|||||||
|
"""
|
||||||
|
The module that offers several Net2 related network utilities
|
||||||
|
"""
|
||||||
|
|
||||||
|
# This is just to force py2exe to include these packages
|
||||||
|
if False == True:
|
||||||
|
import network.net2plus
|
||||||
|
import network.sqlserver
|
@ -0,0 +1,66 @@
|
|||||||
|
"""
|
||||||
|
Net2Plus network utilities
|
||||||
|
"""
|
||||||
|
|
||||||
|
import socket
|
||||||
|
|
||||||
|
|
||||||
|
class Net2PlusFinder(object):
|
||||||
|
"""Class to help find Net2Plus modules
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, port=30718, timeout=4, ip="0.0.0.0"):
|
||||||
|
self._port = port
|
||||||
|
self._timeout = timeout
|
||||||
|
self._ip = ip
|
||||||
|
|
||||||
|
def _broadcast(self):
|
||||||
|
"""Broadcast on udp
|
||||||
|
"""
|
||||||
|
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||||
|
try:
|
||||||
|
s.bind((self._ip, self._port))
|
||||||
|
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||||
|
s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
|
||||||
|
# Send twice
|
||||||
|
s.sendto(bytearray([0x00, 0x00, 0x00, 0xf6]), ("255.255.255.255", self._port))
|
||||||
|
s.sendto(bytearray([0x00, 0x00, 0x00, 0xf6]), ("255.255.255.255", self._port))
|
||||||
|
finally:
|
||||||
|
s.close()
|
||||||
|
|
||||||
|
def find(self):
|
||||||
|
"""Find Net2Plus modules responsing to a Broadcast
|
||||||
|
|
||||||
|
Returns an array of dicts with info about each module
|
||||||
|
"""
|
||||||
|
res = []
|
||||||
|
|
||||||
|
# Broadcast
|
||||||
|
self._broadcast()
|
||||||
|
|
||||||
|
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||||
|
try:
|
||||||
|
s.settimeout(self._timeout)
|
||||||
|
s.bind((self._ip, self._port))
|
||||||
|
|
||||||
|
# Wait for result
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
data, addr = s.recvfrom(1024)
|
||||||
|
if len(data) >= 30:
|
||||||
|
mac_nrs = tuple(ord(i) for i in data[24:30])
|
||||||
|
mac = "%02x:%02x:%02x:%02x:%02x:%02x" % mac_nrs
|
||||||
|
mod_nr = (mac_nrs[3] << 16) + (mac_nrs[4] << 8) + mac_nrs[5]
|
||||||
|
version = "%d.%02d (%d.%02d)" % (ord(data[19]), ord(data[18]), ord(data[21]), ord(data[20]))
|
||||||
|
dev_dict = dict(
|
||||||
|
ip= addr[0],
|
||||||
|
mac=mac,
|
||||||
|
module=mod_nr,
|
||||||
|
version=version)
|
||||||
|
res.append(dev_dict)
|
||||||
|
except socket.timeout:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return res
|
||||||
|
finally:
|
||||||
|
s.close()
|
@ -0,0 +1,54 @@
|
|||||||
|
"""
|
||||||
|
SQLServer network utilities
|
||||||
|
"""
|
||||||
|
|
||||||
|
import socket
|
||||||
|
|
||||||
|
|
||||||
|
class SqlServerFinder(object):
|
||||||
|
"""Class to help find SQLServer instances
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, port=1434, timeout=4, ip="0.0.0.0"):
|
||||||
|
self._port = port
|
||||||
|
self._timeout = timeout
|
||||||
|
self._ip = ip
|
||||||
|
|
||||||
|
def find(self):
|
||||||
|
"""Find SQLServer instances responsing to a Broadcast
|
||||||
|
|
||||||
|
Returns an array of dicts with info about each instance
|
||||||
|
"""
|
||||||
|
res = []
|
||||||
|
|
||||||
|
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||||
|
try:
|
||||||
|
s.settimeout(self._timeout)
|
||||||
|
s.bind((self._ip, 0))
|
||||||
|
self._local_port = s.getsockname()[1]
|
||||||
|
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||||
|
s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
|
||||||
|
s.sendto(bytearray([0x02]), ("255.255.255.255", self._port))
|
||||||
|
|
||||||
|
# Wait for result
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
data, addr = s.recvfrom(1024)
|
||||||
|
if len(data) > 3:
|
||||||
|
data = data.decode()
|
||||||
|
entry = {}
|
||||||
|
parts = data[3:].split(";")
|
||||||
|
nr_values = int(len(parts) / 2)
|
||||||
|
for i in range(nr_values):
|
||||||
|
key = parts[i * 2].strip()
|
||||||
|
value = parts[1 + i * 2].strip()
|
||||||
|
if (key):
|
||||||
|
entry[key] = value
|
||||||
|
res.append(entry)
|
||||||
|
|
||||||
|
except socket.timeout:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return res
|
||||||
|
finally:
|
||||||
|
s.close()
|
@ -0,0 +1,64 @@
|
|||||||
|
"""
|
||||||
|
Module to use log4net from python
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
import clr
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import settings
|
||||||
|
|
||||||
|
# Add path lib to search path
|
||||||
|
LOG4NET_LIB_DIR = os.path.join(settings.LIB_DIR, 'log4netdll')
|
||||||
|
if LOG4NET_LIB_DIR not in sys.path:
|
||||||
|
sys.path.append(LOG4NET_LIB_DIR)
|
||||||
|
|
||||||
|
clr.AddReference('log4net')
|
||||||
|
import log4net
|
||||||
|
from log4net.Config import XmlConfigurator
|
||||||
|
clr.AddReference('System.Xml')
|
||||||
|
from System.Xml import XmlDocument
|
||||||
|
|
||||||
|
|
||||||
|
class Log4NetError(Exception):
|
||||||
|
"""Log4Net exception class
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Log4Net(object):
|
||||||
|
"""Class that offers log4net usage, taking its config from an xml file
|
||||||
|
"""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def read_config(cls, config_file):
|
||||||
|
"""Read and process config file
|
||||||
|
"""
|
||||||
|
if not os.path.isfile(config_file):
|
||||||
|
raise Log4NetError('Failed to find config file "%s"' % config_file)
|
||||||
|
|
||||||
|
# Read document
|
||||||
|
doc = XmlDocument()
|
||||||
|
try:
|
||||||
|
doc.Load(config_file)
|
||||||
|
except Exception as e:
|
||||||
|
raise Log4NetError(str(e))
|
||||||
|
|
||||||
|
# Obtain 1st element with log4net tag
|
||||||
|
for element in doc.GetElementsByTagName('log4net'):
|
||||||
|
XmlConfigurator.Configure(element)
|
||||||
|
break
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_logger(cls, name):
|
||||||
|
"""Obtain logger instance
|
||||||
|
"""
|
||||||
|
return log4net.LogManager.GetLogger(name)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_file_appender_logfile(cls):
|
||||||
|
"""Obtain file appender log file
|
||||||
|
"""
|
||||||
|
for appender in log4net.LogManager.GetRepository().GetAppenders():
|
||||||
|
if isinstance(appender, log4net.Appender.FileAppender):
|
||||||
|
return str(appender.File)
|
||||||
|
return None
|
@ -0,0 +1,51 @@
|
|||||||
|
"""
|
||||||
|
Sample script to add a new user
|
||||||
|
"""
|
||||||
|
|
||||||
|
from net2xs import Net2XS
|
||||||
|
|
||||||
|
# Uncomment to use dotnet DateTime objects
|
||||||
|
# from System import DateTime
|
||||||
|
|
||||||
|
# Uncomment to use python datetime objects
|
||||||
|
# from datetime import datetime
|
||||||
|
|
||||||
|
|
||||||
|
# Operator id 0 is System Engineer
|
||||||
|
OPERATOR_ID = 0
|
||||||
|
# Default Net2 password
|
||||||
|
OPERATOR_PWD = "net2"
|
||||||
|
# When running on the machine where Net2 is installed
|
||||||
|
NET2_SERVER = "localhost"
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
|
||||||
|
with Net2XS(NET2_SERVER) as net2:
|
||||||
|
# Authenticate
|
||||||
|
net2.authenticate(OPERATOR_ID, OPERATOR_PWD)
|
||||||
|
# Add new user
|
||||||
|
res = net2.add_user(
|
||||||
|
access_level_id=1, # Always, all doors
|
||||||
|
department_id=0, # No department
|
||||||
|
anti_passback_ind=False,
|
||||||
|
alarm_user_ind=False,
|
||||||
|
first_name="John",
|
||||||
|
middle_name=None,
|
||||||
|
sur_name="Doe",
|
||||||
|
telephone_no="12345678",
|
||||||
|
telephone_extension=None,
|
||||||
|
pin_code=None,
|
||||||
|
activation_date=None, # Now
|
||||||
|
# Or supply a dotnet DateTime object (also see import)
|
||||||
|
# activation_date=DateTime(2018, 1, 2),
|
||||||
|
# Or supply a python datetime object (also see import)
|
||||||
|
# activation_date=datetime(2018, 1, 2),
|
||||||
|
active=True,
|
||||||
|
fax_no=None,
|
||||||
|
expiry_date=None) # Never expire (also see activation_date)
|
||||||
|
|
||||||
|
if res:
|
||||||
|
print("Success")
|
||||||
|
else:
|
||||||
|
print("Failure")
|
@ -0,0 +1,46 @@
|
|||||||
|
"""
|
||||||
|
Sample script to add many users with cards
|
||||||
|
"""
|
||||||
|
from net2xs import Net2XS
|
||||||
|
|
||||||
|
|
||||||
|
# Operator id 0 is System Engineer
|
||||||
|
OPERATOR_ID = 0
|
||||||
|
# Default Net2 password
|
||||||
|
OPERATOR_PWD = "net2"
|
||||||
|
# When running on the machine where Net2 is installed
|
||||||
|
NET2_SERVER = "localhost"
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
|
||||||
|
with Net2XS(NET2_SERVER) as net2:
|
||||||
|
# Authenticate
|
||||||
|
net2.authenticate(OPERATOR_ID, OPERATOR_PWD)
|
||||||
|
|
||||||
|
# Add 1000 users
|
||||||
|
for i in range(1, 1001):
|
||||||
|
first_name = "Test"
|
||||||
|
sur_name = "User #%04d" % (i)
|
||||||
|
card_nr = 77770000 + i
|
||||||
|
|
||||||
|
# Add user
|
||||||
|
res = net2.add_user(
|
||||||
|
first_name=first_name,
|
||||||
|
sur_name=sur_name)
|
||||||
|
if not res:
|
||||||
|
print("Failed to add user %s %s: %s" %
|
||||||
|
(first_name, sur_name, net2.last_error_message))
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Get user id
|
||||||
|
user_id = net2.get_user_id_by_name((first_name, sur_name))
|
||||||
|
if user_id < 0:
|
||||||
|
print("Failed to find user %s %s" % (first_name, sur_name))
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Create card
|
||||||
|
res = net2.add_card(card_nr, 1, user_id)
|
||||||
|
if not res:
|
||||||
|
print("Failed to add card to user %s %s: %s" %
|
||||||
|
(first_name, sur_name, net2.last_error_message))
|
@ -0,0 +1,35 @@
|
|||||||
|
"""
|
||||||
|
Sample script to query the database directly
|
||||||
|
"""
|
||||||
|
|
||||||
|
from net2dbxs import Net2DBXS
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
|
||||||
|
with Net2DBXS() as net2db:
|
||||||
|
# Connect
|
||||||
|
net2db.connect()
|
||||||
|
# Get last log entry relating a device
|
||||||
|
dataset = net2db.query_db(
|
||||||
|
"select * from EventsEx "
|
||||||
|
"where EventID = "
|
||||||
|
"(select max(EventID) from sdk.EventsEx "
|
||||||
|
" where SerialNumber is not NULL)")
|
||||||
|
|
||||||
|
# Non result
|
||||||
|
if (not dataset
|
||||||
|
or dataset.Tables.Count < 1
|
||||||
|
or dataset.Tables[0].Rows.Count < 1):
|
||||||
|
|
||||||
|
print("Nothing relevant found")
|
||||||
|
else:
|
||||||
|
# Just to demonstrate how to get values from a dataset
|
||||||
|
|
||||||
|
# Typically Table[0]
|
||||||
|
table = dataset.Tables[0]
|
||||||
|
# In this case only interested in the first row
|
||||||
|
row = dataset.Tables[0].Rows[0]
|
||||||
|
# For each column
|
||||||
|
for col in table.Columns:
|
||||||
|
val = row.get_Item(col.ColumnName)
|
||||||
|
print("%s = %s" % (col.ColumnName, val))
|
@ -0,0 +1,17 @@
|
|||||||
|
"""
|
||||||
|
Sample script to detect Net2Plus modules
|
||||||
|
"""
|
||||||
|
|
||||||
|
from network.net2plus import Net2PlusFinder
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
|
||||||
|
inst = Net2PlusFinder()
|
||||||
|
print("Please wait for the modules to respond...")
|
||||||
|
devs = inst.find()
|
||||||
|
if devs:
|
||||||
|
for d in devs:
|
||||||
|
print(d)
|
||||||
|
else:
|
||||||
|
print("No devices detected")
|
@ -0,0 +1,17 @@
|
|||||||
|
"""
|
||||||
|
Sample script to detect all SQLServer instances
|
||||||
|
"""
|
||||||
|
|
||||||
|
from network.sqlserver import SqlServerFinder
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
|
||||||
|
inst = SqlServerFinder()
|
||||||
|
print("Please wait for the servers to respond...")
|
||||||
|
srvs = inst.find()
|
||||||
|
if srvs:
|
||||||
|
for s in srvs:
|
||||||
|
print(s)
|
||||||
|
else:
|
||||||
|
print("No servers detected")
|
@ -0,0 +1,22 @@
|
|||||||
|
"""
|
||||||
|
Sample script to fetch all access levels
|
||||||
|
"""
|
||||||
|
|
||||||
|
from net2xs import Net2XS
|
||||||
|
|
||||||
|
# Operator id 0 is System Engineer
|
||||||
|
OPERATOR_ID = 0
|
||||||
|
# Default Net2 password
|
||||||
|
OPERATOR_PWD = "net2"
|
||||||
|
# When running on the machine where Net2 is installed
|
||||||
|
NET2_SERVER = "localhost"
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
|
||||||
|
with Net2XS(NET2_SERVER) as net2:
|
||||||
|
# Authenticate
|
||||||
|
net2.authenticate(OPERATOR_ID, OPERATOR_PWD)
|
||||||
|
# Show all access levels and their details
|
||||||
|
for al in net2.get_py_access_levels():
|
||||||
|
print(al)
|
@ -0,0 +1,21 @@
|
|||||||
|
"""
|
||||||
|
Sample script to fetch all cards
|
||||||
|
"""
|
||||||
|
|
||||||
|
from net2xs import Net2XS
|
||||||
|
|
||||||
|
# Operator id 0 is System Engineer
|
||||||
|
OPERATOR_ID = 0
|
||||||
|
# Default Net2 password
|
||||||
|
OPERATOR_PWD = "net2"
|
||||||
|
# When running on the machine where Net2 is installed
|
||||||
|
NET2_SERVER = "localhost"
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
|
||||||
|
with Net2XS(NET2_SERVER) as net2:
|
||||||
|
# Authenticate
|
||||||
|
net2.authenticate(OPERATOR_ID, OPERATOR_PWD)
|
||||||
|
# Obtain all Net2 cards
|
||||||
|
print(Net2XS.dataset_to_str(net2.get_cards()))
|
@ -0,0 +1,22 @@
|
|||||||
|
"""
|
||||||
|
Sample script to fetch all time zones
|
||||||
|
"""
|
||||||
|
|
||||||
|
from net2xs import Net2XS
|
||||||
|
|
||||||
|
# Operator id 0 is System Engineer
|
||||||
|
OPERATOR_ID = 0
|
||||||
|
# Default Net2 password
|
||||||
|
OPERATOR_PWD = "net2"
|
||||||
|
# When running on the machine where Net2 is installed
|
||||||
|
NET2_SERVER = "localhost"
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
|
||||||
|
with Net2XS(NET2_SERVER) as net2:
|
||||||
|
# Authenticate
|
||||||
|
net2.authenticate(OPERATOR_ID, OPERATOR_PWD)
|
||||||
|
# Show all time zones and their details
|
||||||
|
for tz in net2.get_py_time_zones():
|
||||||
|
print(tz)
|
@ -0,0 +1,47 @@
|
|||||||
|
"""
|
||||||
|
Sample script to demonstrate inheritance
|
||||||
|
"""
|
||||||
|
|
||||||
|
from net2xs import Net2XS
|
||||||
|
|
||||||
|
# Operator id 0 is System Engineer
|
||||||
|
OPERATOR_ID = 0
|
||||||
|
# Default Net2 password
|
||||||
|
OPERATOR_PWD = "net2"
|
||||||
|
# When running on the machine where Net2 is installed
|
||||||
|
NET2_SERVER = "localhost"
|
||||||
|
|
||||||
|
|
||||||
|
class MyNet2XS(Net2XS):
|
||||||
|
"""Inherited class for additional functionality
|
||||||
|
"""
|
||||||
|
global Net2XS
|
||||||
|
|
||||||
|
def get_current_user_id(self):
|
||||||
|
"""Return logged on user id
|
||||||
|
"""
|
||||||
|
# Place a lock, for thread safety
|
||||||
|
# (if you don't have threads you kan skip this)
|
||||||
|
with Net2XS._lock:
|
||||||
|
# Basic check if client connection is valid
|
||||||
|
self._check_client()
|
||||||
|
return self._client.CurrentUserID
|
||||||
|
|
||||||
|
def get_client_members(self):
|
||||||
|
"""Return all Net2 client object members using the introspective
|
||||||
|
Python dir function
|
||||||
|
"""
|
||||||
|
with Net2XS._lock:
|
||||||
|
self._check_client()
|
||||||
|
return dir(self._client)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
|
||||||
|
with MyNet2XS(NET2_SERVER) as net2:
|
||||||
|
# Authenticate
|
||||||
|
net2.authenticate(OPERATOR_ID, OPERATOR_PWD)
|
||||||
|
# Show current user id
|
||||||
|
print("Current used id:", net2.get_current_user_id())
|
||||||
|
# Show all members
|
||||||
|
print("Net2 client members:", net2.get_client_members())
|
@ -0,0 +1,48 @@
|
|||||||
|
"""
|
||||||
|
Sample script to modify a user.
|
||||||
|
It assumes that a John Doe user exists (see add_user sample).
|
||||||
|
"""
|
||||||
|
|
||||||
|
from net2xs import Net2XS
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
|
||||||
|
# Operator id 0 is System Engineer
|
||||||
|
OPERATOR_ID = 0
|
||||||
|
# Default Net2 password
|
||||||
|
OPERATOR_PWD = "net2"
|
||||||
|
# When running on the machine where Net2 is installed
|
||||||
|
NET2_SERVER = "localhost"
|
||||||
|
|
||||||
|
# First name
|
||||||
|
FIRST_NAME = "John"
|
||||||
|
# Surname
|
||||||
|
SUR_NAME = "Doe"
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
|
||||||
|
with Net2XS(NET2_SERVER) as net2:
|
||||||
|
# Authenticate
|
||||||
|
net2.authenticate(OPERATOR_ID, OPERATOR_PWD)
|
||||||
|
|
||||||
|
# Get user id
|
||||||
|
user_id = net2.get_user_id_by_name((FIRST_NAME, SUR_NAME))
|
||||||
|
|
||||||
|
print("Found user id %d" % (user_id))
|
||||||
|
|
||||||
|
# Found a valid user id
|
||||||
|
if user_id >= 0:
|
||||||
|
|
||||||
|
# Modify expiration date
|
||||||
|
res = net2.modify_user(
|
||||||
|
user_id=user_id,
|
||||||
|
expiry_date=datetime(2022, 12, 31))
|
||||||
|
|
||||||
|
if res:
|
||||||
|
print("Success")
|
||||||
|
else:
|
||||||
|
print("Failure")
|
||||||
|
|
||||||
|
else:
|
||||||
|
print("Failed to find user %s %s" % (FIRST_NAME, SUR_NAME))
|
@ -0,0 +1,48 @@
|
|||||||
|
"""
|
||||||
|
Sample script to monitor acu
|
||||||
|
"""
|
||||||
|
|
||||||
|
from time import sleep
|
||||||
|
from net2xs import Net2XS
|
||||||
|
|
||||||
|
|
||||||
|
# Operator id 0 is System Engineer
|
||||||
|
OPERATOR_ID = 0
|
||||||
|
# Default Net2 password
|
||||||
|
OPERATOR_PWD = "net2"
|
||||||
|
# When running on the machine where Net2 is installed
|
||||||
|
NET2_SERVER = "localhost"
|
||||||
|
# Acu to monitor
|
||||||
|
ACU_ADDRESS = 1219195
|
||||||
|
|
||||||
|
|
||||||
|
def handle_acu_event(sender, event_view):
|
||||||
|
"""Handler for ACU event
|
||||||
|
"""
|
||||||
|
print("-----------------------")
|
||||||
|
print("address=", event_view.Address)
|
||||||
|
print("card=", event_view.CardNumber)
|
||||||
|
print("department=", event_view.Department)
|
||||||
|
print("userid=", event_view.UserId)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
|
||||||
|
with Net2XS(NET2_SERVER) as net2:
|
||||||
|
# Authenticate
|
||||||
|
net2.authenticate(OPERATOR_ID, OPERATOR_PWD)
|
||||||
|
|
||||||
|
# Set function to handle ACU event
|
||||||
|
net2.on_acu_event = handle_acu_event
|
||||||
|
|
||||||
|
res = net2.monitor_acu(ACU_ADDRESS)
|
||||||
|
if not res:
|
||||||
|
print("Failed to monitor ACU %d" % (ACU_ADDRESS))
|
||||||
|
else:
|
||||||
|
print("Monitoring ACU %d; press <ctrl>C to quit" % (ACU_ADDRESS))
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
sleep(1)
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
break
|
||||||
|
net2.stop_monitoring_acu(ACU_ADDRESS)
|
@ -0,0 +1,60 @@
|
|||||||
|
"""
|
||||||
|
Sample script to hold all known doors open for a few seconds.
|
||||||
|
Also shows how to use the logger function.
|
||||||
|
"""
|
||||||
|
import time
|
||||||
|
|
||||||
|
from net2xs import Net2XS
|
||||||
|
from pylog4net import Log4Net
|
||||||
|
|
||||||
|
# Operator id 0 is System Engineer
|
||||||
|
OPERATOR_ID = 0
|
||||||
|
# Default Net2 password
|
||||||
|
OPERATOR_PWD = "net2"
|
||||||
|
# When running on the machine where Net2 is installed
|
||||||
|
NET2_SERVER = "localhost"
|
||||||
|
|
||||||
|
|
||||||
|
def get_doors(net2):
|
||||||
|
"""Obtain a list of all known doors
|
||||||
|
"""
|
||||||
|
res = []
|
||||||
|
dataset = net2.get_doors()
|
||||||
|
if dataset and dataset.Tables.Count > 0:
|
||||||
|
for row in dataset.Tables[0].Rows:
|
||||||
|
res.append(row.Address)
|
||||||
|
return res
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
|
||||||
|
# Create logger object
|
||||||
|
logger = Log4Net.get_logger('open_all_doors')
|
||||||
|
|
||||||
|
with Net2XS(NET2_SERVER) as net2:
|
||||||
|
# Authenticate
|
||||||
|
net2.authenticate(OPERATOR_ID, OPERATOR_PWD)
|
||||||
|
# Get list off door addresses
|
||||||
|
doors = get_doors(net2)
|
||||||
|
# Open each door
|
||||||
|
for door in doors:
|
||||||
|
if not net2.hold_door_open(door):
|
||||||
|
logger.Error(
|
||||||
|
"Failed to hold door %d open: %s." %
|
||||||
|
(door, net2.last_error_message))
|
||||||
|
else:
|
||||||
|
logger.Info("Set door %d open." % (door))
|
||||||
|
|
||||||
|
logger.Info("Now all doors are open...")
|
||||||
|
time.sleep(3)
|
||||||
|
|
||||||
|
# Close each door
|
||||||
|
for door in doors:
|
||||||
|
if not net2.close_door(door):
|
||||||
|
logger.Error(
|
||||||
|
"Failed to close door %d: %s." %
|
||||||
|
(door, net2.last_error_message))
|
||||||
|
else:
|
||||||
|
logger.Info("Set door %d closed." % (door))
|
||||||
|
|
||||||
|
logger.Info("Now all doors are closed again")
|
@ -0,0 +1,21 @@
|
|||||||
|
"""
|
||||||
|
Sample script to fetch a dataset with all active users
|
||||||
|
"""
|
||||||
|
|
||||||
|
from net2xs import Net2XS
|
||||||
|
|
||||||
|
# Operator id 0 is System Engineer
|
||||||
|
OPERATOR_ID = 0
|
||||||
|
# Default Net2 password
|
||||||
|
OPERATOR_PWD = "net2"
|
||||||
|
# When running on the machine where Net2 is installed
|
||||||
|
NET2_SERVER = "localhost"
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
|
||||||
|
with Net2XS(NET2_SERVER) as net2:
|
||||||
|
# Authenticate
|
||||||
|
net2.authenticate(OPERATOR_ID, OPERATOR_PWD)
|
||||||
|
# Obtain all Net2 users
|
||||||
|
print(Net2XS.dataset_to_str(net2.get_users()))
|
@ -0,0 +1,21 @@
|
|||||||
|
import sys
|
||||||
|
import os
|
||||||
|
|
||||||
|
# Running as py2exe
|
||||||
|
if hasattr(sys, 'frozen'):
|
||||||
|
DEBUG = False
|
||||||
|
BASE_DIR = os.path.split(sys.executable)[0]
|
||||||
|
# Running as interpreter
|
||||||
|
else:
|
||||||
|
DEBUG = True
|
||||||
|
BASE_DIR = '.'
|
||||||
|
|
||||||
|
# Config file to use for dotnet stuff
|
||||||
|
CONFIG_FILE = os.path.join(
|
||||||
|
BASE_DIR, 'Net2Scripting.exe.config')
|
||||||
|
|
||||||
|
# Generic lib dir
|
||||||
|
LIB_DIR = os.path.join(BASE_DIR, 'libs')
|
||||||
|
|
||||||
|
# Version
|
||||||
|
VERSION = '4.0'
|
@ -0,0 +1,35 @@
|
|||||||
|
import os
|
||||||
|
|
||||||
|
from distutils.core import setup
|
||||||
|
from glob import glob
|
||||||
|
import py2exe
|
||||||
|
|
||||||
|
# Data files (resources)
|
||||||
|
data_files = [('.', ['Net2Scripting.exe.config']),
|
||||||
|
('.', glob(r'../runtime/*.*')),
|
||||||
|
('libs/log4netdll', glob(r'libs/log4netdll/*.*')),
|
||||||
|
('libs/paxton', glob(r'libs/paxton/*.*')),
|
||||||
|
('resources', glob(r'../resources/*.*')),
|
||||||
|
('docs', glob(r'../docs/*.*')),
|
||||||
|
('samples', glob(r'samples/*.py'))]
|
||||||
|
|
||||||
|
# Source files (python modules)
|
||||||
|
source_files = [os.path.splitext(os.path.basename(f))[0] for f in glob('*.py')]
|
||||||
|
|
||||||
|
setup(
|
||||||
|
# basic console exe
|
||||||
|
console=[{'script': 'Net2Scripting.py',
|
||||||
|
'icon_resources': [(1, '../resources/Net2Scripting.ico')]}],
|
||||||
|
py_modules=source_files,
|
||||||
|
data_files=data_files,
|
||||||
|
# zipfile=None,
|
||||||
|
# options={'py2exe': {'bundle_files': 1, 'compressed': True}}
|
||||||
|
options={
|
||||||
|
'py2exe': {
|
||||||
|
'dist_dir': '../dist',
|
||||||
|
},
|
||||||
|
'build': {
|
||||||
|
'build_base': '../build'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
Loading…
Reference in New Issue