ich hab hier ein ziemliches Problem. Wir nutzen in der Firma lauter Lancom-Geräte (Router, Switches usw). Nun hat unser Chef ein paar ePaper-Displays als Raumbeschilderung angeschafft, die ich einrichten soll. Die Dinger werden im Grunde über eine eigene Serversoftware angesteuert,welche Ihrerseits die Daten vom Exchange-Server holen soll, auf dem das zugehörige Raumpostfach liegt. Wer Lancom kennt, weiß, dass diese Firma prinzipiell IMMER den für den User schlechtesten Weg einschlägt, bei Lancom ist *gar nichts* einfach. Entsprechend dieser Richtlinie ist auch die Kommunikation zwischen ePaper-Server und Exchange-Server aufgebaut.
Heißt im vorliegenden Fall: Ein Script für IronPython dient als Schnittstelle zwischen beiden Servern und schaut regelmäßig auf dem ePaper-Server nach neuen Daten für die Displays. Warum auch ein schönes einfaches Programm nutzen,wenn man den Nutzer auch mit ellenlangen Skripten quälen kann ? Dumm nur,wenn derjenige, der den Kram einrichten soll, von Python ungefähr soviel Ahnung hat wie ein Walross vom jodeln. Ich bräuchte also mal eure Hilfe.
Ich hab hier also 2 Skripte, von denen eins scheinbar permanent den Exchange-Server ausliest und die Daten an Script 2 übergibt, welches es an den ePaper-Server gibt.
Hier mal das erste Script, weil schon das mein Problem ist:
Code: Alles auswählen
# coding: utf-8
#
# Update LANCOM Wireless Displays with room reservations from Microsoft Exchange
# written by Martin Gordziel for LANCOM Systems GmbH in 2014
#
# prerequisites:
#
# * install ironpython from http://ironpython.net/
#
# * install "Microsoft Exchange Web Services Managed API" (use websearch) and include it in the PYTHONPATH environment variable.
# If not set in IRONPYTHONPATH environment, uncomment and change the following path to where EWS got installed
# PATH_TO_EXCHANGE_WEB_SERVICES_MANAGED_API = r'C:\Program Files\Microsoft\Exchange\Web Services\2.2'
#
# Code starts here
#
# Python imports
import os
import sys
import signal
import time
import datetime
import traceback
import argparse
import json
import xml.etree.ElementTree as ET
import xml.sax.saxutils as saxutils
import httplib
import hashlib
import logging
logger = logging.getLogger(__name__)
# .NET imports
try:
#if PATH_TO_EXCHANGE_WEB_SERVICES_MANAGED_API is set, append it to path
sys.path.append(PATH_TO_EXCHANGE_WEB_SERVICES_MANAGED_API)
except:
pass
import clr
clr.AddReferenceToFile('Microsoft.Exchange.WebServices.dll')
import Microsoft.Exchange.WebServices.Data as EWSData
import System.DateTime
def print_console(text=''):
print text
class WirelessDisplayServer(object):
"""
implements HTTP methods for the LANCOM Wireless ePaper Display server
.post(id, xml) sends XML to server
.push_Label(label) takes a Label class instance and sends it to server
.get_transaction_updatestatus(id) returns a dictionary with the updatestatus of a display
"""
def __init__(self, address=None, port=None):
self.server_address = ''
self.server_port = '8001'
if address is not None:
self.server_address = address
if port is not None:
self.server_port = port
def post(self, label_id, xml):
"""
post XML to server
returns:
False if it failed
Transaction_ID if POST was successful and returned the ID
"""
rc = False
h = httplib.HTTPConnection(self.server_address, self.server_port, timeout = 10)
headers = {'Content-Type': 'application/xml'}
try:
h.request('POST', '/service/task', xml, headers)
response = h.getresponse()
if response.status == 200:
xml_response = response.read()
root = ET.fromstring(xml_response)
if root.tag=='Transaction':
rc = root.attrib['id']
logger.debug('pushed via {0}:{1} to {2} under Transaction ID {3}'.format(self.server_address, self.server_port, label_id, rc))
else:
logger.warning('pushed via {0}:{1} to {2}, but got no Transaction ID'.format(self.server_address, self.server_port, label_id))
else:
logger.warning('got response status {} to POST'.format(response.status))
except:
logger.warning('could not communicate with server:')
logger.warning(traceback.format_exc())
h.close()
return rc
def push_Label(self, label):
return self.post(label.label_id, label.get_xml())
def get_transaction_updatestatus(self, transaction_id):
"""
get the status of a transaction (e.g 48)
"""
info = dict()
h = httplib.HTTPConnection(self.server_address, self.server_port, timeout = 10)
try:
h.request('GET', '/service/updatestatus/transaction/{0}'.format(transaction_id))
response = h.getresponse()
if response.status == 200:
xml_response = response.read()
root = ET.fromstring(xml_response)
if root.tag=='UpdateStatusPagedResult':
for child in root:
if child.tag=='UpdateStatus':
info.update(child.attrib)
info.update(dict((el.tag, el.text) for el in child))
logger.info('got Transaction {0} UpdateStatus {1}'.format(transaction_id, ', '.join(['{0}={1}'.format(key, value) for key, value in info.items()])))
break
else:
logger.warning('extpected result of type UpdateStatusPagedResult but got {0}'.format(root.tag))
else:
logger.warning('got HTTP response {0}'.format(response.status))
except:
logger.warning('could not communicate with server:')
logger.warning(traceback.format_exc())
h.close()
return info
class ConferenceLabel(object):
"""
LANCOM Label for Conference Rooms
label_id and template get filled from the config
room_name and the fields dictionary get filled from the calendar
.get_xml() returns the XML which can be POSTed to server
.get_hash() returns a hash of the XML, e.g. to check if the XML has changed
has its own .__str__() to allow a pretty "print my_label_instance"
"""
def __init__(self):
self.label_id = ''
self.template = ''
self.fields = {'date': '', 'time1': '', 'purpose1': '', 'chair1': '', 'time2': '', 'purpose2': '', 'chair2': ''}
self.room_name = ''
def get_xml(self):
roomName = saxutils.quoteattr(self.room_name)
fields = ''.join([' <field key="{0}" value={1}/>\n'.format(key, saxutils.quoteattr(value)) for key, value in self.fields.iteritems()])
xml = '''<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<TaskOrder title="templated data for label {0}">
<TemplateTask labelId="{0}" template="{1}">
<room roomName={2}>
{3} </room>
</TemplateTask>
</TaskOrder>'''.format(self.label_id, self.template, roomName, fields).encode('utf-8')
logger.debug('generated XML:\n'+xml)
return xml
def get_hash(self):
h = hashlib.sha1(self.get_xml()).hexdigest()
logger.debug('hash value is: "{}"'.format(h))
return h
def __str__(self):
pretty = []
pretty.append('{}: {} @ {}'.format(self.label_id, self.room_name, self.fields['date']))
line = ' '
if len(self.fields['time1'])>0: line += '{}: '.format(self.fields['time1'])
line += self.fields['purpose1']
if len(self.fields['chair1'])>0: line += ' ({})'.format(self.fields['chair1'] )
pretty.append(line)
line = ' '
if len(self.fields['time2'])>0: line += '{}: '.format(self.fields['time2'])
line += self.fields['purpose2']
if len(self.fields['chair2'])>0: line += ' ({})'.format(self.fields['chair2'] )
if len(line.strip())>0: pretty.append(line)
return '\n'.join(pretty)
class Exchange(object):
"""
Connect to Exchange server via the managed API
"""
def __init__(self):
# exchange version has to be known to communicate correctly with server, see .init_service() for choices
self.exchange_version = ''
# exchange endpoint definition
self.exchange_autodiscover_url_from_email = True
# if True, specify:
self.exchange_autodiscover_email = ''
# if False, specify:
self.exchange_url = ''
# exchange credentials
self.exchange_default_credentials = True
# if True, the credentials of the current user are used
# if False, specify:
self.exchange_user = ''
self.exchange_password = ''
self.exchange_domain = ''
def init_service(self):
logger.debug('starting init_service')
# exchange version
exchange_versions = {
'Exchange2007_SP1': EWSData.ExchangeVersion.Exchange2007_SP1,
'Exchange2010': EWSData.ExchangeVersion.Exchange2010,
'Exchange2010_SP1': EWSData.ExchangeVersion.Exchange2010_SP1,
'Exchange2010_SP2': EWSData.ExchangeVersion.Exchange2010_SP2,
'Exchange2013': EWSData.ExchangeVersion.Exchange2013,
'Exchange2013_SP1': EWSData.ExchangeVersion.Exchange2013_SP1,
}
if self.exchange_version in exchange_versions:
self.service = EWSData.ExchangeService(exchange_versions[self.exchange_version])
else:
logger.critical('Exchange Version not known')
sys.exit(1)
logger.debug('setting credentials')
if self.exchange_default_credentials:
# Connect by using the default credentials of the authenticated user
self.service.UseDefaultCredentials = True
else:
# Connect by using the specified credentials
self.service.UseDefaultCredentials = False
self.service.Credentials = EWSData.WebCredentials(self.exchange_user, self.exchange_password, self.exchange_domain)
logger.debug('setting url')
if self.exchange_autodiscover_url_from_email:
self.service.AutodiscoverUrl(self.exchange_autodiscover_email)
else:
self.service.Url = System.Uri(self.exchange_url)
print_console('using to Exchange at {}'.format(self.service.Url))
logger.debug('finished init_service')
def get_room_usage(self, room_email, start_date=None, end_date=None):
"""
query the room usage for the specified room (given as room_email)
important: the room mailbox must be configured to NOT delete the subject and NOT to add the organizer to the subject
the date range can be specified, otherwise the complete current day is used.
note: meetings spanning more than one day are not supported.
"""
logger.debug('get room usage for room mailbox {}'.format(room_email))
# open room mailbox and go to calendar
try:
mailbox = EWSData.Mailbox(room_email)
folderId = EWSData.FolderId(EWSData.WellKnownFolderName.Calendar, mailbox)
calendar = EWSData.CalendarFolder.Bind(self.service, folderId)
except:
logger.warning('could not access room mailbox {}: {}'.format(room_email, traceback.format_exc()))
return None
# build CalendarView query for date range and limit results to the wanted members
if start_date is None:
start_date = System.DateTime.Now.Date
if end_date is None:
end_date = System.DateTime.Now.AddDays(1).Date
logger.debug('date range from {} to {}'.format(start_date.ToString(), end_date.ToString()))
calendar_view = EWSData.CalendarView(start_date, end_date, 100)
calendar_view.PropertySet = EWSData.PropertySet(
EWSData.AppointmentSchema.Start,
EWSData.AppointmentSchema.End,
EWSData.AppointmentSchema.Subject,
EWSData.AppointmentSchema.Organizer)
# perform query and fill room_usage dictionary
appointments = calendar.FindAppointments(calendar_view)
room_usage = []
for app in appointments:
try:
meeting = {'start_DateTime': app.Start, 'end_DateTime': app.End, 'subject': app.Subject.ToString(), 'organizer': app.Organizer.Name.ToString()}
room_usage.append(meeting)
logger.debug('-> '+', '.join('{}: {}'.format(k,v) for k,v in meeting.iteritems()))
except:
logger.warning('skipped malformed appointment: '+traceback.format_exc())
logger.info('got {} meetings for room {} in range {} - {}'.format(len(room_usage), room_email, start_date, end_date))
return room_usage
def get_next_meetings(self, room_email_list):
"""
loop through all rooms (all queries use the same time range)
"""
now = System.DateTime.Now
start_date = now.Date
end_date = now.AddDays(1).Date
next_meetings = {}
for room_email in room_email_list:
room_usage = self.get_room_usage(room_email, start_date, end_date)
if room_usage is not None:
next_usage = []
for usage in room_usage:
if usage['end_DateTime']>now:
next_usage.append(usage)
next_meetings[room_email] = {'date': start_date.ToString('d'), 'details': next_usage}
return next_meetings
class Updater(object):
"""
Update the LANCOM Wireless ePaper Displays
"""
def __init__(self):
self.config = None # dict set by load_config
self.args = None # instance set by argument parser
self.label_hashes = {} # keep hashes of XML sent to server
self.label_transactions = {} # unfinished transactions
self.stats = {'skipped': 0, 'updates (new)': 0, 'updates finished successfully': 0, 'updates (repeated)': 0}
def load_config(self, file):
"""
load a JSON config file
return success as True/False
"""
if os.path.exists(file):
with open(file, 'r') as f:
try:
self.config = json.load(f)
return True
except:
logger.critical('Could not parse the config file: '+traceback.format_exc())
self.config = None
else:
self.config = None
return False
def save_config(self, file):
"""
Save config as JSON
"""
with open(file, 'w') as f:
f.write(json.dumps(self.config, sort_keys=True, indent=4, separators=(',',':')))
def connect_exchange(self):
"""
configure a Exchange instance based on the parameters in the config file
"""
self.ews = Exchange()
for element in ['version', 'default_credentials', 'user', 'password', 'domain', 'autodiscover_url_from_email', 'autodiscover_email', 'url']:
if element in self.config['exchange_server']:
setattr(self.ews, 'exchange_'+element, self.config['exchange_server'][element])
else:
setattr(self.ews, 'exchange_'+element, '')
self.ews.init_service()
def connect_display_server(self):
"""
configure a WirelessDisplayServer instance based on the parameters in the config file
"""
self.wds = WirelessDisplayServer()
for element in ['address', 'port']:
if element in self.config['wireless_display_server']:
setattr(self.wds, 'server_'+element, self.config['wireless_display_server'][element])
else:
setattr(self.wds, 'server_'+element, '')
def update(self):
"""
update all displays (from config/displays)
collects data from Exchange, and decides if a Update should be performed with the Wireless Display Server
"""
print_console('{}: updating labels...'.format(datetime.datetime.now().strftime(('%Y-%m-%d %H:%M:%S'))))
next_meetings = self.ews.get_next_meetings(display['exchange_room_mailbox'] for display in self.config['displays'])
for display_data in self.config['displays']:
if display_data['exchange_room_mailbox'] in next_meetings:
meeting_date = next_meetings[display_data['exchange_room_mailbox']]['date']
meeting_details = next_meetings[display_data['exchange_room_mailbox']]['details']
# fill Label instance
label = ConferenceLabel()
label.template = self.config['conference_label']['template']
label.label_id = display_data['display_id']
label.room_name = display_data['display_name'].decode('utf-8') # json data comes in utf-8
label.fields['date'] = meeting_date
logger.debug('processing label {} from {}: date {}, length of details {}, {}'.format(label.label_id, display_data['exchange_room_mailbox'], repr(meeting_date), len(meeting_details), meeting_details))
if len(meeting_details)>0:
logger.debug('added first meeting to label {} from {}'.format(label.label_id, display_data['exchange_room_mailbox']))
label.fields['time1'] = '{}-{}'.format(meeting_details[0]['start_DateTime'].ToString('t'), meeting_details[0]['end_DateTime'].ToString('t'))
label.fields['purpose1'] = meeting_details[0]['subject']
label.fields['chair1'] = meeting_details[0]['organizer']
if len(meeting_details)>1:
logger.debug('added second meeting to label {} from {}'.format(label.label_id, display_data['exchange_room_mailbox']))
label.fields['time2'] = '{}-{}'.format(meeting_details[1]['start_DateTime'].ToString('t'), meeting_details[1]['end_DateTime'].ToString('t'))
label.fields['purpose2'] = meeting_details[1]['subject']
label.fields['chair2'] = meeting_details[1]['organizer']
else:
logger.debug('no meetings for label {} from {}'.format(label.label_id, display_data['exchange_room_mailbox']))
label.fields['purpose1'] = self.config['conference_label']['no_new_data_message'].decode('utf-8') # json data comes in utf-8
# decide if update should be performed
do_update = True
if self.args.update=='always':
self.stats['updates (new)'] += 1
elif self.args.update in ['newdata', 'required']:
# build hash of label data
if label.label_id not in self.label_hashes:
self.label_hashes[label.label_id] = ''
hash = label.get_hash()
logger.debug('compare against stored hash value: "{}"'.format(self.label_hashes[label.label_id]))
# if hash has changed, do update for both 'newdata' and 'required'
if self.label_hashes[label.label_id] != hash:
do_update = True
self.stats['updates (new)'] += 1
logger.info('update label because hash value of data has changed')
elif self.args.update=='required':
# do update if last transaction was not successful. but if transaction is still pending, do nothing
if display_data['display_id'] in self.label_transactions:
update_status = self.wds.get_transaction_updatestatus(self.label_transactions[display_data['display_id']])
if update_status['Status']=='SUCCESSFUL':
do_update = False
del self.label_transactions[display_data['display_id']]
self.stats['updates finished successfully'] += 1
print_console('{}: last update went in state SUCCESSFUL'.format(display_data['display_id']))
elif update_status['Status']=='WAITING':
do_update = False
print_console('{}: last update is in state WAITING'.format(display_data['display_id']))
else:
do_update = True
print_console('{}: repeating update, previous one is in state {}'.format(display_data['display_id'], update_status['Status']))
self.stats['updates (repeated)'] += 1
else:
do_update = False
logger.debug('update skipped, hash has not changed and no ongoing transaction')
self.label_hashes[label.label_id] = hash
if do_update:
print_console(label)
transaction = self.wds.push_Label(label)
if transaction is False:
print_console('{}: update failed, communication with wireless display server failed'.format(display_data['display_id']))
self.label_hashes[label.label_id] = '' # invalidate hash
else:
self.label_transactions[display_data['display_id']] = transaction
else:
self.stats['skipped'] += 1
print_console('{}: skipped update (update not necessary)'.format(display_data['display_id']))
else:
print_console('{}: skipped update, could not get reservations from Exchange'.format(display_data['display_id']))
def run(self, fullhour=False):
"""
Run the update loop
If specified, wait for fullhour before starting the loops
Ctrl-c breaks the update loop and prints statistics
"""
self.connect_exchange()
self.connect_display_server()
self.start = datetime.datetime.now()
if self.args.fullhour:
# wait for full hour before starting update loop
prewait = 3600 - (self.start.minute*60 + self.start.second)
print_console('waiting for full hour ({} seconds)'.format(prewait))
time.sleep(prewait)
self.start = datetime.datetime.now()
try:
# update loop
loop=1
while True:
print_console()
self.update()
now = datetime.datetime.now()
wait = self.start+datetime.timedelta(seconds = self.args.interval*60*loop)-now
wait_until = now+wait
print_console('finished, next update at {}'.format(wait_until.strftime('%Y-%m-%d %H:%M:%S')))
wait_seconds = wait.total_seconds()
if wait_seconds>0:
time.sleep(wait_seconds)
loop += 1
except (KeyboardInterrupt):
# print stats and finish
print_console()
for k in ['updates (new)', 'updates (repeated)', 'updates finished successfully', 'skipped']:
v = self.stats[k.strip()]
if v>0:
print_console('{0:30s} : {1:8d}'.format(k, v))
except:
raise
if __name__=='__main__':
# configure argument parser
parser = argparse.ArgumentParser(description='Update LANCOM Wireless Displays with room reservations from Microsoft Exchange')
parser.add_argument('config', help='filename of the configuration in JSON format (use UTF-8 without BOM)')
parser.add_argument('-i', '--interval', type=int, default=5, help='interval (in minutes) between updates')
parser.add_argument('-f', '--fullhour', action='store_true', help='wait for fullhour before starting update loop')
parser.add_argument('-u', '--update', choices=['always', 'newdata', 'required'], default='required', help='each interval: update always, update those with new data, or update those which require an update (new data/last transmission not successful')
parser.add_argument('-d', '--debuglevel', choices=['debug', 'info', 'warning', 'error', 'critical'], default='warning', help='debuglevel for logging')
parser.add_argument('-q', '--quiet', action='store_true', help='quiet mode, no console output')
# configure Updater and start the loop
u = Updater()
u.args = parser.parse_args()
if u.args.quiet:
def print_console(text=''): pass
logging.basicConfig(level = getattr(logging, u.args.debuglevel.upper()), format = '%(asctime)s - %(levelname)s = %(message)s')
if u.load_config(u.args.config):
u.run()
else:
print 'could not load config: {}'.format(u.args.config)
"Line 40 in (module) IO Error: System.IO.IOException: Could not add references to assemby Microsoft.Exchange.WebServices.dll
Diese Datei liegt unter C:\Program Files\Microsoft\Exchange\Web Services\2.2. Ich vermute,dass ich da noch diesen Pfad irgendwo als Variable eingeben muss. Aber wo bitte soll ich das tun ? Als normale Umgebungsvariable in Windows funktionierts jedenfalls nicht.