...
 
Commits (17)
......@@ -14,11 +14,10 @@ function(CopyFilesFromSource TARGET SOURCE_FILES)
if (NOT IN_SOURCE_BUILD)
add_custom_target(${TARGET} ALL SOURCES ${SOURCE_FILES})
foreach (infile ${SOURCE_FILES})
string(REPLACE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} outfile ${infile})
#message("-- staging for copy: ${CMAKE_CURRENT_SOURCE_DIR}/${infile} -> ${CMAKE_CURRENT_BINARY_DIR}/${outfile}")
#message("-- staging for copy: ${CMAKE_CURRENT_SOURCE_DIR}/${infile} -> ${CMAKE_CURRENT_BINARY_DIR}/${infile}")
add_custom_command(
TARGET ${TARGET}
COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/${infile} ${outfile}
COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/${infile} ${infile}
VERBATIM
)
endforeach()
......
......@@ -16,6 +16,8 @@ foreach (infile ${INIT_FILES})
endforeach (infile)
CopyFilesFromSource(augeas_lens "agocontrol.aug")
add_subdirectory (conf.d)
add_subdirectory (schema.d)
add_subdirectory (sysvinit)
......
cmake_minimum_required (VERSION 3.0)
file (GLOB CONFD_FILES *.in)
file (GLOB CONFD_FILES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *.in)
foreach (infile ${CONFD_FILES})
string(REGEX REPLACE ".in$" "" outfile ${infile})
string(REGEX REPLACE ".*/" "" outfile ${outfile})
configure_file(
"${infile}"
"${CMAKE_CURRENT_BINARY_DIR}/${outfile}"
@ONLY
)
LIST(APPEND CONFD_FILES_DONE ${CMAKE_CURRENT_BINARY_DIR}/${outfile})
LIST(APPEND CONFD_FILES_DONE ${outfile})
endforeach (infile)
file (GLOB CONFD_FILES *.conf)
install (FILES ${CONFD_FILES_DONE} ${CONFD_FILES} DESTINATION ${CONFDIR}/conf.d)
file (GLOB CONFD_FILES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *.conf)
CopyFilesFromSource(conf_files "${CONFD_FILES}")
InstallFiles(${CONFDIR}/conf.d "${CONFD_FILES_DONE};${CONFD_FILES}")
[system]
uuid=00000000-0000-0000-000000000000
# mqtt or qpid
messaging=qpid
# address to broker, hostname[:port]
broker=localhost
# when you change username and/or password, you also need to adjust the qpid sasl db to reflect the change
username=agocontrol
password=letmein
units=SI
# Please see http://wiki.agocontrol.com/index.php/Logging for more details on Logging
......@@ -11,3 +19,28 @@ units=SI
log_method=syslog
# log levels: TRACE, DEBUG, INFO, WARNING, ERROR, FATAL
log_level=INFO
[loggers]
# The main log_level controls the overall logging level.
# It is then possible to limit different loggers (python) / channels (C++) to a higher level.
# For example, if log_level is set to INFO, nothing lower than INFO will be logged,
# even if a specific logger is set to TRACE.
# Names are shared between both python & C++ implementations (where applicable).
# The low-level qpid python library is very verbose, so do not log anything lower
# than INFO from that library:
qpid=INFO
# Same goes for low-level mqtt library, both on C++ and Python. Cap it to INFO.
mqtt=INFO
# The following are internal application logging in the AgoClient library
# transport is the abstraction of the MQTT/QPID connectivity, and only deals with simple messaging.
transport = TRACE
# connection is a higher-level interface between each application and the transport
connection = TRACE
# This is the "main" logger for C++, if no specific one is used. It is also the default for all applications.
app = TRACE
#!/usr/bin/env python
from __future__ import print_function
#Returns all or specified directory structure
#@params get: all|plugins|configs|helps|supported
......@@ -30,7 +31,7 @@ def loadMetadatasInDir(d):
items[obj["dir"]] = obj
except Exception as error:
pass
for key in sorted(items.iterkeys()):
for key in sorted(items.keys()):
out.append(items[key])
return out
......@@ -83,9 +84,9 @@ try:
except Exception as e:
#TODO add message to agolog
pass
result['error'] = str(e)
#send output
print "Content-type: application/json\n"
print json.dumps(result)
print("Content-type: application/json\n")
print(json.dumps(result))
#!/usr/bin/env python
from __future__ import print_function
import os
import sys
......@@ -142,6 +144,6 @@ except Exception as e:
pass
#send output
print "Content-type: application/json\n"
print json.dumps(result)
print("Content-type: application/json\n")
print(json.dumps(result))
......@@ -19,5 +19,9 @@ override_dh_auto_configure:
override_dh_auto_build:
dh_auto_build --parallel
# Default runs make test which is very silent in what fails
override_dh_auto_test:
$(MAKE) -j1 check
%:
dh $@ --with python2
......@@ -6,28 +6,29 @@ import time
import logging
import agoclient
from agoclient import agoproto
class AgoSimulator(agoclient.AgoApp):
def message_handler(self, internalid, content):
if "command" in content:
if content["command"] == "on":
print "switching on: " + internalid
print("switching on: " + internalid)
self.connection.emit_event(internalid, "event.device.statechanged", 255, "")
elif content["command"] == "off":
print "switching off: " + internalid
print("switching off: " + internalid)
self.connection.emit_event(internalid, "event.device.statechanged", 0, "")
elif content["command"] == "push":
print "push button: " + internalid
print("push button: " + internalid)
elif content['command'] == 'setlevel':
if 'level' in content:
print "device level changed", content["level"]
print("device level changed", content["level"])
self.connection.emit_event(internalid, "event.device.statechanged", content["level"], "")
else:
return self.connection.response_unknown_command()
return agoproto.response_unknown_command()
return self.connection.response_success()
return agoproto.response_success()
else:
return self.connection.response_bad_parameters()
return agoproto.response_bad_parameters()
def app_cmd_line_options(self, parser):
......@@ -85,11 +86,11 @@ class TestEvent(threading.Thread):
hum = random.randint(20, 75) + random.randint(0,90)/100.0
log.debug("Sending enviromnet changes on sensor 126 (%.2f dgr C, %.2f %% humidity)",
temp, hum)
self.connection.emit_event("126", "event.environment.temperaturechanged", temp, "degC");
self.connection.emit_event("126", "event.environment.humiditychanged", hum, "percent");
self.app.connection.emit_event("126", "event.environment.temperaturechanged", temp, "degC")
self.app.connection.emit_event("126", "event.environment.humiditychanged", hum, "percent")
log.debug("Sending sensortriggered for internal-ID 125, level %d", level)
self.connection.emit_event("125", "event.security.sensortriggered", level, "")
self.app.connection.emit_event("125", "event.security.sensortriggered", level, "")
if (level == 0):
level = 255
......
This diff is collapsed.
This diff is collapsed.
......@@ -194,7 +194,7 @@ class PullStatus(threading.Thread):
else:
self.log.error('PullStatus: Could not get status from light')
except TypeError as e:
self.log.error("PullStatus: Exception occurred in background thread. {}".format(e.message))
self.log.error("PullStatus: Exception occurred in background thread. {}".format(str(e)))
time.sleep(float(self.PollDelay)) # TODO: Calculate ((60-n)/NoDevices)/60
if __name__ == "__main__":
......
This diff is collapsed.
......@@ -8,6 +8,7 @@
#
import agoclient
from agoclient import agoproto
import urllib2
import base64
import picamera
......@@ -33,7 +34,7 @@ def messageHandler(internalid, content):
camera.capture(stream, format='jpeg')
frame = stream.getvalue()
return client.response_success({'image':buffer(base64.b64encode(frame))})
return agoproto.response_success({'image':buffer(base64.b64encode(frame))})
client.add_handler(messageHandler)
......
......@@ -5,6 +5,7 @@
import sys
import agoclient
from agoclient import agoproto
import pylmsserver
import pylmsplaylist
import pylmslibrary
......@@ -199,7 +200,7 @@ def messageHandler(internalid, content):
#check parameters
if not content.has_key("command"):
logging.error('No command specified in content')
return client.response_unknown_command(message='No command specified')
return agoproto.response_unknown_command(message='No command specified')
if internalid==host:
......@@ -208,27 +209,27 @@ def messageHandler(internalid, content):
logging.info("Command ALLON: %s" % internalid)
for player in getPlayers():
player.on()
return client.response_success()
return agoproto.response_success()
elif content["command"]=="alloff":
logging.info("Command ALLOFF: %s" % internalid)
for player in getPlayers():
player.off()
return client.response_success()
return agoproto.response_success()
elif content["command"]=="displaymessage":
if content.has_key('line1') and content.has_key('line2') and content.has_key('duration'):
logging.info("Command DISPLAYMESSAGE: %s" % internalid)
for player in getPlayers():
player.display(content['line1'], content['line2'], content['duration'])
return client.response_success()
return agoproto.response_success()
else:
logging.error('Missing parameters to command DISPLAYMESSAGE')
return client.response_missing_parameters(data={'command': 'displaymessage', 'params': ['line1', 'line2', 'duration']})
return agoproto.response_missing_parameters(data={'command': 'displaymessage', 'params': ['line1', 'line2', 'duration']})
#unhandled command
logging.warn('Unhandled server command')
return client.response_unknown_command(message='Unhandled server command', data=content["command"])
return agoproto.response_unknown_command(message='Unhandled server command', data=content["command"])
else:
......@@ -238,69 +239,69 @@ def messageHandler(internalid, content):
logging.info('Found player: %s' % player)
if not player:
logging.error('Player %s not found!' % internalid)
return client.response_failed('Player "%s" not found!' % internalid)
return agoproto.response_failed('Player "%s" not found!' % internalid)
if content["command"] == "on":
logging.info("Command ON: %s" % internalid)
player.on()
return client.response_success()
return agoproto.response_success()
elif content["command"] == "off":
logging.info("Command OFF: %s" % internalid)
player.off()
return client.response_success()
return agoproto.response_success()
elif content["command"] == "play":
logging.info("Command PLAY: %s" % internalid)
player.play()
return client.response_success()
return agoproto.response_success()
elif content["command"] == "pause":
logging.info("Command PAUSE: %s" % internalid)
player.pause()
return client.response_success()
return agoproto.response_success()
elif content["command"] == "stop":
logging.info("Command STOP: %s" % internalid)
player.stop()
return client.response_success()
return agoproto.response_success()
elif content["command"] == "next":
logging.info("Command NEXT: %s" % internalid)
player.next()
return client.response_success()
return agoproto.response_success()
elif content["command"] == "previous":
logging.info("Command PREVIOUS: %s" % internalid)
player.prev()
return client.response_success()
return agoproto.response_success()
elif content["command"] == "setvolume":
logging.info("Command SETVOLUME: %s" % internalid)
if content.has_key('volume'):
player.set_volume(content['volume'])
return client.response_success()
return agoproto.response_success()
else:
logging.error('Missing parameter "volume" to command SETVOLUME')
return client.response_missing_parameters(data={'command': 'setvolume', 'params': ['volume']})
return agoproto.response_missing_parameters(data={'command': 'setvolume', 'params': ['volume']})
elif content["command"] == "displaymessage":
if content.has_key('line1') and content.has_key('line2') and content.has_key('duration'):
logging.info("Command DISPLAYMESSAGE: %s" % internalid)
player.display(content['line1'], content['line2'], content['duration'])
return client.response_success()
return agoproto.response_success()
else:
logging.error('Missing parameters to command DISPLAYMESSAGE')
return client.response_missing_parameters(data={'command': 'displaymessage', 'params': ['line1', 'line2', 'duration']})
return agoproto.response_missing_parameters(data={'command': 'displaymessage', 'params': ['line1', 'line2', 'duration']})
elif content["command"] == "mediainfos":
infos = get_media_infos(internalid, None)
logging.info(infos)
return client.response_success(infos)
return agoproto.response_success(infos)
#unhandled device command
logging.warn('Unhandled device command')
return client.response_unknown_command(message='Unhandled device command', data=content["command"])
return agoproto.response_unknown_command(message='Unhandled device command', data=content["command"])
#init
......
#!/usr/bin/python
from __future__ import print_function
import time
import agoclient
from agoclient import agoproto
AGO_TELLSTICK_VERSION = '0.0.91'
############################################
......@@ -32,16 +35,16 @@ class AgoTellstick(agoclient.AgoApp):
self.log.trace("rescode = %s", resCode)
# res = self.tellstick.getErrorString(resCode)
self.log.error("Failed to turn on device, res=%s", resCode)
return self.connection.response_failed("Failed to turn on device (%s)" % resCode)
return agoproto.response_failed("Failed to turn on device (%s)" % resCode)
else:
self.connection.emit_event(internalid, "event.device.statechanged", 255, "")
self.log.debug("Turning on device: %s res=%s", internalid, resCode)
return self.connection.response_success()
return agoproto.response_success()
# Allon - TODO: will require changes in schema.yaml + somewhere else too
if content["command"] == "allon":
return self.connection.response_unknown_command()
return agoproto.response_unknown_command()
# Off
if content["command"] == "off":
......@@ -49,13 +52,13 @@ class AgoTellstick(agoclient.AgoApp):
if resCode != 'success': # 0:
# res = self.tellstick.getErrorString(resCode)
self.log.error("Failed to turn off device, res=%s", resCode)
return self.connection.response_failed("Failed to turn off device (%s)" % resCode)
return agoproto.response_failed("Failed to turn off device (%s)" % resCode)
else:
# res = 'Success'
self.connection.emit_event(internalid, "event.device.statechanged", 0, "")
self.log.debug("Turning off device: %s res=%s", internalid, resCode)
return self.connection.response_success()
return agoproto.response_success()
# Setlevel for dimmer
if content["command"] == "setlevel":
......@@ -63,17 +66,17 @@ class AgoTellstick(agoclient.AgoApp):
255 * int(content["level"])) / 100) # Different scales: aGo use 0-100, Tellstick use 0-255
if resCode != 'success': # 0:
self.log.error("Failed dimming device, res=%s", resCode)
return self.connection.response_failed("Failed to dim device (%s)" % resCode)
return agoproto.response_failed("Failed to dim device (%s)" % resCode)
else:
# res = 'Success'
self.connection.emit_event(internalid, "event.device.statechanged", content["level"], "")
self.log.debug("Dimming device=%s res=%s level=%s", internalid, resCode, str(content["level"]))
return self.connection.response_success()
return agoproto.response_success()
return self.connection.response_unknown_command()
return agoproto.response_unknown_command()
return self.connection.response_missing_parameters()
return agoproto.response_missing_parameters()
# Event handlers for device and sensor events
# This method is a call-back, triggered when there is a device event
......@@ -194,7 +197,7 @@ class AgoTellstick(agoclient.AgoApp):
def listNewSensors(self):
sensors = self.tellstick.listSensors()
self.log.debug("listSensors returned %d items", len(sensors))
for id, value in sensors.iteritems():
for id, value in sensors.items():
self.log.trace("listNewSensors: devId: %s ", str(id))
if not value["new"]:
continue
......@@ -311,7 +314,7 @@ class AgoTellstick(agoclient.AgoApp):
from tellstickduo import tellstickduo
self.tellstick = tellstickduo(self)
self.log.debug("Stick: Defaulting to Tellstick Duo")
except OSError, e:
except OSError as e:
self.log.error("Failed to load Tellstick stick version code: %s", e)
raise agoclient.agoapp.StartupError()
......@@ -383,7 +386,7 @@ class AgoTellstick(agoclient.AgoApp):
def listNewDevices(self):
switches = self.tellstick.listSwitches()
for devId, dev in switches.iteritems():
for devId, dev in switches.items():
model = dev["model"]
name = dev["name"]
......
#!/usr/bin/python
from __future__ import print_function
# Filter class, used to store a series of samples and determine if
# a new sample is noise or real sample before adding it to the series
......@@ -60,8 +61,8 @@ class sampleseries():
def load(self):
self.samples = [3.3, 3.4, 3.5, 3.6, 3.5, 3.6, 3.5, 3.6, 3.5, 3.4, 3.3, 3.4, 3.5, 3.6, 3.7, 3.8, 3.9, 4.0, 3.9,
4.0]
print self.samples
print ("len of samples=" + str(self.samples.__len__()))
print(self.samples)
print("len of samples=" + str(self.samples.__len__()))
self.times = [datetime.fromtimestamp(mktime(time.strptime("16 Feb 07 00:56", "%y %b %d %H:%M"))),
datetime.fromtimestamp(mktime(time.strptime("16 Feb 07 01:00", "%y %b %d %H:%M"))),
......@@ -84,11 +85,11 @@ class sampleseries():
datetime.fromtimestamp(mktime(time.strptime("16 Feb 07 04:04", "%y %b %d %H:%M"))),
datetime.fromtimestamp(mktime(time.strptime("16 Feb 07 04:08", "%y %b %d %H:%M")))]
self.createT(self.samples, self.times)
print self.samples2
print self.samples2.__len__()
print(self.samples2)
print(self.samples2.__len__())
# print self.times
print ("len of times=" + str(self.times.__len__()))
print("len of times=" + str(self.times.__len__()))
def createT(self, samples, times):
for x in range(0, times.__len__()):
......@@ -101,12 +102,12 @@ class sampleseries():
self.samples.append(sample)
self.times.append(tm if tm is not None else now)
print ("Now=" + str(now) + " sample=" + str(sample))
print(("Now=" + str(now) + " sample=" + str(sample)))
def removeold(self):
print ("len of samples=" + str(self.samples.__len__()))
print("len of samples=" + str(self.samples.__len__()))
if self.samples.__len__() >= self.maxitems:
print "maxitems reached"
print("maxitems reached")
# find oldest, remove it
def avg(self):
......@@ -129,11 +130,11 @@ if __name__ == "__main__":
# 1/31/2016 4:10 AM 22.4
ret = a.test(22.4, datetime.fromtimestamp(mktime(time.strptime("16 Feb 07 04:10", "%y %b %d %H:%M"))))
print "ret="
print ret
print("ret=")
print(ret)
x = a.times[1] - a.times[0]
print x
print(x)
a.insert(1.0, datetime.fromtimestamp(mktime(time.strptime("16 Feb 07 04:10", "%y %b %d %H:%M"))))
# a.insert(1.1)
......@@ -141,7 +142,7 @@ if __name__ == "__main__":
# a.insert(1.1)
# a.insert(-1.1)
print "avg=" + str(a.avg())
print("avg=" + str(a.avg()))
# print a.times
# print ("len of times=" + str(a.times.__len__()))
......
This diff is collapsed.
from __future__ import print_function
import sys, getopt, httplib, urllib, json, os
import logging, syslog
import oauth.oauth as oauth
......@@ -7,22 +9,22 @@ def info (text):
logging.info (text)
syslog.syslog(syslog.LOG_INFO, text)
if debug:
print "INF " + text + "\n"
print("INF " + text + "\n")
def debug (text):
logging.debug (text)
syslog.syslog(syslog.LOG_DEBUG, text)
if debug:
print "DBG " + text + "\n"
print("DBG " + text + "\n")
def error (text):
logging.error(text)
syslog.syslog(syslog.LOG_ERR, text)
if debug:
print "ERR " + text + "\n"
print("ERR " + text + "\n")
def warning(text):
logging.warning (text)
syslog.syslog(syslog.LOG_WARNING, text)
if debug:
print "WRN " + text + "\n"
print("WRN " + text + "\n")
class LogErr:
def write(self, data):
......@@ -55,13 +57,13 @@ def requestToken():
consumer = oauth.OAuthConsumer(PUBLIC_KEY, PRIVATE_KEY)
request = oauth.OAuthRequest.from_consumer_and_token(consumer, http_url='http://api.telldus.com/oauth/requestToken')
request.sign_request(oauth.OAuthSignatureMethod_HMAC_SHA1(), consumer, None)
conn = httplib.HTTPConnection('api.telldus.com:80')
conn = http.client.HTTPConnection('api.telldus.com:80')
conn.request(request.http_method, '/oauth/requestToken', headers=request.to_header())
resp = conn.getresponse().read()
token = oauth.OAuthToken.from_string(resp)
print 'Open the following url in your webbrowser:\nhttp://api.telldus.com/oauth/authorize?oauth_token=%s\n' % token.key
print 'After logging in and accepting to use this application run:\n%s --authenticate' % (sys.argv[0])
print('Open the following url in your webbrowser:\nhttp://api.telldus.com/oauth/authorize?oauth_token=%s\n' % token.key)
print('After logging in and accepting to use this application run:\n%s --authenticate' % (sys.argv[0]))
config['telldus']['requestToken'] = str(token.key)
config['telldus']['requestTokenSecret'] = str(token.secret)
saveConfig()
......@@ -77,14 +79,14 @@ def getAccessToken():
resp = conn.getresponse()
if resp.status != 200:
print 'Error retreiving access token, the server replied:\n%s' % resp.read()
print('Error retreiving access token, the server replied:\n%s' % resp.read())
return
token = oauth.OAuthToken.from_string(resp.read())
config['telldus']['requestToken'] = None
config['telldus']['requestTokenSecret'] = None
config['telldus']['token'] = str(token.key)
config['telldus']['tokenSecret'] = str(token.secret)
print 'Authentication successful, you can now use your Tellstick Net with aGo control'
print('Authentication successful, you can now use your Tellstick Net with aGo control')
saveConfig()
def authenticate():
......
......@@ -140,7 +140,7 @@ class tellstickduo(tellstickbase):
sensors = td.listSensors()
if len(sensors) != 0:
for id, value in sensors.iteritems():
for id, value in sensors.items():
self.log.trace("listSensors: devId: %s ", str(id))
if id not in self.ignoreDevices:
......
......@@ -15,6 +15,7 @@ __status__ = "Experimental"
__version__ = AGO_TELLSTICK_VERSION
############################################
from __future__ import print_function
from tellstickbase import tellstickbase
import sys, getopt, httplib, urllib, json, os, thread, time
import oauth.oauth as oauth
......@@ -89,7 +90,7 @@ class tellsticknet(tellstickbase):
if ('error' in response):
name = ''
retString = response['error']
print ("retString=" + retString)
print("retString=" + retString)
else:
name = response['name']
self.names[devId] = response['name']
......@@ -126,7 +127,7 @@ class tellsticknet(tellstickbase):
if ('error' in response):
model = ''
retString = response['error']
print ("retString=" + retString)
print("retString=" + retString)
else:
if response['type'] == 'device':
self.models[devId] = response['model']
......@@ -282,7 +283,7 @@ class tellsticknet(tellstickbase):
def sensorThread(self, sensorCallback, dummy):
while (True):
for devId, dev in self.sensors.iteritems():
for devId, dev in self.sensors.items():
response = self.doRequest('sensor/info', {'id': dev["id"]})
lastupdate = datetime.datetime.fromtimestamp(int(response['lastUpdated']))
......@@ -300,7 +301,7 @@ class tellsticknet(tellstickbase):
if data['name'] == 'humidity' and float(data['value']) != self.sensors[devId]["lastHumidity"]:
humidity = float(data['value'])
self.sensors[devId]["lastHumidity"] = humidity
if humidity <> self.sensors[devId]["humidity"]:
if humidity != self.sensors[devId]["humidity"]:
self.sensors[devId]["humidity"] = humidity
sensorCallback(protocol="", model="humidity", devId=devId, dataType=0, value=humidity,
timestamp=lastupdate, callbackId=None)
......
......@@ -9,6 +9,10 @@ configure_file (
set(PYTHON_AGOCLIENT_SOURCE_FILES
agoclient/agoapp.py
agoclient/agoconnection.py
agoclient/agoproto.py
agoclient/agotransport.py
agoclient/agotransport_qpid.py
agoclient/agotransport_mqtt.py
agoclient/config.py
agoclient/__init__.py
agoclient/_logging.py
......
......@@ -9,9 +9,9 @@ import syslog
import sys
# Re-export each export module
from agoapp import *
from config import *
from agoconnection import *
from .agoapp import *
from .config import *
from .agoconnection import *
syslog.openlog(sys.argv[0], syslog.LOG_PID, syslog.LOG_DAEMON)
......@@ -2,21 +2,17 @@ import logging
# We want to keep C++ and Python ago-coding as similar as possible.
# In C++ we have levels TRACE and CRITICAL.
#
# Add extra Trace level
TRACE = logging.TRACE = 5
logging._levelNames[TRACE] = TRACE
logging._levelNames['TRACE'] = TRACE
# C++ impl has FATAL, in python we have CRITICAL
# Add this alias so we can use the same logging consts
FATAL = logging.FATAL = logging.CRITICAL
logging._levelNames[FATAL] = FATAL
logging._levelNames['FATAL'] = FATAL
LOGGING_LOGGER_CLASS = logging.getLoggerClass()
class AgoLoggingClass(LOGGING_LOGGER_CLASS):
def trace(self, msg, *args, **kwargs):
if self.isEnabledFor(TRACE):
......@@ -27,10 +23,11 @@ class AgoLoggingClass(LOGGING_LOGGER_CLASS):
self._log(FATAL, msg, args, **kwargs)
if logging.getLoggerClass() is not AgoLoggingClass:
logging.setLoggerClass(AgoLoggingClass)
logging.addLevelName(TRACE, 'TRACE')
logging.addLevelName(FATAL, 'FATAL')
def init():
if logging.getLoggerClass() is not AgoLoggingClass:
logging.setLoggerClass(AgoLoggingClass)
logging.addLevelName(TRACE, 'TRACE')
logging.addLevelName(FATAL, 'FATAL')
# TODO: Syslog priority mapping
# TODO: Syslog priority mapping
from __future__ import print_function
import agoclient._directories
import agoclient._logging
import argparse
import config
from . import config
import logging
import os.path
import signal
......@@ -11,8 +12,15 @@ import sys
from agoclient.agoconnection import AgoConnection
from logging.handlers import SysLogHandler
try:
import faulthandler
except:
faulthandler = None
__all__ = ["AgoApp"]
agoclient._logging.init()
class StartupError(Exception):
pass
......@@ -70,7 +78,7 @@ class AgoApp:
parser.add_argument('--log-method', dest="log_method",
help='Where to log', choices=['console', 'syslog'])
facilities = SysLogHandler.facility_names.keys()
facilities = list(SysLogHandler.facility_names.keys())
facilities.sort()
parser.add_argument('--log-syslog-facility', dest="syslog_facility",
help='Which syslog facility to log to.',
......@@ -163,12 +171,25 @@ class AgoApp:
lvl_name = self.get_config_option("log_level", "INFO",
section=[None, "system"])
if lvl_name.upper() not in logging._levelNames:
raise ConfigurationError("Invalid log_level %s" % lvl_name)
lvl_name = lvl_name.upper()
# ..and set it
lvl = logging._levelNames[lvl_name.upper()]
root.setLevel(lvl)
lvl = logging.getLevelName(lvl_name)
try:
root.setLevel(lvl)
except ValueError as e:
raise ConfigurationError("Invalid log_level %s: %s" % (lvl_name, str(e)))
# Read logging-specific levels from configuration file. The same functionality is present in
# c++ version with Boost Log channels.
for logger_name, level in self.get_config_section('loggers', (self.app_short_name, 'system')).items():
# Our global level controls "maximum output level" rather than "root" level
# to mimic the way C++ agoapp does. Thus, cap each of the loggers to that level.
lvl = logging.getLevelName(level)
level = max(root.level, lvl)
try:
logging.getLogger(logger_name).setLevel(level)
except ValueError as e:
raise ConfigurationError("Invalid log level for logger %s: %s" % (logger_name, str(e)))
# Find log method..
if self.args.trace or self.args.debug:
......@@ -211,14 +232,14 @@ class AgoApp:
self.log_handler.setFormatter(self.log_formatter)
# Forcibly limit QPID logging to INFO
logging.getLogger('qpid').setLevel(max(root.level, logging.INFO))
root.addHandler(self.log_handler)
self.log = logging.getLogger(self.app_name)
self.log = logging.getLogger("app")
def setup_connection(self):
"""Create an AgoConnection instance, assigned to self.connection"""
self.connection = AgoConnection(self.app_short_name)
if not self.connection.start():
raise StartupError()
def cleanup_connection(self):
"""Shutdown and clean up our AgoConnection instance"""
......@@ -250,7 +271,7 @@ class AgoApp:
def _do_shutdown(self):
if self.connection:
self.connection.begin_shutdown()
self.connection.prepare_shutdown()
def setup_app(self):
"""This should be overriden by the application to setup app specifics"""
......@@ -296,6 +317,11 @@ class AgoApp:
OS Exit code
"""
if faulthandler:
faulthandler.enable()
faulthandler.register(signal.SIGINFO)
if not self.parse_command_line(argv):
return 1
......@@ -308,10 +334,10 @@ class AgoApp:
except ConfigurationError as e:
if self.log:
self.log.error("Failed to start %s due to configuration error: %s",
self.app_name, e.message)
self.app_name, e)
else:
# Print to stderr, in case logging setup failed
print("Failed to start %s due to configuration error: %s" % (self.app_name, e.message),
print("Failed to start %s due to configuration error: %s" % (self.app_name, e),
file=sys.stderr)
return 1
......@@ -409,6 +435,29 @@ class AgoApp:
return config.get_config_option(section, option, default_value, app)
def get_config_section(self, section=None, app=None):
"""Read all options in the given section(s) from the configuration subsystem.
Same as get_config_option, but will return a dict with all defined values under the given
section(s). If more than one section/app is defined, all will be looked at, in the order
specified. If an option is set in multiple sections/app, the last one seen will be used.
Returns a dict with key/values.
"""
if section is None:
section = self.app_short_name
if app is None:
if type(section) == str:
app = [self.app_short_name, section]
else:
app = [self.app_short_name] + section
config._iterable_replace_none(section, self.app_short_name)
config._iterable_replace_none(app, self.app_short_name)
return config.get_config_section(section, app)
def set_config_option(self, option, value, section=None, app=None):
"""Write a config option to the configuration subsystem.
......@@ -461,7 +510,7 @@ class AgoApp:
self.log.debug('new_value=%s isinstance=%s len=%d' % (
new_value, str(isinstance(new_value, str)), len(new_value)))
if empty and isinstance(new_value, str) and len(new_value) == 0:
self.log.debug('Parameter "%s" is empty' % (param))
self.log.debug('Parameter "%s" is empty' % param)
return False, value
else:
return True, new_value
......@@ -478,7 +527,7 @@ class AgoApp:
else:
# check if str is empty
if empty and isinstance(value, str) and len(value) == 0:
self.log.trace('Parameter "%s" is empty' % (param))
self.log.trace('Parameter "%s" is empty' % param)
return False, value
return True, param
This diff is collapsed.
"""Response codes"""
RESPONSE_SUCCESS = 'success'
RESPONSE_ERR_FAILED = 'failed'
RESPONSE_ERR_UNKNOWN_COMMAND = 'unknown.command'
RESPONSE_ERR_BAD_PARAMETERS = 'bad.parameters'
RESPONSE_ERR_NO_COMMANDS_FOR_DEVICE = 'no.commands.for.device'
RESPONSE_ERR_MISSING_PARAMETERS = 'missing.parameters'
class AgoResponse:
"""This class represents a response as obtained from AgoConnection.send_request"""
def __init__(self, response=None, identifier=None, message=None):
"""Create a response holder, either from a received response or implicitly via
an identifier & optional message.
"""
if not response:
response = response_error(identifier, message)
else:
assert not identifier
assert not message
self.response = response
if self.is_ok():
self.root = self.response["result"]
elif self.is_error():
self.root = self.response["error"]
else:
raise Exception("Invalid response, neither result or error present")
def __str__(self):
if self.is_error():
return 'AgoResponse[ERROR] message="%s" data=[%s]' % (self.message(), str(self.data()))
else:
return 'AgoResponse[OK] message="%s" data=[%s]' % (self.message(), str(self.data()))
def is_error(self):
return "error" in self.response
def is_ok(self):
return "result" in self.response
def identifier(self):
return self.root["identifier"]
def message(self):
if "message" in self.root:
return self.root["message"]
return None
def data(self):
if "data" in self.root:
return self.root["data"]
return None
class ResponseError(Exception):
def __init__(self, response):
if not response.is_error():
raise Exception("Not an error response")
self.response = response
super(ResponseError, self).__init__(self.message())
def identifier(self):
return self.response.identifier()
def message(self):
return self.response.message()
def data(self):
return self.response.data()
# Helpers to create proper responses
def response_result(iden, mess=None, data=None):
"""
Construct a response message.
:param iden: response 'identifier'
:param mess: response 'message' <optional>
:param data: response 'data' <optional>
"""
response = {}
result = {}
if iden is not None:
result['identifier'] = iden
else:
raise Exception('Response without identifier (param "iden") not permitted')
if mess is not None:
result['message'] = mess
if data is not None:
result['data'] = data
response['result'] = result
return response
def response_success(data=None, message=None):
"""
Construct a response message with the very generic 'success' identifier.
First parameter is data because data is more often returned than message when response is successful
"""
return response_result(iden=RESPONSE_SUCCESS, mess=message, data=data)
def response_error(iden, mess, data=None):
"""
Construct an response message indicating an error.
parameters are voluntary shortened!
:param iden: response identifier <mandatory>
:param mess: response message <optional>
:param data: response data <optional>
"""
response = {}
error = {}
# identifier
if iden is not None:
error['identifier'] = iden
else:
# iden is mandatory
raise Exception('Response without identifier (param "iden") not permitted')
# message
if mess is not None:
error['message'] = mess
else:
# mess is mandatory
raise Exception('Error response without message (param "mess") not permitted')
# data
if data is not None:
error['data'] = data
response['error'] = error
return response
def response_unknown_command(message="Unhandled command", data=None):
"""
Create a response which indicates that an unknown command was sent.
:param message: A human readable message giving a hint of what failed.
:param data: Any optional machine readable data.
:return:
"""
return response_error(iden=RESPONSE_ERR_UNKNOWN_COMMAND, mess=message, data=data)
def response_missing_parameters(message="Missing parameter", data=None):
"""
Create a response which indicates missing parameter was passed to the command.
:param message: A human readable message giving a hint of what failed.
:param data: Any optional machine readable data.
:return:
"""
return response_error(iden=RESPONSE_ERR_MISSING_PARAMETERS, mess=message, data=data)
def response_bad_parameters(message="Bad parameter", data=None):
"""
Create a response which indicates bad parameter was passed to the command.
:param message: A human readable message giving a hint of what failed.
:param data: Any optional machine readable data.
:return:
"""
return response_error(iden=RESPONSE_ERR_BAD_PARAMETERS, mess=message, data=data)
def response_failed(message, data=None):
"""
Create a response which indicate a general failure (RESPONSE_ERR_FAILED)
:param message: A human readable message giving a hint of what failed.
:param data: Any optional machine readable data.
:return:
"""
return response_error(iden=RESPONSE_ERR_FAILED, mess=message, data=data)
from abc import abstractmethod
class AgoTransport:
@abstractmethod
def start(self):
raise NotImplementedError()
@abstractmethod
def prepare_shutdown(self):
raise NotImplementedError()
@abstractmethod
def shutdown(self):
raise NotImplementedError()
@abstractmethod
def is_active(self):
raise NotImplementedError()
@abstractmethod
def send_message(self, message):
raise NotImplementedError()
@abstractmethod
def send_request(self, message, timeout):
raise NotImplementedError()
@abstractmethod
def fetch_message(self, timeout):
"""
Wait for an incoming messages from the message bus.
If the returned object has a reply_function defined, the sender expects the receiver to send
a reply to the message using this method.
:param timeout: How long to wait, in seconds.
:return: An object which holds the message body and an optional reply_function
:rtype: AgoTransportMessage
"""
raise NotImplementedError()
class AgoTransportMessage:
def __init__(self, message, reply_function):
self.message = message
self.reply_function = reply_function
@property
def content(self):
return self.message['content']
import functools
import json
import logging
import re
import sys
import threading
import uuid
import paho.mqtt.client as mqtt
import agoclient.agotransport
from agoclient import agoproto
TOPIC_BASE = 'com.agocontrol'
PUBLISH_TOPIC = TOPIC_BASE + '/legacy'
class AgoMqttTransport(agoclient.agotransport.AgoTransport):
def __init__(self, client_id, broker, username, password):
self.log = logging.getLogger('transport')
self.client_id = client_id
self.broker = broker
self.username = username
self.password = password
self.connection_uuid = str(uuid.uuid4())
self.reply_topic_base = TOPIC_BASE + "/" + self.connection_uuid + "/"
self.reply_seq = 1
self.mqtt = None # type: mqtt.Client
self._shutting_down = False
self.lock = threading.Lock()
self.ready_event = threading.Event()
self.queue = []
self.queue_condition = threading.Condition(self.lock)
self.pending_replies = {}
def start(self):
self.mqtt = mqtt.Client(self.broker, clean_session=True)
if self.username and self.password:
self.mqtt.username_pw_set(self.username, self.password)
self.mqtt.on_connect = self._on_connect
self.mqtt.on_subscribe = self._on_subscribe
self.mqtt.on_message = self._on_message
self.mqtt.enable_logger(logging.getLogger('mqtt')) # Ues custom logger for lowlevel mqtt
host = self.broker
port = 1883
m = re.match('^([^:]+)(?::?(\d+))?$', self.broker)
if m:
host = m.group(1)
if m.group(2):
port = int(m.group(2))
try:
self.log.info("Connecting to MQTT broker %s:%d", host, port)
self.mqtt.connect_async(self.broker, port)
except ValueError as e:
self.log.error("Invalid MQTT broker configuration (%s:%d): %s", host, port, e)
return False
self.mqtt.loop_start()
# Block until connected
while not self._shutting_down:
try:
self.log.trace("Waiting for MQTT connection readiness")
if self.ready_event.wait(5):
self.log.trace("MQTT ready")
return True
except KeyboardInterrupt:
break
return False
def _on_connect(self, client, userdata, flags, rc):
self.pending_subscribes = 2
self.ready_event.clear()
self.log.debug("Connected to MQTT broker, subscribing")
self.mqtt.subscribe(PUBLISH_TOPIC)
self.mqtt.subscribe(self.reply_topic_base + '+')
def _on_subscribe(self, client, userdata, mid, granted_qos):
self.pending_subscribes -= 1
if self.pending_subscribes > 0:
return
self.log.debug("All MQTT topics subscribed, ready")
self.ready_event.set()
def _on_message(self, client, userdata, message):
try:
if not message.topic.startswith(TOPIC_BASE):
self.log.error("Got a message on topic %s, which should not be subscribed to", self.topic)
return
with self.lock:
if message.topic in self.pending_replies:
pending = self.pending_replies[message.topic]
c = pending['condition'] # type: threading.Condition
pending['reply'] = json.loads(message.payload.decode('utf-8'))
c.notify()
return
elif message.topic.startswith(self.reply_topic_base):
self.log.warning("Received too late response for %s: %s", message.topic)
return
elif message.topic != PUBLISH_TOPIC:
# Should not see anything not subscribed too
self.log.warning("on_message for unknown topic %s", message.topic)
return
self.queue.append(json.loads(message.payload.decode('utf-8')))
self.queue_condition.notify()
except:
self.log.exception("Unhandled exception in MQTT on_message")
sys.exit(1)
def prepare_shutdown(self):
self._shutting_down = True
if self.mqtt:
self.log.debug("Preparing MQTT shutdown")
self.mqtt.disconnect()
# wakeup thread
with self.lock:
for pending in list(self.pending_replies.values()):
pending['condition'].notify()
self.queue_condition.notify()
def shutdown(self):
self.prepare_shutdown()
if self.mqtt:
self.log.debug("Stopping MQTT loop")
self.mqtt.loop_stop()
self.log.trace("MQTT loop stopped")
self.mqtt = None
def is_active(self):
return self.mqtt is not None and not self._shutting_down
def send_message(self, message, wait=False):
self.log.trace("Sending message: %s", message)
self._send_message(PUBLISH_TOPIC, message, wait)
def _send_message(self, topic, message, wait=False):
msginfo = self.mqtt.publish(topic, json.dumps(message))
rc = msginfo.rc
if rc == mqtt.MQTT_ERR_SUCCESS:
if wait:
msginfo.wait_for_publish()
return True
elif rc == mqtt.MQTT_ERR_QUEUE_SIZE:
self.log.warning("Failed to send message, MQTT queue full")
elif rc == mqtt.MQTT_ERR_NO_CONN:
self.log.warning("Failed to send message, MQTT not connected")
return False
def send_request(self, message, timeout):
if self._shutting_down:
return agoproto.AgoResponse(identifier='no.reply', message='Client shutting down')
reply_seq = self.reply_seq
self.reply_seq += 1
reply_topic = self.reply_topic_base + str(reply_seq)
message['reply-to'] = reply_topic
pending = dict(condition=threading.Condition(self.lock), reply=None)
self.pending_replies[reply_topic] = pending
try:
self.send_message(message, wait=True)
with self.lock:
pending['condition'].wait(timeout)
if not pending['reply']:
self.log.warning('Timeout waiting for reply to %s', message)
return agoproto.AgoResponse(identifier='no.reply', message='Timeout waiting for reply')
reply = pending['reply']
finally:
del self.pending_replies[reply_topic]
response = agoproto.AgoResponse(reply)
if self.log.isEnabledFor(logging.TRACE):
self.log.trace("Received response: %s", response.response)
return response
def fetch_message(self, timeout):
try:
if self._shutting_down:
return None
with self.lock:
if not self.queue:
self.queue_condition.wait(timeout)
if not self.queue:
return None
message = self.queue.pop()
reply_function = None
if 'reply-to' in message:
reply_function = functools.partial(self._sendreply, message['reply-to'])
del message['reply-to']
return agoclient.agotransport.AgoTransportMessage(message, reply_function)
except:
raise
def _sendreply(self, reply_to, content):
"""Internal used to send a reply."""
self.log.trace("Sending reply to %s: %s", reply_to, content)
self._send_message(reply_to, content)
\ No newline at end of file
import errno
import functools
import logging
import select
import time
import uuid
import qpid.log
import qpid.messaging
import qpid.util
import agoclient.agotransport
from agoclient import agoproto
class AgoQpidTransport(agoclient.agotransport.AgoTransport):
def __init__(self, broker, username, password):
self.log = logging.getLogger('transport')