New Plugin: GhostXML

Posted on
Mon Jul 10, 2017 6:41 pm
dtich offline
Posts: 798
Joined: Sep 24, 2005

Re: New Plugin: GhostXML

thank you dave. i’ll see how far i can get, and if you end up with any time to tinker with it it would be much appreciated.

thanks all

Posted on
Mon Jul 10, 2017 6:47 pm
DaveL17 offline
User avatar
Posts: 6753
Joined: Aug 20, 2013
Location: Chicago, IL, USA

Re: New Plugin: GhostXML

dtich wrote:
thank you dave. i’ll see how far i can get, and if you end up with any time to tinker with it it would be much appreciated.

thanks all

You bet. Happy to try to answer any questions that you might have as you get on.

I came here to drink milk and kick ass....and I've just finished my milk.

[My Plugins] - [My Forums]

Posted on
Sat Jul 15, 2017 6:33 pm
dtich offline
Posts: 798
Joined: Sep 24, 2005

Re: New Plugin: GhostXML

so, i have this script compiled, and can run in terminal and receive data. works well.

now. how do i... get a few of these variables into indigo??

sorry guys, i just don't know how to do it in python.

see output below code --- any thoughts?? thanks so much, in advance.

Code: Select all

#!/usr/bin/env python
# version: 0.3
# date:    2012-11-30
# author:  Joerg Raedler joerg@j-raedler.de
# license: BSD
# purpose: make RPC to a Sunny WebBox, a monitoring system for solar plants
#
# http://www.sma.de/en/produkte/monitoring-systems/sunny-webbox.html
#
# Use the classes SunnyWebBoxHTTP or SunnyWebBoxUDPStream in your code
# or run this file as a script to test the classes. First parameter is
# the hostname or IP of the box. A password can be provided with an
# optional second parameter.
#
# Example: python SunnyWebBox.py 123.123.123.123 "foobar"
#
# Documentation on the RPC API can be found here:
#
# http://files.sma.de/dl/2585/SWebBoxRPC-BEN092713.pdf
#

import sys, json, hashlib

# handle python 2 and 3
if sys.version_info >= (3, 0):
    py3 = True
    def str2buf(s):
        return bytes(s, 'latin1')
    def buf2str(b):
        return str(b, 'latin1')
else:
    py3 = False
    def str2buf(s):
        return bytes(s)
    def buf2str(b):
        return unicode(b, 'latin1')


class Counter:
    """generate increasing numbers with every call - just for convenience"""
   
    def __init__(self, start=0):
        self.i = start

    def __call__(self):
        i = self.i
        self.i += 1
        return i


class SunnyWebBoxBase(object):
    """Communication with a 'Sunny WebBox', a monitoring system for solar plants.
       This is an abstract base class, please use SunnyWebBoxHTTP or
       SunnyWebBoxUDPStream instead!
    """
   
    def __init__(self, host='192.168.0.98', password='*****'):
        self.host = host
        if password:
            self.pwenc = hashlib.md5(password).hexdigest()
        else:
            self.pwenc = ''
        self.count = Counter(1)
        self.openConnection()
   
    def openConnection(self):
        raise Exception('Abstract base class, use child class instead!')

    def newRequest(self, name='GetPlantOverview', withPW=False, **params):
        """return a new rpc request structure (dictionary)"""
        r = {'version': '1.0', 'proc': name, 'format': 'JSON'}
        r['id'] = str(self.count())
        if withPW:
            r['passwd'] = self.pwenc
        if params:
            r['params'] = params
        return r

    def _rpc(self, *args):
        raise Exception('Abstract base class, use child class instead!')

    def printOverview(self):
        """print a short overview of the plant data"""
        for v in self.getPlantOverview():
            print("%15s (%15s): %s %s" %
                (v['name'], v['meta'], v['value'], v['unit']))

    # wrapper methods for the RPC functions

    def getPlantOverview(self):
        res = self._rpc(self.newRequest('GetPlantOverview'))
        return res['overview']

    def getDevices(self):
        res = self._rpc(self.newRequest('GetDevices'))
        return res['devices']

    def getProcessDataChannels(self, deviceKey):
        res = self._rpc(self.newRequest('GetProcessDataChannels', device=deviceKey))
        return res[deviceKey]

    def getProcessData(self, channels):
        res = self._rpc(self.newRequest('GetProcessData', devices=channels))
        # reorder data structure: {devKey: {dict of channels}, ...}
        # return {l['key']: l['channels'] for l in res['devices']}
        r = {}
        for l in res['devices']:
            r[l['key']] = l['channels']
        return r

    def getParameterChannels(self, deviceKey):
        res = self._rpc(self.newRequest('GetParameterChannels', withPW=True, device=deviceKey))
        return res[deviceKey]

    def getParameter(self, channels):
        res = self._rpc(self.newRequest('GetParameter', withPW=True, devices=channels))
        # reorder data structure: {devKey: {dict of channels}, ...}
        # return {l['key']: l['channels'] for l in res['devices']}
        r = {}
        for l in res['devices']:
            r[l['key']] = l['channels']
        return r

    def setParameter(self, *args):
        raise Exception('Not yet implemented!')


class SunnyWebBoxHTTP(SunnyWebBoxBase):
    """Communication with a 'Sunny WebBox' via HTTP."""
   
    def openConnection(self):
        if py3:
            from http.client import HTTPConnection
        else:
            from httplib import HTTPConnection
        self.conn  = HTTPConnection(self.host)

    def _rpc(self, request):
        """send an rpc request as JSON object via http and read the result"""
        js = json.dumps(request)
        self.conn.request('POST', '/rpc', "RPC=%s" % js)
        tmp = buf2str(self.conn.getresponse().read())
        response = json.loads(tmp)
        if response['id'] != request['id']:
            raise Exception('RPC answer has wrong id!')
        return response['result']


class SunnyWebBoxUDPStream(SunnyWebBoxBase):
    """Communication with a 'Sunny WebBox' via UDP Stream."""
   
    def openConnection(self):
        from socket import socket, AF_INET, SOCK_DGRAM
        self.udpPort = 34268
        self.ssock = socket(AF_INET, SOCK_DGRAM)
        self.rsock = socket(AF_INET, SOCK_DGRAM)
        self.rsock.bind(("", self.udpPort))
        self.rsock.settimeout(100.0)

    def _rpc(self, request):
        """send an rpc request as JSON object via UDP Stream and read the result"""
        js = ''.join(i+'\0' for i in json.dumps(request, separators=(',',':')))
        self.ssock.sendto(str2buf(js), (self.host, self.udpPort))
        while True:
            data, addr = self.rsock.recvfrom(10*1024)
            if addr[0] == self.host:
                break
        tmp = buf2str(data).replace('\0', '')
        response = json.loads(tmp)
        if response['id'] != request['id']:
            raise Exception('RPC answer has wrong id!')
        return response['result']


if __name__ == '__main__':
   
   # pw = ''
    #ip = sys.argv[1]
    #if len(sys.argv) > 2:
     #   pw = sys.argv[2]

   ip = "192.168.0.98"
   pw = "******"
   
swb = SunnyWebBoxHTTP(ip, password=pw)
    # swb = SunnyWebBoxUDPStream(ip, password=pw)

print('###### Overview:')
swb.printOverview()
   
for d in swb.getDevices():
        devKey = d['key']
        print("\n  #### Device %s (%s):" % (devKey, d['name']))
       
        print("\n    ## Process data:")
        channels = swb.getProcessDataChannels(devKey)
        data = swb.getProcessData([{'key':devKey, 'channels':channels}])
        for c in data[devKey]:
           print("      %15s (%15s): %s %s" %
               (c['name'], c['meta'], c['value'], c['unit']))

               
        print("\n    ## Parameters:")
        channels = swb.getParameterChannels(devKey)
        data = swb.getParameter([{'key':devKey, 'channels':channels}])
        for c in data[devKey]:
            print("      %15s (%15s): %s %s" %
                (c['name'], c['meta'], c['value'], c['unit']))



output looks like this in terminal, the results i'm looking to get into Indigo are "Pac", "GriPwr", and "GriEgyTdy"):

###### Overview:
GriPwr ( GriPwr): 2734 W
GriEgyTdy ( GriEgyTdy): 45.728 kWh
GriEgyTot ( GriEgyTot): 55805.878 kWh
OpStt ( OpStt):
Msg ( Msg):

#### Device WR8KU002:2002099375 (WR8KU002:2002099375):

## Process data:
Backup State ( Backup State): -----
CO2 saved ( CO2 saved): 94870.000 lbs
Error ( Error): -------
E-Total ( E-Total): 55805.878 kWh
Fac ( Fac): 60.010 Hz
Grid Type ( Grid Type): 240V w/o N
h-On ( h-On): 22557.7320823962 h
h-Total ( h-Total): 21499.3419977250 h
Iac ( Iac): 11.732 A
Ipv ( Ipv): 9.736 A
Mode ( Mode): Mpp
Pac ( Pac): 2734 W
Power On ( Power On): 6822
Serial Number ( Serial Number): 2002099375
Temperature ( Temperature): 60.3 degC
Vac ( Vac): 233.0 V
Vpv ( Vpv): 291 V

## Parameters:
Backup Mode ( Backup Mode): Off
CO2-Fact ( CO2-Fact): 1.7 lbs/kWh
Default ( Default): USA/UL1741/2005
E_Total ( E_Total): 55805.48 kWh
Fac-delta- ( Fac-delta-): 0.69 Hz
Fac-delta ( Fac-delta ):
Fac-Limit delta (Fac-Limit delta): 2 Hz
Fac-MinTripTime (Fac-MinTripTime): 0.16 s
Fac-Start delta (Fac-Start delta): 1 Hz
Fan-Test ( Fan-Test): 0
h_Total ( h_Total): 21499.21 h
KI-Wind-Reg ( KI-Wind-Reg): 0.005
KP-Wind-Reg ( KP-Wind-Reg): 0.02
Memory Function (Memory Function): No function
Operating mode ( Operating mode): Mpp-Operation
Plimit ( Plimit): 8100 W
Pmax ( Pmax): 8100 W
PowerBalancer ( PowerBalancer): Off
PowerBalMax ( PowerBalMax): 8000 W
P-Wind-Mid ( P-Wind-Mid): 2000 W
P-Wind-Ramp ( P-Wind-Ramp): 1000 W/s
SMA-SN ( SMA-SN): 2002099375
Software-BFR ( Software-BFR): 1.18 Version
Software-SRR ( Software-SRR): 1.18 Version
T-Max-Fan ( T-Max-Fan): 90 degC
T-Start ( T-Start): 10 s
T-Start-Fan ( T-Start-Fan): 70 degC
T-Stop ( T-Stop): 2 s
T-Stop-Fan ( T-Stop-Fan): 55 degC
Vac-Max ( Vac-Max): 10 %
Vac-Max-Fast ( Vac-Max-Fast): 20 %
Vac-Max-Recnct ( Vac-Max-Recnct): 5.83 %
Vac-Min ( Vac-Min): 12 %
Vac-Min-Fast ( Vac-Min-Fast): 50 %
Vac-Min-Recnct ( Vac-Min-Recnct): 11.7 %
V-Const Setval ( V-Const Setval): 600 V
VdcWindMax ( VdcWindMax): 550 V
VdcWindMid ( VdcWindMid): 380 V
VdcWindStart ( VdcWindStart): 310 V
Vpv-Max ( Vpv-Max): 600 V
Vpv-Start ( Vpv-Start): 365 V

Posted on
Sat Jul 15, 2017 7:01 pm
DaveL17 offline
User avatar
Posts: 6753
Joined: Aug 20, 2013
Location: Chicago, IL, USA

Re: New Plugin: GhostXML

Looks like you've got the hard part figured out. As an example, you're printing the values to the log, you just need a similar kind of operation to save the values for later use. One way would be to create a plugin that has a custom device that stores the values into device states. The other is obviously to save the values to variables. I'd suggest that to start.

This statement is used to store values into variables (they must be strings to store them, so some might need to be converted to strings.
Code: Select all
indigo.variable.updateValue(VARIABLE_ID, "VALUE_STRING")

You can use the variable ID or the variable Name. This presumes the variable already exists.

I came here to drink milk and kick ass....and I've just finished my milk.

[My Plugins] - [My Forums]

Posted on
Sat Jul 15, 2017 7:07 pm
dtich offline
Posts: 798
Joined: Sep 24, 2005

Re: New Plugin: GhostXML

great dave! thanks

i've got that part, the python variables for indigo, so, the syntax for setting a value string from the script to indigo var is:

for Pac:
#indigo.variables[1958563501] # "PV_Pac"


something like, but correctly done??:

indigo.variable.updateValue(1958563501, Pac)

? will this work? or is the syntax different. i'm trying it..

thanks again!

Posted on
Sat Jul 15, 2017 10:46 pm
howartp offline
Posts: 4559
Joined: Jan 09, 2014
Location: West Yorkshire, UK

Re: New Plugin: GhostXML

The latter will work, if you've a Python variable called Pac.

Code: Select all
pac = 2734
indigo.updateValue(1958563501, Pac)


The interesting part is going to be adding that to your code.

You're gonna have to go to the printOverview() method and where it says

for v in self.getPlantOverview()

You need to add line something like

Code: Select all
if (v['name'] == 'GriEgyTdy'):
indigo.updateValue(1958563501, v['value'])


getPlantOverview() is returning a list of names (GriEgyTdy, GriEgyTot etc) and their values, of which we only want GriEgyTdy. We then store that value into our indigo variable.

I've used your PV_Pac indigo variable number as that's the only one you've given us.

Do you know if Pac and GriEgyTdy are the same thing, or is it just coincidence that they're the same in your log output.

Having said all that....... I've just noticed you're still in Terminal, so you need to get it into an Indigo script (Actions > New > Execute script) first.


Sent from my iPhone using Tapatalk Pro

Posted on
Sun Jul 16, 2017 4:12 am
DaveL17 offline
User avatar
Posts: 6753
Joined: Aug 20, 2013
Location: Chicago, IL, USA

Re: New Plugin: GhostXML

Thanks Peter.

I completely forgot that the script you're using is not one you authored (sorry about that). I think we can help. What we need to do is access the JSON values you want in some kind of logical way--such that adding more stuff later (should you choose to) shouldn't be too hard. This line of code causes the script to reach out to get the RPC data. It appears to return a JSON object.
Code: Select all
swb = SunnyWebBoxHTTP(ip, password=pw)

Please try running this version of the script (below) and post the resulting output. I've added the new bit to print out the result so that we can see it in its native state. If you've moved your script into Indigo as Peter suggested, you'll need to change the line I added from a 'print' statement to an 'indigo.server.log' statement.
Code: Select all
print(unicode(swb))

or
Code: Select all
indigo.server.logt(unicode(swb))

Code: Select all
#! /usr/bin/env python
# -*- coding: utf-8 -*-


#!/usr/bin/env python
# version: 0.3
# date:    2012-11-30
# author:  Joerg Raedler joerg@j-raedler.de
# license: BSD
# purpose: make RPC to a Sunny WebBox, a monitoring system for solar plants
#
# http://www.sma.de/en/produkte/monitoring-systems/sunny-webbox.html
#
# Use the classes SunnyWebBoxHTTP or SunnyWebBoxUDPStream in your code
# or run this file as a script to test the classes. First parameter is
# the hostname or IP of the box. A password can be provided with an
# optional second parameter.
#
# Example: python SunnyWebBox.py 123.123.123.123 "foobar"
#
# Documentation on the RPC API can be found here:
#
# http://files.sma.de/dl/2585/SWebBoxRPC-BEN092713.pdf
#

import sys, json, hashlib

# handle python 2 and 3
if sys.version_info >= (3, 0):
    py3 = True
    def str2buf(s):
        return bytes(s, 'latin1')
    def buf2str(b):
        return str(b, 'latin1')
else:
    py3 = False
    def str2buf(s):
        return bytes(s)
    def buf2str(b):
        return unicode(b, 'latin1')


class Counter:
    """generate increasing numbers with every call - just for convenience"""
   
    def __init__(self, start=0):
        self.i = start

    def __call__(self):
        i = self.i
        self.i += 1
        return i


class SunnyWebBoxBase(object):
    """Communication with a 'Sunny WebBox', a monitoring system for solar plants.
       This is an abstract base class, please use SunnyWebBoxHTTP or
       SunnyWebBoxUDPStream instead!
    """
   
    def __init__(self, host='192.168.0.98', password='*****'):
        self.host = host
        if password:
            self.pwenc = hashlib.md5(password).hexdigest()
        else:
            self.pwenc = ''
        self.count = Counter(1)
        self.openConnection()
   
    def openConnection(self):
        raise Exception('Abstract base class, use child class instead!')

    def newRequest(self, name='GetPlantOverview', withPW=False, **params):
        """return a new rpc request structure (dictionary)"""
        r = {'version': '1.0', 'proc': name, 'format': 'JSON'}
        r['id'] = str(self.count())
        if withPW:
            r['passwd'] = self.pwenc
        if params:
            r['params'] = params
        return r

    def _rpc(self, *args):
        raise Exception('Abstract base class, use child class instead!')

    def printOverview(self):
        """print a short overview of the plant data"""
        for v in self.getPlantOverview():
            print("%15s (%15s): %s %s" %
                (v['name'], v['meta'], v['value'], v['unit']))

    # wrapper methods for the RPC functions

    def getPlantOverview(self):
        res = self._rpc(self.newRequest('GetPlantOverview'))
        return res['overview']

    def getDevices(self):
        res = self._rpc(self.newRequest('GetDevices'))
        return res['devices']

    def getProcessDataChannels(self, deviceKey):
        res = self._rpc(self.newRequest('GetProcessDataChannels', device=deviceKey))
        return res[deviceKey]

    def getProcessData(self, channels):
        res = self._rpc(self.newRequest('GetProcessData', devices=channels))
        # reorder data structure: {devKey: {dict of channels}, ...}
        # return {l['key']: l['channels'] for l in res['devices']}
        r = {}
        for l in res['devices']:
            r[l['key']] = l['channels']
        return r

    def getParameterChannels(self, deviceKey):
        res = self._rpc(self.newRequest('GetParameterChannels', withPW=True, device=deviceKey))
        return res[deviceKey]

    def getParameter(self, channels):
        res = self._rpc(self.newRequest('GetParameter', withPW=True, devices=channels))
        # reorder data structure: {devKey: {dict of channels}, ...}
        # return {l['key']: l['channels'] for l in res['devices']}
        r = {}
        for l in res['devices']:
            r[l['key']] = l['channels']
        return r

    def setParameter(self, *args):
        raise Exception('Not yet implemented!')


class SunnyWebBoxHTTP(SunnyWebBoxBase):
    """Communication with a 'Sunny WebBox' via HTTP."""
   
    def openConnection(self):
        if py3:
            from http.client import HTTPConnection
        else:
            from httplib import HTTPConnection
        self.conn  = HTTPConnection(self.host)

    def _rpc(self, request):
        """send an rpc request as JSON object via http and read the result"""
        js = json.dumps(request)
        self.conn.request('POST', '/rpc', "RPC=%s" % js)
        tmp = buf2str(self.conn.getresponse().read())
        response = json.loads(tmp)
        if response['id'] != request['id']:
            raise Exception('RPC answer has wrong id!')
        return response['result']


class SunnyWebBoxUDPStream(SunnyWebBoxBase):
    """Communication with a 'Sunny WebBox' via UDP Stream."""
   
    def openConnection(self):
        from socket import socket, AF_INET, SOCK_DGRAM
        self.udpPort = 34268
        self.ssock = socket(AF_INET, SOCK_DGRAM)
        self.rsock = socket(AF_INET, SOCK_DGRAM)
        self.rsock.bind(("", self.udpPort))
        self.rsock.settimeout(100.0)

    def _rpc(self, request):
        """send an rpc request as JSON object via UDP Stream and read the result"""
        js = ''.join(i+'\0' for i in json.dumps(request, separators=(',',':')))
        self.ssock.sendto(str2buf(js), (self.host, self.udpPort))
        while True:
            data, addr = self.rsock.recvfrom(10*1024)
            if addr[0] == self.host:
                break
        tmp = buf2str(data).replace('\0', '')
        response = json.loads(tmp)
        if response['id'] != request['id']:
            raise Exception('RPC answer has wrong id!')
        return response['result']


if __name__ == '__main__':
   
    # pw = ''
    # ip = sys.argv[1]
    # if len(sys.argv) > 2:
    #    pw = sys.argv[2]

   ip = "192.168.0.98"
   pw = "******"
   
swb = SunnyWebBoxHTTP(ip, password=pw)
# swb = SunnyWebBoxUDPStream(ip, password=pw)

# print('###### Overview:')
# swb.printOverview()
#     
# for d in swb.getDevices():
#         devKey = d['key']
#         print("\n  #### Device %s (%s):" % (devKey, d['name']))
#         
#         print("\n    ## Process data:")
#         channels = swb.getProcessDataChannels(devKey)
#         data = swb.getProcessData([{'key':devKey, 'channels':channels}])
#         for c in data[devKey]:
#            print("      %15s (%15s): %s %s" %
#                (c['name'], c['meta'], c['value'], c['unit']))
#
#               
#         print("\n    ## Parameters:")
#         channels = swb.getParameterChannels(devKey)
#         data = swb.getParameter([{'key':devKey, 'channels':channels}])
#         for c in data[devKey]:
#             print("      %15s (%15s): %s %s" %
#                 (c['name'], c['meta'], c['value'], c['unit']))

print(unicode(swb))

I came here to drink milk and kick ass....and I've just finished my milk.

[My Plugins] - [My Forums]

Posted on
Sun Jul 16, 2017 11:18 am
dtich offline
Posts: 798
Joined: Sep 24, 2005

Re: New Plugin: GhostXML

thanks dave, yes, i have been running on indigo the whole time, just testing syntax in terminal first.

i will copy your script and post, thanks!

also i'll send you what i've been running for ref. even though it doesn't work, lol.

(i had been responding on all this in the other forum)

Posted on
Sun Jul 16, 2017 11:25 am
dtich offline
Posts: 798
Joined: Sep 24, 2005

Re: New Plugin: GhostXML

that script gives this:

Code: Select all
Action Group                    sma webbox poll
   Script Error                    webbox_poll_2.py: 'ServerInfo' object has no attribute 'logt'
   Script Error                    Exception Traceback (most recent call shown last):

     webbox_poll_2.py, line 214, at top level
AttributeError: 'ServerInfo' object has no attribute 'logt'


looking at that line now...

(i replaced the last line with the indigo log version, yes)

Posted on
Sun Jul 16, 2017 11:27 am
dtich offline
Posts: 798
Joined: Sep 24, 2005

Re: New Plugin: GhostXML

taking the 't' out of 'logt' in that last line and running returns this:


Code: Select all
Jul 16, 2017, 10:26:08 AM
   Action Group                    sma webbox poll
   Script                          <__main__.SunnyWebBoxHTTP object at 0x10f133c10>
   Received INSTEON                "hvac/therm A" temperature changed to 77.0


not sure what this means.

Posted on
Sun Jul 16, 2017 11:29 am
dtich offline
Posts: 798
Joined: Sep 24, 2005

Re: New Plugin: GhostXML

this is the script i had been running, with my attempts to get indigo variables updating...

it threw a ton of indent errors and others and i tracked down as far as i could, but got stymied and then it was too late, :D


thanks for looking!

Code: Select all
#!/usr/bin/env python
# version: 0.3
# date:    2012-11-30
# author:  Joerg Raedler joerg@j-raedler.de
# license: BSD
# purpose: make RPC to a Sunny WebBox, a monitoring system for solar plants
#
# http://www.sma.de/en/produkte/monitoring-systems/sunny-webbox.html
#
# Use the classes SunnyWebBoxHTTP or SunnyWebBoxUDPStream in your code
# or run this file as a script to test the classes. First parameter is
# the hostname or IP of the box. A password can be provided with an
# optional second parameter.
#
# Example: python SunnyWebBox.py 123.123.123.123 "foobar"
#
# Documentation on the RPC API can be found here:
#
# http://files.sma.de/dl/2585/SWebBoxRPC-BEN092713.pdf
#

import sys, json, hashlib


# INDIGO GLOBALS
global Pac, Mode, GriEgyTot, GriEgyTdy, GriPwr 


# handle python 2 and 3
if sys.version_info >= (3, 0):
    py3 = True
    def str2buf(s):
        return bytes(s, 'latin1')
    def buf2str(b):
        return str(b, 'latin1')
else:
    py3 = False
    def str2buf(s):
        return bytes(s)
    def buf2str(b):
        return unicode(b, 'latin1')


class Counter:
    """generate increasing numbers with every call - just for convenience"""
   
    def __init__(self, start=0):
        self.i = start

    def __call__(self):
        i = self.i
        self.i += 1
        return i


class SunnyWebBoxBase(object):
    """Communication with a 'Sunny WebBox', a monitoring system for solar plants.
       This is an abstract base class, please use SunnyWebBoxHTTP or
       SunnyWebBoxUDPStream instead!
    """
   
    def __init__(self, host='192.168.0.98', password='*****'):
        self.host = host
        if password:
            self.pwenc = hashlib.md5(password).hexdigest()
        else:
            self.pwenc = ''
        self.count = Counter(1)
        self.openConnection()
   
    def openConnection(self):
        raise Exception('Abstract base class, use child class instead!')

    def newRequest(self, name='GetPlantOverview', withPW=False, **params):
        """return a new rpc request structure (dictionary)"""
        r = {'version': '1.0', 'proc': name, 'format': 'JSON'}
        r['id'] = str(self.count())
        if withPW:
            r['passwd'] = self.pwenc
        if params:
            r['params'] = params
        return r

    def _rpc(self, *args):
        raise Exception('Abstract base class, use child class instead!')

    def printOverview(self):
        """print a short overview of the plant data"""
        for v in self.getPlantOverview():
            print("%15s (%15s): %s %s" %
                (v['name'], v['meta'], v['value'], v['unit']))
           
            # INDIGO UPDATE VARIABLES SECTION

            if (v['name'] == 'GriEgyTdy'):
               indigo.variable.updateValue(1822257245, v['value'])
            if (v['name'] == 'GriEgyTot'):
               indigo.variable.updateValue(1469286291, v['value'])
         if (v['name'] == 'GriPwr'):
               indigo.variable.updateValue(1958563501, v['value']) 
         #if (v['name'] == 'Mode'):
               #indigo.variable.updateValue(789149473, v['value'])



    # wrapper methods for the RPC functions

   def getPlantOverview(self):
        res = self._rpc(self.newRequest('GetPlantOverview'))
        return res['overview']

   def getDevices(self):
        res = self._rpc(self.newRequest('GetDevices'))
        return res['devices']

   def getProcessDataChannels(self, deviceKey):
        res = self._rpc(self.newRequest('GetProcessDataChannels', device=deviceKey))
        return res[deviceKey]

   def getProcessData(self, channels):
        res = self._rpc(self.newRequest('GetProcessData', devices=channels))
        # reorder data structure: {devKey: {dict of channels}, ...}
        # return {l['key']: l['channels'] for l in res['devices']}
        r = {}
        for l in res['devices']:
            r[l['key']] = l['channels']
        return r

   def getParameterChannels(self, deviceKey):
        res = self._rpc(self.newRequest('GetParameterChannels', withPW=True, device=deviceKey))
        return res[deviceKey]

   def getParameter(self, channels):
        res = self._rpc(self.newRequest('GetParameter', withPW=True, devices=channels))
        # reorder data structure: {devKey: {dict of channels}, ...}
        # return {l['key']: l['channels'] for l in res['devices']}
        r = {}
        for l in res['devices']:
            r[l['key']] = l['channels']
        return r

   def setParameter(self, *args):
        raise Exception('Not yet implemented!')


class SunnyWebBoxHTTP(SunnyWebBoxBase):
    """Communication with a 'Sunny WebBox' via HTTP."""
   
    def openConnection(self):
        if py3:
            from http.client import HTTPConnection
        else:
            from httplib import HTTPConnection
        self.conn  = HTTPConnection(self.host)

    def _rpc(self, request):
        """send an rpc request as JSON object via http and read the result"""
        js = json.dumps(request)
        self.conn.request('POST', '/rpc', "RPC=%s" % js)
        tmp = buf2str(self.conn.getresponse().read())
        response = json.loads(tmp)
        if response['id'] != request['id']:
            raise Exception('RPC answer has wrong id!')
        return response['result']


class SunnyWebBoxUDPStream(SunnyWebBoxBase):
    """Communication with a 'Sunny WebBox' via UDP Stream."""
   
    def openConnection(self):
        from socket import socket, AF_INET, SOCK_DGRAM
        self.udpPort = 34268
        self.ssock = socket(AF_INET, SOCK_DGRAM)
        self.rsock = socket(AF_INET, SOCK_DGRAM)
        self.rsock.bind(("", self.udpPort))
        self.rsock.settimeout(100.0)

    def _rpc(self, request):
        """send an rpc request as JSON object via UDP Stream and read the result"""
        js = ''.join(i+'\0' for i in json.dumps(request, separators=(',',':')))
        self.ssock.sendto(str2buf(js), (self.host, self.udpPort))
        while True:
            data, addr = self.rsock.recvfrom(10*1024)
            if addr[0] == self.host:
                break
        tmp = buf2str(data).replace('\0', '')
        response = json.loads(tmp)
        if response['id'] != request['id']:
            raise Exception('RPC answer has wrong id!')
        return response['result']


if __name__ == '__main__':
   
   # pw = ''
    #ip = sys.argv[1]
    #if len(sys.argv) > 2:
     #   pw = sys.argv[2]

   ip = "192.168.0.98"
   pw = "*****"
   
swb = SunnyWebBoxHTTP(ip, password=pw)
    # swb = SunnyWebBoxUDPStream(ip, password=pw)

print('###### Overview:')
swb.printOverview()
   
for d in swb.getDevices():
        devKey = d['key']
        print("\n  #### Device %s (%s):" % (devKey, d['name']))
       
        print("\n    ## Process data:")
        channels = swb.getProcessDataChannels(devKey)
        data = swb.getProcessData([{'key':devKey, 'channels':channels}])
        for c in data[devKey]:
           print("      %15s (%15s): %s %s" %
               (c['name'], c['meta'], c['value'], c['unit']))

               
        print("\n    ## Parameters:")
        channels = swb.getParameterChannels(devKey)
        data = swb.getParameter([{'key':devKey, 'channels':channels}])
        for c in data[devKey]:
            print("      %15s (%15s): %s %s" %
                (c['name'], c['meta'], c['value'], c['unit']))
               
               
               
               
               
               
               
#     indigo.variables[1822257245] # "PV_EToday"
#   indigo.variables[1469286291] # "PV_ETotal"
#   indigo.variables[789149473] # "PV_Mode"
#     indigo.variables[1958563501] # "PV_Pac"
#   indigo.variables[532315452] # "PV_Overview"
             
#   global Pac, Mode, GriEgyTot, GriEgyTdy     

#   Pac =   
               
               
#indigo.variable.updateValue(1958563501, "Pac")
#indigo.variable.updateValue(1958563501, str(value of variable "Pac" as string))
#indigo.variable.updateValue(789149473, "Mode")
#indigo.variable.updateValue(1469286291, "GriEgyTot")
#indigo.variable.updateValue(1822257245, "GriEgyTdy")
#indigo.variable.updateValue(532315452, overview)

               
# indigo.server.log("The variable had a value of 'true'")

Posted on
Sun Jul 16, 2017 11:58 am
dtich offline
Posts: 798
Joined: Sep 24, 2005

Re: New Plugin: GhostXML

so, tried adding indigo vars in, and tweaking few things. commented out the UDP section, etc.

result:

Code: Select all
Jul 16, 2017, 10:54:18 AM
   Action Group                    sma webbox poll
   Script                          <__main__.SunnyWebBoxHTTP object at 0x108039b10>


so, i'm now not getting errors running in indigo, but neither is it updating variables or printing log entry correctly. (the variable there is unclear, swb is just a name field i think, and the hash of the password..)

thoughts at this point most appreciated!

here's the current version:

Code: Select all
#! /usr/bin/env python
# -*- coding: utf-8 -*-


#!/usr/bin/env python
# version: 0.3
# date:    2012-11-30
# author:  Joerg Raedler joerg@j-raedler.de
# license: BSD
# purpose: make RPC to a Sunny WebBox, a monitoring system for solar plants
#
# http://www.sma.de/en/produkte/monitoring-systems/sunny-webbox.html
#
# Use the classes SunnyWebBoxHTTP or SunnyWebBoxUDPStream in your code
# or run this file as a script to test the classes. First parameter is
# the hostname or IP of the box. A password can be provided with an
# optional second parameter.
#
# Example: python SunnyWebBox.py 123.123.123.123 "foobar"
#
# Documentation on the RPC API can be found here:
#
# http://files.sma.de/dl/2585/SWebBoxRPC-BEN092713.pdf
#

import sys, json, hashlib

# handle python 2 and 3
if sys.version_info >= (3, 0):
    py3 = True
    def str2buf(s):
        return bytes(s, 'latin1')
    def buf2str(b):
        return str(b, 'latin1')
else:
    py3 = False
    def str2buf(s):
        return bytes(s)
    def buf2str(b):
        return unicode(b, 'latin1')


class Counter:
    """generate increasing numbers with every call - just for convenience"""
   
    def __init__(self, start=0):
        self.i = start

    def __call__(self):
        i = self.i
        self.i += 1
        return i


class SunnyWebBoxBase(object):
    """Communication with a 'Sunny WebBox', a monitoring system for solar plants.
       This is an abstract base class, please use SunnyWebBoxHTTP or
       SunnyWebBoxUDPStream instead!
    """
   
    def __init__(self, host='192.168.0.98', password='*****'):
        self.host = host
        if password:
            self.pwenc = hashlib.md5(password).hexdigest()
        else:
            self.pwenc = ''
        self.count = Counter(1)
        self.openConnection()
   
    def openConnection(self):
        raise Exception('Abstract base class, use child class instead!')

    def newRequest(self, name='GetPlantOverview', withPW=False, **params):
        """return a new rpc request structure (dictionary)"""
        r = {'version': '1.0', 'proc': name, 'format': 'JSON'}
        r['id'] = str(self.count())
        if withPW:
            r['passwd'] = self.pwenc
        if params:
            r['params'] = params
        return r

    def _rpc(self, *args):
        raise Exception('Abstract base class, use child class instead!')

    def printOverview(self):
        """print a short overview of the plant data"""
        for v in self.getPlantOverview():
            print("%15s (%15s): %s %s" %
                (v['name'], v['meta'], v['value'], v['unit']))

# INDIGO UPDATE VARIABLES SECTION

#     indigo.variables[1822257245] # "PV_EToday"
#   indigo.variables[1469286291] # "PV_ETotal"
#   indigo.variables[789149473] # "PV_Mode"
#     indigo.variables[1958563501] # "PV_Pac"
#   indigo.variables[532315452] # "PV_Overview"

            if (v['meta'] == 'GriEgyTdy'):
               indigo.variable.updateValue(1822257245, v['value'])
            if (v['name'] == 'GriEgyTot'):
               indigo.variable.updateValue(1469286291, v['value'])
         #if (v['name'] == 'GriPwr'):
               #indigo.variable.updateValue(1958563501, v['value']) 
         #if (v['name'] == 'Mode'):
               #indigo.variable.updateValue(789149473, v['value'])

    # wrapper methods for the RPC functions

    def getPlantOverview(self):
        res = self._rpc(self.newRequest('GetPlantOverview'))
        return res['overview']

    def getDevices(self):
        res = self._rpc(self.newRequest('GetDevices'))
        return res['devices']

    def getProcessDataChannels(self, deviceKey):
        res = self._rpc(self.newRequest('GetProcessDataChannels', device=deviceKey))
        return res[deviceKey]

    def getProcessData(self, channels):
        res = self._rpc(self.newRequest('GetProcessData', devices=channels))
        # reorder data structure: {devKey: {dict of channels}, ...}
        # return {l['key']: l['channels'] for l in res['devices']}
        r = {}
        for l in res['devices']:
            r[l['key']] = l['channels']
        return r

    def getParameterChannels(self, deviceKey):
        res = self._rpc(self.newRequest('GetParameterChannels', withPW=True, device=deviceKey))
        return res[deviceKey]

    def getParameter(self, channels):
        res = self._rpc(self.newRequest('GetParameter', withPW=True, devices=channels))
        # reorder data structure: {devKey: {dict of channels}, ...}
        # return {l['key']: l['channels'] for l in res['devices']}
        r = {}
        for l in res['devices']:
            r[l['key']] = l['channels']
        return r

    def setParameter(self, *args):
        raise Exception('Not yet implemented!')


class SunnyWebBoxHTTP(SunnyWebBoxBase):
    """Communication with a 'Sunny WebBox' via HTTP."""
   
    def openConnection(self):
        if py3:
            from http.client import HTTPConnection
        else:
            from httplib import HTTPConnection
        self.conn  = HTTPConnection(self.host)

    def _rpc(self, request):
        """send an rpc request as JSON object via http and read the result"""
        js = json.dumps(request)
        self.conn.request('POST', '/rpc', "RPC=%s" % js)
        tmp = buf2str(self.conn.getresponse().read())
        response = json.loads(tmp)
        if response['id'] != request['id']:
            raise Exception('RPC answer has wrong id!')
        return response['result']


#class SunnyWebBoxUDPStream(SunnyWebBoxBase):
    """Communication with a 'Sunny WebBox' via UDP Stream."""
   
    #def openConnection(self):
        #from socket import socket, AF_INET, SOCK_DGRAM
        #self.udpPort = 34268
        #self.ssock = socket(AF_INET, SOCK_DGRAM)
        #self.rsock = socket(AF_INET, SOCK_DGRAM)
        #self.rsock.bind(("", self.udpPort))
        #self.rsock.settimeout(100.0)

    #def _rpc(self, request):
        #"""send an rpc request as JSON object via UDP Stream and read the result"""
        #js = ''.join(i+'\0' for i in json.dumps(request, separators=(',',':')))
        #self.ssock.sendto(str2buf(js), (self.host, self.udpPort))
        #while True:
            #data, addr = self.rsock.recvfrom(10*1024)
            #if addr[0] == self.host:
                #break
        #tmp = buf2str(data).replace('\0', '')
        #response = json.loads(tmp)
        #if response['id'] != request['id']:
            #raise Exception('RPC answer has wrong id!')
        #return response['result']


#if __name__ == '__main__':
   
     #pw = ''
     #ip = sys.argv[1]
     #if len(sys.argv) > 2:
        #pw = sys.argv[2]

ip = "192.168.0.98"
pw = "******"
   
swb = SunnyWebBoxHTTP(ip, password=pw)
# swb = SunnyWebBoxUDPStream(ip, password=pw)

# print('###### Overview:')
# swb.printOverview()
#     
# for d in swb.getDevices():
#         devKey = d['key']
#         print("\n  #### Device %s (%s):" % (devKey, d['name']))
#         
#         print("\n    ## Process data:")
#         channels = swb.getProcessDataChannels(devKey)
#         data = swb.getProcessData([{'key':devKey, 'channels':channels}])
#         for c in data[devKey]:
#            print("      %15s (%15s): %s %s" %
#                (c['name'], c['meta'], c['value'], c['unit']))
#
#               
#         print("\n    ## Parameters:")
#         channels = swb.getParameterChannels(devKey)
#         data = swb.getParameter([{'key':devKey, 'channels':channels}])
#         for c in data[devKey]:
#             print("      %15s (%15s): %s %s" %
#                 (c['name'], c['meta'], c['value'], c['unit']))

indigo.server.log(unicode(swb))

Posted on
Sun Jul 16, 2017 12:07 pm
dtich offline
Posts: 798
Joined: Sep 24, 2005

Re: New Plugin: GhostXML

aha!

so, using the last script i posted, and adding globals, still not working. so -- i uncommented the print section at the bottom (surmising that perhaps that is where the actual variable setting in python was occurring) and now i get updating variables in indigo! major success in a way.

!!

still sorting getting all the variables, and the logging correct. but, it's starting to work.


thanks all so much for the continued help.

dt

Posted on
Sun Jul 16, 2017 12:20 pm
howartp offline
Posts: 4559
Joined: Jan 09, 2014
Location: West Yorkshire, UK

Re: New Plugin: GhostXML

Excellent.

I just wrote this reply (below) and previewed it when I found your response; I'll still post it anyway as it's worth looking at doing it Dave's way going forward, even if you've got it going with my original suggestions for now:

Dave is right to get us going down the proper route for futurability. I was just chickening out of creating our own JSON. :)

