view service/xidle/xidle.py @ 1533:a598d2141587

more mqtt_to_rdf renames. bring in basic LitElement setup for the debug page Ignore-this: 85e2ab49915e44b08219e537fab21870 darcs-hash:f2d312ec10bb4a33c501a24bd7d11a1f35673f25
author drewp <drewp@bigasterisk.com>
date Sat, 08 Feb 2020 04:02:22 -0800
parents 6cd9341f0a28
children 2500a3ee9102
line wrap: on
line source

"""
X server idle time is now available over http!

Note: HD-4110 webcams stop X from going idle by sending events
constantly. Run this to fix:

    xinput disable "HP Webcam HD-4110"
"""
import time
import socket, json, os
from rdflib import Namespace, URIRef, Literal
from influxdb import InfluxDBClient
import influxdb.exceptions
import cyclone.web
from twisted.internet import reactor, task
from standardservice.logsetup import log, verboseLogging
from patchablegraph import PatchableGraph, CycloneGraphEventsHandler, CycloneGraphHandler

DEV = Namespace("http://projects.bigasterisk.com/device/")
ROOM = Namespace("http://projects.bigasterisk.com/room/")

host = socket.gethostname()
client = InfluxDBClient('bang6', 9060, 'root', 'root', 'main')

os.environ['DISPLAY'] = ':0.0'

import pxss
# another option: http://thp.io/2007/09/x11-idle-time-and-focused-window-in.html

def get_idle_time():
    return pxss.get_info().idle

get_idle_time() # fail if we can't get the display or something

class Root(cyclone.web.RequestHandler):
    def get(self):
        get_idle_time() # fail if we can't get the display or something
        self.set_header('content-type', 'text/html')
        self.write('''
        <!doctype html>
<html>
  <head>
    <title>xidle</title>
    <meta charset="utf-8" />
    <script src="/lib/polymer/1.0.9/webcomponentsjs/webcomponents.min.js"></script>
    <script src="/lib/require/require-2.3.3.js"></script>
    <script src="/rdf/common_paths_and_ns.js"></script>

    <link rel="import" href="/rdf/streamed-graph.html">
    <link rel="import" href="/lib/polymer/1.0.9/polymer/polymer.html">

    <meta name="mobile-web-app-capable" content="yes">
    <meta name="viewport" content="width=device-width, initial-scale=1">
  </head>
  <body>
    <template id="t" is="dom-bind">

      Get the <a href="idle">X idle time</a> on %s.

      <streamed-graph url="graph/xidle/events" graph="{{graph}}"></streamed-graph>
      <div id="out"></div>
      <script type="module" src="/rdf/streamed_graph_view.js"></script>
    </template>
    <style>
     .served-resources {
         margin-top: 4em;
         border-top: 1px solid gray;
         padding-top: 1em;
     }
     .served-resources a {
         padding-right: 2em;
     }
    </style>

      <div class="served-resources">
        <a href="stats/">/stats/</a>
        <a href="graph/dpms">/graph/dpms</a>
        <a href="graph/dpms/events">/graph/dpms/events</a>
        <a href="idle">/idle</a>
    </div>

  </body>
</html>
        ''' % host)

class Idle(cyclone.web.RequestHandler):
    def get(self):
        self.set_header('Content-type', 'application/json')
        self.write(json.dumps({"idleMs" : get_idle_time()}))

class Poller(object):
    def __init__(self):
        self.points = []
        self.lastSent = None
        self.lastSentTime = 0
        self.lastGraphSent = None
        self.lastGraphSentTime = 0
        task.LoopingCall(self.poll).start(1)

    def poll(self):
        ms = get_idle_time()
        ctx = DEV['xidle/%s' % host]
        subj = URIRef("http://bigasterisk.com/host/%s/xidle" % host)
        lastMinActive = ms < 60 * 1000
        now = int(time.time())

        nextGraphUpdate = self.lastGraphSentTime + min(10, ms / 1000 / 2)
        if self.lastGraphSent != lastMinActive or now > nextGraphUpdate:
            masterGraph.patchObject(ctx, subj, ROOM['idleTimeMs'], Literal(ms))
            masterGraph.patchObject(ctx, subj, ROOM['idleTimeMinutes'],
                                    Literal(round(ms / 1000 / 60, 2)))
            self.lastGraphSent = lastMinActive
            self.lastGraphSentTime = now

        if self.lastSent != lastMinActive or now > self.lastSentTime + 3600:
            self.points.append({"measurement": "presence",
                                "tags": {"host": host, "sensor": "xidle"},
                                "fields": {"value": 1 if lastMinActive else 0},
                                "time": now})
            self.lastSent = lastMinActive
            self.lastSentTime = now

            try:
                client.write_points(self.points, time_precision='s')
            except influxdb.exceptions.InfluxDBServerError as e:
                log.error(repr(e))
                reactor.crash()
            self.points = []

verboseLogging(False)

masterGraph = PatchableGraph()
poller = Poller()

reactor.listenTCP(9107, cyclone.web.Application([
    (r'/', Root),
    (r'/idle', Idle),
    (r'/graph/xidle', CycloneGraphHandler, {'masterGraph': masterGraph}),
    (r'/graph/xidle/events', CycloneGraphEventsHandler, {'masterGraph': masterGraph}),
]), interface='::')

reactor.run()