The apt-get installation places some shell scripts (among them 
wee_database) into /usr/bin. That is outside the WeeWX tree. The reason for 
that is to place those scripts somewhere in the search path. I am not sure, 
where the setup.py installation puts those scripts. 

In my case there is a script like wee_database to add the observation types 
provided by the extension to the data_base. As the user shall call it 
separately it must be found within the search path.

And I have no glue, how to do that.

I placed the Python part of the script in the bin directory (not bin/user, 
bin only). But what to do with the shell part of the script?

gjr80 schrieb am Montag, 19. Juli 2021 um 22:47:23 UTC+2:

> I’m not sure if I understand your question correctly but provided you are 
> setting the ‘files’ parameter in install.py you can fairly much put 
> anything anywhere in the WeeWX bin and skins tree branches irrespective of 
> WeeWX install type (eg specifying bin/user will see the installer 
> automatically use the correct location of either /home/weewx/bin/user or 
> /usr/share/weewx/user depending on the install type). Of course I could be 
> misunderstanding your question completely.
>
> One other thing, be wary of placing code outside of the user and skins 
> directories; files placed anywhere else in the WeeWX tree may not survive a 
> WeeWX upgrade.
>
> Gary
>
> On Monday, 19 July 2021 at 20:28:25 UTC+10 [email protected] wrote:
>
>> I wrote an extension to read the values out of an air quality device. It 
>> supplies a lot of observation types not included in the standard database 
>> schema. So I wrote a little utility to add the necessary columns to the 
>> database (or drop them from it).
>>
>> Now the problem:
>>
>> The utilities of WeeWX like wee_database all are started by a little 
>> wrapper shell script. But the location of that shell scripts depends on 
>> installation way like setup.py or apt-get. How can I define that wrapper 
>> script in install.py that it is put to the right place during installation?
>>
>> https://github.com/roe-dl/weewx-airQ
>>
>

-- 
You received this message because you are subscribed to the Google Groups 
"weewx-development" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To view this discussion on the web visit 
https://groups.google.com/d/msgid/weewx-development/4162020d-c37e-4e63-a3e8-10cca09ba28bn%40googlegroups.com.

Attachment: airq_conf
Description: application/shellscript

#!/usr/bin/env python3
#
#    config tool for using airQ device with WeeWX
#
#    Copyright (C) 2021 Johanna Roedenbeck
#    WeeWX functions Copyright (C) 2009-2021 Tom Keffer
#    airQ API Copyright (C) Corant GmbH

from __future__ import absolute_import
from __future__ import print_function
from __future__ import with_statement

import weewx
import weecfg.database
from weeutil.weeutil import y_or_n
import user.airQ_corant
import configobj
import optparse

# modules for airQ access
import base64
from Cryptodome.Cipher import AES
import http.client
from Cryptodome import Random
import json

import six

usage = """airq_conf --help
       airq_conf --device=DEVICE --print-config
       airq_conf --device=DEVICE --add-columns
       airq_conf --device=DEVICE --drop-columns
       airq_conf --device=DEVICE --set-location=station
       airq_conf --device=DEVICE --set-location=LATITUDE,LOGITUDE
       airq_conf --device=DEVICE --set-roomsize=HEIGHT,AREA
       airq_conf [--device=DEVICE] --set-ntp=NTP_SERVER"""
        
epilog = """NOTE: MAKE A BACKUP OF YOUR DATABASE BEFORE USING THIS UTILITY!
Many of its actions are irreversible!"""

headers = {'Content-type': 'application/x-www-form-urlencoded'}

NTP_SERVERS = {
    'default':'pool.ntp.org',
    'ntp':'pool.ntp.org',
    'de':'ptbtime3.ptb.de',
    'ptb':'ptbtime3.ptb.de'}
    
def airQrequest(data, passwd):
    data = json.dumps(data)
    _aeskey = passwd.encode('utf-8')
    _aeskey = _aeskey.ljust(32,b'0')
    # Erster Schritt: AES256 verschlüsseln
    iv = Random.new().read(AES.block_size)
    cipher = AES.new(key=_aeskey, mode=AES.MODE_CBC, IV=iv)
    msg = data.encode('utf-8')
    length = 16-(len(msg)%16)
    crypt = iv + cipher.encrypt(msg+chr(length).encode('utf-8')*length)
    # Zweiter Schritt: base64 enkodieren
    msgb64 = base64.b64encode(crypt).decode('utf-8')
    return msgb64

def airQput(host, page, passwd, data):
    try:
        connection = http.client.HTTPConnection(host)
        connection.request("POST", page, "request="+airQrequest(data,passwd),headers)
        _response = connection.getresponse()
        if _response.status==200:
            reply = user.airQ_corant.airQreply(_response.read(), passwd)
        else:
            reply = {'content':{}}
    except Exception as e:
        print(e)
        reply = None
    finally:
        connection.close()
    return reply



def main():

    # Create a command line parser:
    parser = optparse.OptionParser(usage=usage, epilog=epilog)
    
    parser.add_option("--device", type=str, metavar="DEVICE",
                       help="airQ device as defined in weewx.conf")
                       
    parser.add_option("--config", dest="config_path", type=str,
                      metavar="CONFIG_FILE",
                      help="Use configuration file CONFIG_FILE.")

    parser.add_option("--binding", metavar="BINDING_NAME", default='wx_binding',
                      help="The data binding to use. Default is 'wx_binding'.")
                      
    parser.add_option("--print-config", action="store_true",
                      help="Get the config from the airQ device and print")
                      
    parser.add_option("--add-columns",action="store_true",
                       help="add columns to the WeeWX database")
                       
    parser.add_option("--drop-columns",action="store_true",
                       help="drop columns from the WeeWX database")

    parser.add_option("--set-location", dest="location", type=str, metavar="LOCATION",
                      help="write location into the airQ device")
                     
    parser.add_option("--set-roomsize", dest="roomsize", type=str, metavar="HEIGHT,AREA",
                      help="write room height and room area into the airQ device")
                      
    parser.add_option("--set-ntp", dest="ntp", type=str, metavar="NTP_SERVER",
                      help="write NTP server address to use into the airQ device")
    
    (options, args) = parser.parse_args()
    
    # get config_dict to use
    config_path, config_dict = weecfg.read_config(options.config_path, args)
    print("Using configuration file %s" % config_path)

    action_add = options.add_columns
    if action_add is None: action_add = False
    action_drop = options.drop_columns
    if action_drop is None: action_drop = False
    device = options.device
    db_binding = options.binding

    if options.print_config:
        printConfig(config_path,config_dict,device)
    elif options.location:
        setLocation(config_dict,device,options.location)
    elif options.roomsize:
        setRoomsize(config_dict,device,options.roomsize)
    elif options.ntp:
        setNTP(config_dict,device,options.ntp)
    else:
        addDropColumns(config_dict, db_binding, device, action_add, action_drop)


def printConfig(config_path,config_dict, device):
    """ retrieve config data from device and print to stdout """
    if device:
        conf = config_dict.get('airQ',{}).get(device)
        if conf is None:
            print("device '%s' not found in '%s'" % (device,config_path))
        else:
            host = conf.get('host')
            passwd = conf.get('password')
            print("requesting data...")
            reply = user.airQ_corant.airQget(host,"/config",passwd)
            print("config of device '%s' in '%s', host '%s', prefix '%s'" % (device,config_path,host,conf.get('prefix')))
            _printDict(reply['content'],0)
    else:
        conf = config_dict.get('airQ',{})
        for dev in conf:
            if isinstance(conf[dev],dict) and 'host' in conf[dev] and 'password' in conf[dev]:
                printConfig(config_path,config_dict,dev)
                print()

def _printDict(reply, indent):
    """ print dict with indent """
    for key in reply:
        if isinstance(reply[key],dict):
            print(' '*indent+"%s:" % key)
            _printDict(reply[key],indent+4)
        else:
            print(' '*indent+"%s: %s" % (key,reply[key]))
            
