Add web infrastructure borrowing them from my domotia daemon

parent 62f83c5d
......@@ -14,9 +14,24 @@ and Beaglebone like my [Osso](https://git.nexlab.net/nexlab/Osso) I/O expansion
Penguidom is written in python 2.x on top of the well known twisted framework,
and it uses few external libraries.
Other than twisted you will also need to install:
Other than twisted and newov you will also need to install:
* DMLib : https://git.nexlab.net/domotika/dmlib
* python-nexlibs: https://git.nexlab.net/nexlab/python-nexlibs
## WARNING: Penguidom isn't yet ready, i will remove this warning as soon as it will be usable.
\ No newline at end of file
* socketjs-twisted: https://github.com/DesertBus/sockjs-twisted
* corepost: https://github.com/nextime/corepost
### Paradox alarm panels:
At the moment only MG5050 panel is fully supported as it's the one i own.
To make other panels works you need to compile the panel registers file in
penguidom/plugins/paradox/panels/ like the MG5050.py one.
To make this easier, you can "sniff" the protocol communication between the panel and
a client side software like babyware or winload by installing them inside a virtual machine
( i use virtualbox for that ) and then attach the serial over TCP using the included serial proxy
in the paradox module.
## WARNING: Penguidom isn't yet ready, i will remove this warning as soon as it will be usable.
###########################################################################
# Copyright (c) 2018- Franco (nextime) Lanza <franco@nexlab.it>
#
# Penguidom System client Daemon "penguidomd" [https://git.nexlab.net/domotika/Penguidom]
#
# This file is part of penguidom.
#
# penguidom is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
###########################################################################
# Copyright (c) 2011-2014 Unixmedia S.r.l. <info@unixmedia.it>
# Copyright (c) 2011-2014 Franco (nextime) Lanza <franco@unixmedia.it>
#
# Domotika System Controller Daemon "domotikad" [http://trac.unixmedia.it]
#
# This file is part of domotikad.
#
# domotikad is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
import json
from twisted.internet import defer, task, reactor, protocol
from nevow import inevow, rend, tags, loaders, flat, athena, stan, guard
import logging, os, sys
from twisted.web import resource, static, server
import time
from txsockjs.factory import SockJSResource, SockJSFactory
import dmjson as dmj
log = logging.getLogger( 'Webgui' )
curdir=os.path.abspath(os.path.dirname(sys.argv[0]))
class AjaxProtocol(object):
def messageReceived(self, message, binary):
f=getattr(self, 'on_command_'+message.split(":")[0], None)
if f and callable(f):
log.debug("Received "+str(message.split(":")[0])+" command")
return f(message)
else:
self.messageSend(message, binary)
def _getIOStatus(self, d, ts):
rs = self.factory.core.getRelays(ts)
rs.addCallback(relstatus, False, d)
return rs.addCallback(dmj.jsonize_text)
def on_command_getIOStatus(self, data):
wts=data.split(":")[1].split("=")[1]
ret=self.factory.core.getActionStatus()
ts=int(time.time())-1
ist=self.factory.core.getInputs(wts)
ist.addCallback(inputstatus, ts, ret).addCallback(self._getIOStatus, wts)
return ist.addCallback(self.messageSend)
def connectionMade(self, who):
log.debug('AJAX connectionMade called by '+str(who))
def connectionLost(self, who, reason):
log.debug('AJAX connectionLost called by '+str(who)+' for reason '+str(reason))
class SockJSServerProtocolWrapper(protocol.Protocol, AjaxProtocol):
def dataReceived(self, data):
log.debug("SockJS RECEIVED: "+str(data))
self.messageReceived(data, False)
def messageSend(self, msg, binary=False):
self.transport.write(msg)
def connectionMade(self):
AjaxProtocol.connectionMade(self, 'sockjs')
def connectionLost(self, reason):
AjaxProtocol.connectionLost(self, 'sockjs', reason)
class SockJSServerFactory(protocol.Factory):
protocol = SockJSServerProtocolWrapper
def buildProtocol(self, addr):
log.debug("SockJS BuildProtocol for "+str(addr))
p = self.protocol()
p.core = self.core
p.factory = self
return p
def getSocketJSResource(core):
options = {
'websocket': True,
'cookie_needed': False,
'heartbeat': 25,
'timeout': 5,
'streaming_limit': 128 * 1024,
'encoding': 'cp1252', # Latin1
#'sockjs_url': '/resources/js/sockjs-0.3.min.js'
#'sockjs_url': 'https://d1fxtkz8shb9d2.cloudfront.net/sockjs-0.3.js'
}
f = SockJSServerFactory()
f.core = core
return SockJSResource(f, options)
###########################################################################
# Copyright (c) 2018- Franco (nextime) Lanza <franco@nexlab.it>
#
# Penguidom System client Daemon "penguidomd" [https://git.nexlab.net/domotika/Penguidom]
#
# This file is part of penguidom.
#
# penguidom is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from twisted.web import client
from zope.interface import implements
from twisted import cred
import twisted.cred.portal
import twisted.cred.credentials
import twisted.cred.checkers
import twisted.cred.error
import logging
from twisted.internet import defer
try:
import hashlib
md5 = hashlib
md5.new = hashlib.md5
sha1 = hashlib.sha1
except:
import md5
import sha1
from nexlibs.utils import genutils
log = logging.getLogger("Webgui")
class mindFactory(object):
loginsaved = False
def __init__(self, request, credential):
self.request = request
self.credential = credential
self.args = self.request.args
class clientAuth(object):
implements(cred.checkers.ICredentialsChecker)
credentialInterfaces = (
cred.credentials.IUsernamePassword,
)
def __init__(self):
pass
def checkAuth(self, usr, pwd):
log.debug("CheckAuth for "+str(usr)+" "+str(pwd))
return self.core.getAuth(usr, genutils.hashPwd(pwd))
def getAuth(self, usr, pwd):
log.debug("getAuth for "+str(usr)+" "+str(pwd))
return self.checkAuth(usr, pwd).addCallback(self.getPerms, pwd)
def getPerms(self, res, pwd):
log.info("getPerms "+str(res))
"""
if len(res) > 0:
if res[0].admin == True:
perms['admin'] = True
return perms
"""
if len(res) > 0:
perms = res[0]
perms.loginpwd = pwd
return perms
return False
def requestAvatarId(self, c):
log.debug('AUTH: '+str(c)+" "+str(c.username))
return self.checkAuth(c.username, c.password).addCallback(
self.getPerms, c.password).addCallback(
self.AvatarResults, c)
def AvatarResults(self, res, c):
log.debug("AvatarResults "+str(res)+" "+str(c))
if res:
return defer.succeed([c, res])
raise cred.error.UnauthorizedLogin()
This diff is collapsed.
###########################################################################
# Copyright (c) 2018- Franco (nextime) Lanza <franco@nexlab.it>
#
# Penguidom System client Daemon "penguidomd" [https://git.nexlab.net/domotika/Penguidom]
#
# This file is part of penguidom.
#
# penguidom is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
import struct
import zlib
from twisted.web import http, server, resource, static, client
import random
import time
try:
import hashlib
md5 = hashlib
md5.new = hashlib.md5
sha1 = hashlib.sha1
except:
import md5
import sha1
import logging
from nexlibs.utils.blockingdefer import blockingDeferred
import cgi, logging
from zope.interface import implements
from twisted.cred import portal, checkers, credentials
import cgi, logging
from nevow import inevow, rend, tags, loaders, flat, athena, stan, guard
import os, sys
from twisted.python import reflect
from twisted import cred
import twisted.cred.portal
import twisted.cred.credentials
import twisted.cred.checkers
import twisted.cred.error
from twisted.internet import defer
from nevow import appserver
from twisted.web.twcgi import CGIScript, FilteredScript
log = logging.getLogger( 'Webgui' )
curdir=os.path.abspath(os.path.dirname(sys.argv[0]))
EXCLUDELIST=(
'.swf',
'.png',
'.jpg',
'.gif',
'.gz',
'.tgz'
'.zip'
)
class GzipRequest(object):
"""Wrapper for a request that applies a gzip content encoding"""
def __init__(self, request, compressLevel=6):
self.request = request
self.request.setHeader('Content-Encoding', 'gzip')
# Borrowed from twisted.web2 gzip filter
self.compress = zlib.compressobj(compressLevel, zlib.DEFLATED,
-zlib.MAX_WBITS, zlib.DEF_MEM_LEVEL,0)
def __getattr__(self, attr):
if 'request' in self.__dict__:
return getattr(self.request, attr)
else:
raise AttributeError, attr
def __setattr__(self, attr, value):
if 'request' in self.__dict__:
return setattr(self.request, attr, value)
else:
self.__dict__[attr] = value
def write(self, data):
if not self.request.startedWriting:
self.crc = zlib.crc32('')
self.size = self.csize = 0
# XXX: Zap any length for now since we don't know final size
if 'content-length' in self.request.headers:
del self.request.headers['content-length']
# Borrow header information from twisted.web2 gzip filter
self.request.write('\037\213\010\000' '\0\0\0\0' '\002\377')
self.crc = zlib.crc32(data, self.crc)
self.size += len(data)
cdata = self.compress.compress(data)
self.csize += len(cdata)
if cdata:
self.request.write(cdata)
elif self.request.producer:
# Simulate another pull even though it hasn't really made it
# out to the consumer yet.
self.request.producer.resumeProducing()
def finish(self):
remain = self.compress.flush()
self.csize += len(remain)
if remain:
self.request.write(remain)
self.request.write(struct.pack('<LL',
self.crc & 0xFFFFFFFFL,
self.size & 0xFFFFFFFFL))
self.request.finish()
class StaticFile(static.File):
def render(self, request):
# Some flash file ( flowplayer.. ) needs
# to be server without gzip compression or
# they will not work sometime...
if self.basename().endswith(EXCLUDELIST):
return static.File.render(self, request)
accept_encoding = request.getHeader('accept-encoding')
if accept_encoding:
encodings = accept_encoding.split(',')
for encoding in encodings:
name = encoding.split(';')[0].strip()
if name == 'gzip':
request = GzipRequest(request)
break
return static.File.render(self, request)
class codeOk(rend.Page):
addSlash = True
content = 'OK'
def renderHTTP( self, ctx):
request = inevow.IRequest(ctx)
#request.setHeader('Location', '/')
request.setResponseCode(200)
return self.content
def setContent(self, cont):
self.content = str(cont)
def childFactory(self, ctx, data):
return self
class permissionDenied(rend.Page):
addSlash = True
def renderHTTP( self, ctx):
log.debug("Rendering Permission Denied")
request = inevow.IRequest(ctx)
#request.setHeader('Location', '/')
#request.setResponseCode(302)
request.setResponseCode(403)
html = '<html><head><title>Penguidom GUI</title></head>'
html = html+"<h3>I'm so sorry!</h3>"
html = html+"<p>you can't do that.</p>"
html = html+"<p><a href=\"/\">Go Home</a></p>"
html = html+'</body></html>'
return html
def childFactory(self, ctx, data):
return self
class RedirectToHome(rend.Page):
addSlash = True
def renderHTTP( self, ctx):
request = inevow.IRequest(ctx)
request.setHeader('Location', '/')
request.setResponseCode(302)
return 'Redirecting...'
def childFactory(self, ctx, data):
return self
def uni(s):
if isinstance(s, str):
return s.decode('utf-8', "replace")
elif isinstance(s, unicode):
return s.encode('utf-8', "replace")
else:
return s
class Caller(object):
def __init__(self, skobj):
self.domotika = skobj
self.missing_method_name = None # Hack!
def __getattribute__(self, name):
sk = object.__getattribute__(self, 'domotika')
try:
return object.__getattribute__(sk, name)
except:
self.missing_method_name = name
log.debug("Missing Method: "+str(name))
return object.__getattribute__(self, '__methodmissing__')
def __methodmissing__(self, *args, **kwargs):
log.debug("Missing Client method %s called (args = %s) and (kwargs = %s)" % (object.__getattribute__(self, 'missing_method_name'), str(args), str(kwargs)))
def neededPermission(method):
if method in ['GET','OPTIONS','HEAD','REPORT','CHECKOUT','SEARCH']:
return 'r'
else:
return 'w'
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment