script error help -- str encode issue

Posted on
Sun Aug 28, 2022 1:06 pm
dtich offline
Posts: 798
Joined: Sep 24, 2005

script error help -- str encode issue

getting this in log:

Code: Select all
 Script Error                    schedule "sma webbox update" script error in file smapoll.py:
   Script Error                    Strings must be encoded before hashing
   Script Error                    Exception Traceback (most recent call shown last):

     smapoll.py, line 197, at top level
     smapoll.py, line 74, in __init__
TypeError: Strings must be encoded before hashing


can anyone identify what str is the issue (assume the password) and point me to the correct syntax? thanks!!

script :

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
# updates: Dylan Tichenor for Indigo Automation system
# 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

### I stick this at the top of all Indigo python scripts
try:
    import indigo
except:
    print("This script must be run from Indigo to work properly")
    raise

### No need for globals. They are generally bad and most often unnecessary as was the case here.

# 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='xxxx'):
        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 = "xxxx"
   
swb = SunnyWebBoxHTTP(ip, password=pw)
# swb = SunnyWebBoxUDPStream(ip, password=pw)

### Update Indigo variables with values from the overview
indigo.server.log("Starting updates to Indigo variables", type="SunnyWebBox Script")
for overview_dict in swb.getPlantOverview():
   ### The indigo.variables lines above did nothing, so I just removed them.
   ### Everything else needed to be moved. The variable v used below wasn't defined as
   ### you had it before because it wasn't within the scope of the for loop.
   ### Because Indigo variables are always stored as strings, I converted each
   ### value because I wanted to make sure that they were strings.
   if overview_dict['meta'] == 'GriEgyTdy':
      indigo.variable.updateValue(1822257245, str(overview_dict['value']))
   if overview_dict['name'] == 'GriEgyTot':
      indigo.variable.updateValue(1469286291, str(overview_dict['value']))
   if overview_dict['name'] == 'GriPwr':
      indigo.variable.updateValue(1958563501, str(overview_dict['value']))
   if overview_dict['name'] == 'Mode':
      indigo.variable.updateValue(789149473, str(overview_dict['value']))


     
# 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 Aug 28, 2022 2:23 pm
jay (support) offline
Site Admin
User avatar
Posts: 18219
Joined: Mar 19, 2008
Location: Austin, Texas

Re: script error help -- str encode issue

hashlib needs bytes objects, not strings in Python 3 (because all strings are unicode). Change this line:

Code: Select all
self.pwenc = hashlib.md5(password).hexdigest()


to this:

Code: Select all
self.pwenc = hashlib.md5(password.encode('utf8')).hexdigest()


So rather than use password directly in the md5 hash (which doesn't work because password is a unicode string), you encode the string as a bytes object using the encode() function.

I *think* that should work.

Jay (Indigo Support)
Twitter | Facebook | LinkedIn

Posted on
Sun Aug 28, 2022 2:48 pm
dtich offline
Posts: 798
Joined: Sep 24, 2005

Re: script error help -- str encode issue

yup -- thanks jay!!!

Page 1 of 1

Who is online

Users browsing this forum: No registered users and 4 guests