Dave, can you form the request from here into a JSON object using your knowledge of JSON?

We need
Code: Select all
jsonRequest = "SOME_JSON_FROM_DAVE"
swb = SunnyWebBoxHTTP(ip, password=pw)
jsonData = swb._rpc(jsonRequest)

but to achieve that we need the JSON request encapsulating.

Peter

Posted on
Sun Jul 16, 2017 12:26 pm
dtich offline
Posts: 798
Joined: Sep 24, 2005

Re: New Plugin: GhostXML

thanks peter.

here's where i'm at now, i get two of the four vars updating, but it's throwing indent error for the others, i'm stymied as the lines look fine to me, but.. i wouldn't really know.

the pertinent script section:

Code: Select all
 def printOverview(self):
        """print a short overview of the plant data"""
        for v in self.getPlantOverview():
            print("%15s (%15s): %s %s" %
                (v['name'], v['meta'], v['value'], v['unit']))

# INDIGO UPDATE VARIABLES SECTION

#     indigo.variables[1822257245] # "PV_EToday"
#   indigo.variables[1469286291] # "PV_ETotal"
#   indigo.variables[789149473] # "PV_Mode"
#     indigo.variables[1958563501] # "PV_Pac"
#   indigo.variables[532315452] # "PV_Overview"

            if (v['meta'] == 'GriEgyTdy'):
               indigo.variable.updateValue(1822257245, v['value'])
            if (v['name'] == 'GriEgyTot'):
               indigo.variable.updateValue(1469286291, v['value'])
         if (v['name'] == 'GriPwr'):
               indigo.variable.updateValue(1958563501, v['value']) 
         if (v['name'] == 'Mode'):
               indigo.variable.updateValue(789149473, v['value'])

    # wrapper methods for the RPC functions

    def getPlantOverview(self):
        res = self._rpc(self.newRequest('GetPlantOverview'))
        return res['overview']

    def getDevices(self):



and the log result:


Code: Select all
sma webbox poll
   Script Error                    webbox_poll_2.py: unindent does not match any outer indentation level
   Script Error                    around line 108 - "if (v['name'] == 'GriPwr'):"



NOTE: interestingly, the code as i posted it here, does have different indents, but in BBEdit it does not. so, i deleted all spaces and reindented those lines, but.. still same result. odd. thoughts?

Page 11 of 20 1 ... 8, 9, 10, 11, 12, 13, 14 ... 20

Who is online

Users browsing this forum: No registered users and 1 guest