def addDropColumns(config_dict, db_binding, device, action_add, action_drop):
    """ prepare WeeWX database for airQ columens """
    if action_add and action_drop:
        # columns can be added or dropped but not both
        print("one of '--add-columns' or '--drop-columns' is possible only")
    elif not action_add and not action_drop:
        # add or drop?
        print("'--add-columns' or '--drop-columns' is needed")
    else:
        # action is defined, check device
        if device:
            # observeration types
            airq_data = user.airQ_corant.AirqService.AIRQ_DATA
            # weewx
            conf =  config_dict.get('airQ',{}).get(device)
            if conf is None:
                # device 'device' not defined in weewx.conf
                print("device '%s' not found in '%s'" % (device,config_path))
            else:
                prefix = conf.get('prefix',None)
                # what action
                if action_add:
                    print("Adding columns for device '%s', prefix '%s'" % (device,prefix))
                elif action_drop:
                    print("Dropping columns for device '%s', prefix '%s'" % (device,prefix))
                # columns of the original schema
                manager_dict = weewx.manager.get_manager_dict_from_config(
                                                  config_dict,db_binding)
                schema = manager_dict.get('schema',{}).get('table',{})
                # determine columns to add or drop
                cols = []
                ocls = []
                for ii in airq_data:
                    if airq_data[ii] is not None and airq_data[ii][0] is not None and ii not in user.airQ_corant.AirqService.ACCUM_LAST:
                        __col = user.airQ_corant.AirqService.obstype_with_prefix(airq_data[ii][0],prefix)
                        if __col in [col[0] for col in schema]:
                            ocls.append(__col)
                        else:
                            cols.append(__col)
                print()
                if action_add:
                    print("Columns to add:")
                elif action_drop:
                    print("Columns to drop:")
                print(cols)
                if len(ocls)>0:
                    print()
                    print("Omitted columns:")
                    print(ocls)
                    print("Those columns are in the database schema used "
                          "when the WeeWX database was created. So they cannot "
                          "be changed by the airQ configuration tool.")
                print()
                ans = y_or_n("Are you sure you want to proceed (y/n)?")
                if ans=='y':
                    if action_add:
                        addColumns(config_dict,db_binding,cols)
                    elif action_drop:
                        dropColumns(config_dict,db_binding,cols)
                    else:
                        print("invalid action")
                else:
                    print("Aborted. Nothing changed.")
                

        else:
            print("option '--device=DEVICE' is mandatory")


def addColumns(config_dict, db_binding, cols):
    """ add columns for the airQ device to the WeeWX database """
    column_type = 'REAL'
    dbm = weewx.manager.open_manager_with_config(config_dict, db_binding)
    for column_name in cols:
        dbm.add_column(column_name, column_type)
        print("New column %s of type %s added to database." % (column_name, column_type))

def dropColumns(config_dict, db_binding, cols):
    """ drop columns for the airQ device from the WeeWX database """
    drop_set = set(cols)
    dbm = weewx.manager.open_manager_with_config(config_dict, db_binding)
    # Now drop the columns. If one is missing, a NoColumnError will be raised. Be prepared
    # to catch it.
    try:
        print("This may take a while...")
        dbm.drop_columns(drop_set)
    except weedb.NoColumnError as e:
        print(e, file=sys.stderr)
        print("Nothing done.")
    else:
        print("Column(s) '%s' dropped from the database" % ", ".join(cols))


def setLocation(config_dict, device, loc):
    """ set location """
    if loc=="station":
        stn_info = config_dict.get('Station',{})
        lat = float(stn_info.get('latitude'))
        lon = float(stn_info.get('longitude'))
    else:
        _loc = loc.split(',')
        lat = float(_loc[0])
        lon = float(_loc[1])
    data = { 'geopos': { 'lat':lat, 'long':lon }}
    setConfig(config_dict, device, data)

def setRoom(config_dict, device, roomsize):
    """ set room size parameters """
    _size = roomsize.split(',')
    data = { 'RoomHeight':float(_size[0]),'RoomArea':float(_size[1]) }
    setConfig(config_dict, device, data)

def setNTP(config_dict, device, ntp):
    """ set NTP server for the airQ device """
    if ntp.lower() in NTP_SERVERS:
        ntp = NTP_SERVERS[ntp.lower()]
    data = { 'TimeServer': ntp }
    if device:
        setConfig(config_dict, device, data)
    else:
        conf = config_dict.get('airQ',{})
        for dev in conf:
            if isinstance(conf[dev],dict) and 'host' in conf[dev] and 'password' in conf[dev]:
                setConfig(config_dict, dev, data)

def setConfig(config_dict, device, data):
    """ write config data into the airQ device """
    if device:
        conf = config_dict.get('airQ',{}).get(device)
        if conf is None:
            print("device '%s' not found in '%s'" % (device,config_path))
        else:
            print("device '%s' host '%s' set %s" % (device,conf['host'],data))
            ans = y_or_n("Are you sure you want to proceed (y/n)?")
            if ans=='y':
                reply = airQput(conf['host'],"/config",conf['password'],data)
                _printDict(reply,0)
    else:
        print("option --device=DEVICE missing")

     
if __name__ == "__main__":
    main()

Reply via email to