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@2246: 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@2246: 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@2246: 'action_' + name, Edouard@2209: annotate.Method(arguments=[ Edouard@2217: annotate.Argument(*field) Edouard@2217: for field in fields], Edouard@2246: label=desc), Edouard@2246: action=btnlabel) Edouard@2246: setattr(self, 'bind_' + name, _bind) Edouard@2246: Edouard@2246: setattr(self, 'action_' + name, callback) Edouard@2209: Edouard@2209: self.bindingsNames.append(name) Edouard@2209: Edouard@2209: ConfigurableSettings = ConfigurableBindings() Edouard@2209: Edouard@2246: Edouard@2208: class ISettings(annotate.TypedInterface): Edouard@2246: platform = annotate.String(label=_("Platform"), Edouard@2246: default=platform.system( Edouard@2246: ) + " " + platform.release(), Edouard@2246: immutable=True) Edouard@2217: # TODO version ? Edouard@2217: Edouard@2210: def sendLogMessage( Edouard@2246: ctx=annotate.Context(), Edouard@2246: level=annotate.Choice(LogLevels, Edouard@2246: required=True, Edouard@2246: label=_("Log message level")), Edouard@2246: message=annotate.String(label=_("Message text"))): Edouard@2208: pass Edouard@2246: sendLogMessage = annotate.autocallable(sendLogMessage, Edouard@2246: label=_( Edouard@2246: "Send a message to the log"), Edouard@2210: action=_("Send")) Edouard@2208: Edouard@2219: customSettingsURLs = { Edouard@2219: } Edouard@2208: Edouard@2246: Edouard@2208: class SettingsPage(rend.Page): Edouard@2208: # We deserve a slash Edouard@2208: addSlash = True Edouard@2246: 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@2246: docFactory = loaders.stan([ Edouard@2246: tags.html[ Edouard@2246: tags.head[ Edouard@2246: tags.title[_("Beremiz Runtime Settings")], Edouard@2246: tags.link(rel='stylesheet', Edouard@2246: type='text/css', Edouard@2246: href=url.here.child("webform_css")) Edouard@2246: ], Edouard@2246: tags.body[ Edouard@2246: tags.h1["Runtime settings:"], Edouard@2246: webform.renderForms('staticSettings'), Edouard@2246: tags.h2["Extensions settings:"], Edouard@2246: webform.renderForms('dynamicSettings'), Edouard@2246: ] Edouard@2246: ] Edouard@2246: ]) Edouard@2208: 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@2246: Edouard@2210: def sendLogMessage(self, level, message, **kwargs): Edouard@2210: level = LogLevelsDict[level] Edouard@2210: if _PySrv.plcobj is not None: Edouard@2246: _PySrv.plcobj.LogMessage( Edouard@2246: level, "Web form log message: " + message) Edouard@2208: Edouard@2219: def locateChild(self, ctx, segments): Edouard@2246: if segments[0] in customSettingsURLs: Edouard@2219: return customSettingsURLs[segments[0]](ctx, segments) Edouard@2219: return super(SettingsPage, self).locateChild(ctx, segments) Edouard@2219: 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@2246: type='text/css', Edouard@2208: href=url.here.child("webform_css")) Edouard@2208: ], andrej@1767: tags.body[ andrej@1767: tags.div[ Edouard@2246: tags.div( Edouard@2246: render=tags.directive( Edouard@2246: "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) Edouard@2246: self.jsModules.mapping[u'WebInterface'] = paths.AbsNeighbourFile( Edouard@2246: __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@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@2246: 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