andrej@1511: #!/usr/bin/env python andrej@1511: # -*- coding: utf-8 -*- andrej@1511: andrej@1667: # This file is part of Beremiz runtime. andrej@1511: # andrej@1511: # Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD andrej@1680: # Copyright (C) 2017: Andrey Skvortsov andrej@1511: # andrej@1667: # See COPYING.Runtime file for copyrights details. andrej@1511: # andrej@1667: # This library is free software; you can redistribute it and/or andrej@1667: # modify it under the terms of the GNU Lesser General Public andrej@1667: # License as published by the Free Software Foundation; either andrej@1667: # version 2.1 of the License, or (at your option) any later version. andrej@1667: andrej@1667: # This library is distributed in the hope that it will be useful, andrej@1511: # but WITHOUT ANY WARRANTY; without even the implied warranty of andrej@1667: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU andrej@1667: # Lesser General Public License for more details. andrej@1667: andrej@1667: # You should have received a copy of the GNU Lesser General Public andrej@1667: # License along with this library; if not, write to the Free Software andrej@1667: # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA andrej@1511: andrej@1826: andrej@1881: from __future__ import absolute_import andrej@1826: from __future__ import print_function Edouard@1438: import os Edouard@2217: import platform Edouard@2208: from zope.interface import implements Edouard@2208: from nevow import appserver, inevow, tags, loaders, athena, url, rend Edouard@1438: from nevow.page import renderer Edouard@2208: from formless import annotate Edouard@2208: from formless import webform Edouard@2209: from formless import configurable Edouard@1439: from twisted.internet import reactor Edouard@2210: Edouard@1919: import util.paths as paths Edouard@2210: from runtime.loglevels import LogLevels, LogLevelsDict Edouard@1438: Edouard@2208: PAGE_TITLE = 'Beremiz Runtime Web Interface' Edouard@2208: Edouard@1438: xhtml_header = ''' Edouard@1438: Edouard@1438: ''' Edouard@1438: Edouard@1453: WorkingDir = None Edouard@2210: _PySrv = None Edouard@1453: andrej@1736: Edouard@1438: class PLCHMI(athena.LiveElement): Edouard@1438: Edouard@1438: initialised = False Edouard@1438: Edouard@1438: def HMIinitialised(self, result): Edouard@1438: self.initialised = True Edouard@1438: Edouard@1438: def HMIinitialisation(self): Edouard@1438: self.HMIinitialised(None) Edouard@1438: Edouard@1438: class DefaultPLCStartedHMI(PLCHMI): andrej@1878: docFactory = loaders.stan( andrej@1878: tags.div(render=tags.directive('liveElement'))[ andrej@1878: tags.h1["PLC IS NOW STARTED"], andrej@1878: ]) Edouard@1438: andrej@1736: Edouard@1438: class PLCStoppedHMI(PLCHMI): andrej@1878: docFactory = loaders.stan( andrej@1878: tags.div(render=tags.directive('liveElement'))[ andrej@1878: tags.h1["PLC IS STOPPED"], andrej@1878: ]) Edouard@1438: andrej@1736: Edouard@1438: class MainPage(athena.LiveElement): Edouard@1438: jsClass = u"WebInterface.PLC" andrej@1878: docFactory = loaders.stan( andrej@1878: tags.div(render=tags.directive('liveElement'))[ andrej@1878: tags.div(id='content')[ andrej@1878: tags.div(render=tags.directive('PLCElement'))] andrej@1878: ]) Edouard@1438: Edouard@1438: def __init__(self, *a, **kw): Edouard@1438: athena.LiveElement.__init__(self, *a, **kw) Edouard@1438: self.pcl_state = False Edouard@1438: self.HMI = None Edouard@1438: self.resetPLCStartedHMI() Edouard@1438: Edouard@1438: def setPLCState(self, state): Edouard@1438: self.pcl_state = state Edouard@1438: if self.HMI is not None: Edouard@1438: self.callRemote('updateHMI') Edouard@1438: Edouard@1438: def setPLCStartedHMI(self, hmi): Edouard@1438: self.PLCStartedHMIClass = hmi Edouard@1438: Edouard@1438: def resetPLCStartedHMI(self): Edouard@1438: self.PLCStartedHMIClass = DefaultPLCStartedHMI Edouard@1438: Edouard@1438: def getHMI(self): Edouard@1438: return self.HMI Edouard@1438: Edouard@1438: def HMIexec(self, function, *args, **kwargs): Edouard@1438: if self.HMI is not None: andrej@1740: getattr(self.HMI, function, lambda: None)(*args, **kwargs) Edouard@1438: athena.expose(HMIexec) Edouard@1438: Edouard@1438: def resetHMI(self): Edouard@1438: self.HMI = None Edouard@1438: Edouard@1438: def PLCElement(self, ctx, data): Edouard@1438: return self.getPLCElement() Edouard@1438: renderer(PLCElement) Edouard@1438: Edouard@1438: def getPLCElement(self): Edouard@1438: self.detachFragmentChildren() Edouard@1438: if self.pcl_state: Edouard@1438: f = self.PLCStartedHMIClass() Edouard@1438: else: Edouard@1438: f = PLCStoppedHMI() Edouard@1438: f.setFragmentParent(self) Edouard@1438: self.HMI = f Edouard@1438: return f Edouard@1438: athena.expose(getPLCElement) Edouard@1438: Edouard@1438: def detachFragmentChildren(self): Edouard@1438: for child in self.liveFragmentChildren[:]: Edouard@1438: child.detach() Edouard@1438: Edouard@2209: class ConfigurableBindings(configurable.Configurable): Edouard@2209: Edouard@2209: def __init__(self): Edouard@2209: configurable.Configurable.__init__(self, None) Edouard@2209: self.bindingsNames = [] Edouard@2209: Edouard@2209: def getBindingNames(self, ctx): Edouard@2209: return self.bindingsNames Edouard@2209: Edouard@2210: def addExtension(self, name, desc, fields, btnlabel, callback): Edouard@2209: def _bind(ctx): Edouard@2209: return annotate.MethodBinding( Edouard@2209: 'action_'+name, Edouard@2209: annotate.Method(arguments=[ Edouard@2217: annotate.Argument(*field) Edouard@2217: for field in fields], Edouard@2209: label = desc), Edouard@2214: action = btnlabel) Edouard@2209: setattr(self, 'bind_'+name, _bind) Edouard@2209: Edouard@2210: setattr(self, 'action_'+name, callback) Edouard@2209: Edouard@2209: self.bindingsNames.append(name) Edouard@2209: Edouard@2209: ConfigurableSettings = ConfigurableBindings() Edouard@2209: Edouard@2208: class ISettings(annotate.TypedInterface): Edouard@2217: platform = annotate.String(label = _("Platform"), Edouard@2217: default = platform.system() + " " + platform.release(), Edouard@2217: immutable = True) Edouard@2217: # TODO version ? Edouard@2217: Edouard@2210: def sendLogMessage( Edouard@2208: ctx = annotate.Context(), Edouard@2210: level = annotate.Choice(LogLevels, Edouard@2210: required=True, Edouard@2210: label=_("Log message level")), Edouard@2210: message = annotate.String(label=_("Message text"))): Edouard@2208: pass Edouard@2210: sendLogMessage = annotate.autocallable(sendLogMessage, Edouard@2210: label=_("Send a message to the log"), Edouard@2210: action=_("Send")) Edouard@2208: Edouard@2208: Edouard@2208: class SettingsPage(rend.Page): Edouard@2208: # We deserve a slash Edouard@2208: addSlash = True Edouard@2208: Edouard@2208: # This makes webform_css url answer some default CSS Edouard@2208: child_webform_css = webform.defaultCSS Edouard@2208: Edouard@2208: implements(ISettings) Edouard@2208: Edouard@2208: Edouard@2208: docFactory = loaders.stan([tags.html[ Edouard@2208: tags.head[ Edouard@2208: tags.title[_("Beremiz Runtime Settings")], Edouard@2208: tags.link(rel='stylesheet', Edouard@2208: type='text/css', Edouard@2208: href=url.here.child("webform_css")) Edouard@2208: ], Edouard@2208: tags.body[ Edouard@2209: tags.h1["Runtime settings:"], Edouard@2209: webform.renderForms('staticSettings'), Edouard@2209: tags.h2["Extensions settings:"], Edouard@2209: webform.renderForms('dynamicSettings'), Edouard@2208: ]]]) Edouard@2208: Edouard@2209: def __init__(self): Edouard@2209: rend.Page.__init__(self) Edouard@2209: Edouard@2209: def configurable_staticSettings(self, ctx): Edouard@2209: return configurable.TypedInterfaceConfigurable(self) Edouard@2209: Edouard@2209: def configurable_dynamicSettings(self, ctx): Edouard@2209: return ConfigurableSettings Edouard@2210: Edouard@2210: def sendLogMessage(self, level, message, **kwargs): Edouard@2210: level = LogLevelsDict[level] Edouard@2210: if _PySrv.plcobj is not None: Edouard@2210: _PySrv.plcobj.LogMessage(level, "Web form log message: " + message ) Edouard@2208: andrej@1736: Edouard@1438: class WebInterface(athena.LivePage): Edouard@1438: Edouard@1438: docFactory = loaders.stan([tags.raw(xhtml_header), andrej@1767: tags.html(xmlns="http://www.w3.org/1999/xhtml")[ Edouard@2208: tags.head(render=tags.directive('liveglue'))[ Edouard@2208: tags.title[PAGE_TITLE], Edouard@2208: tags.link(rel='stylesheet', Edouard@2208: type='text/css', Edouard@2208: href=url.here.child("webform_css")) Edouard@2208: ], andrej@1767: tags.body[ andrej@1767: tags.div[ Edouard@2208: tags.div(render=tags.directive("MainPage")), andrej@1767: ]]]]) Edouard@1438: MainPage = MainPage() Edouard@1438: PLCHMI = PLCHMI Edouard@1438: Edouard@2208: def child_settings(self, context): Edouard@2208: return SettingsPage() Edouard@2208: Edouard@1438: def __init__(self, plcState=False, *a, **kw): Edouard@1438: super(WebInterface, self).__init__(*a, **kw) Anton@1682: self.jsModules.mapping[u'WebInterface'] = paths.AbsNeighbourFile(__file__, 'webinterface.js') Edouard@1438: self.plcState = plcState Edouard@1438: self.MainPage.setPLCState(plcState) Edouard@1438: Edouard@1438: def getHMI(self): Edouard@1438: return self.MainPage.getHMI() Edouard@1438: Edouard@1438: def LoadHMI(self, hmi, jsmodules): Edouard@1438: for name, path in jsmodules.iteritems(): Edouard@1438: self.jsModules.mapping[name] = os.path.join(WorkingDir, path) Edouard@1438: self.MainPage.setPLCStartedHMI(hmi) Edouard@1438: Edouard@1438: def UnLoadHMI(self): Edouard@1438: self.MainPage.resetPLCStartedHMI() Edouard@1438: Edouard@1438: def PLCStarted(self): Edouard@1438: self.plcState = True Edouard@1438: self.MainPage.setPLCState(True) Edouard@1438: Edouard@1438: def PLCStopped(self): Edouard@1438: self.plcState = False Edouard@1438: self.MainPage.setPLCState(False) Edouard@1438: Edouard@1438: def renderHTTP(self, ctx): Edouard@1438: """ Edouard@1438: Force content type to fit with SVG Edouard@1438: """ andrej@1870: req = ctx.locate(inevow.IRequest) Edouard@1438: req.setHeader('Content-type', 'application/xhtml+xml') Edouard@1438: return super(WebInterface, self).renderHTTP(ctx) Edouard@1438: Edouard@1438: def render_MainPage(self, ctx, data): Edouard@1438: f = self.MainPage Edouard@1438: f.setFragmentParent(self) Edouard@1438: return ctx.tag[f] Edouard@1438: Edouard@1438: def child_(self, ctx): Edouard@1438: self.MainPage.detachFragmentChildren() Edouard@1438: return WebInterface(plcState=self.plcState) Edouard@1438: Edouard@1438: def beforeRender(self, ctx): Edouard@1438: d = self.notifyOnDisconnect() Edouard@1438: d.addErrback(self.disconnected) Edouard@1438: Edouard@1438: def disconnected(self, reason): Edouard@1438: self.MainPage.resetHMI() andrej@1782: # print reason andrej@1782: # print "We will be called back when the client disconnects" Edouard@1438: andrej@1736: Edouard@2208: Edouard@1439: def RegisterWebsite(port): Edouard@1438: website = WebInterface() Edouard@1438: site = appserver.NevowSite(website) Edouard@1438: Edouard@1439: reactor.listenTCP(port, site) andrej@1826: print(_('HTTP interface port :'), port) Edouard@1439: return website Edouard@1438: Edouard@2210: andrej@1831: class statuslistener(object): Edouard@1438: def __init__(self, site): Edouard@1438: self.oldstate = None Edouard@1438: self.site = site Edouard@1438: Edouard@1438: def listen(self, state): Edouard@1438: if state != self.oldstate: Edouard@1453: action = {'Started': self.site.PLCStarted, Edouard@1453: 'Stopped': self.site.PLCStopped}.get(state, None) andrej@1756: if action is not None: andrej@1756: action() Edouard@1438: self.oldstate = state Edouard@1438: andrej@1736: Edouard@1438: def website_statuslistener_factory(site): Edouard@1438: return statuslistener(site).listen Edouard@2208: Edouard@2208: Edouard@2210: def SetServer(pysrv): Edouard@2210: global _PySrv Edouard@2210: _PySrv = pysrv Edouard@2210: