ezynq_feature_config.py 14.3 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
#!/usr/bin/env python
# Copyright (C) 2013, Elphel.inc.
# process configuration of features
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
__author__ = "Andrey Filippov"
__copyright__ = "Copyright 2013, Elphel, Inc."
__license__ = "GPL"
__version__ = "3.0+"
__maintainer__ = "Andrey Filippov"
__email__ = "andrey@elphel.com"
__status__ = "Development"
class EzynqFeatures:
    #Modify for this class
    ERRORS={
            'ERR_NOT_A_VARIANT':   'Specified value is not a valid variant',
            'ERR_NOT_AN_INTEGER':  'Value is not an integer',
            'ERR_NOT_A_FLOAT':     'Value is not a float',
            'ERR_NOT_A_BOOLEAN':   'Value is not a boolean'
            }
32 33
    BOOLEANS=(('0','FALSE','DISABLE','DISABLED','N','OFF'),
              ('1','TRUE', 'ENABLE','ENABLED','Y','ON'))
34 35 36 37 38 39 40 41 42 43 44 45 46 47
#  defines - a list, order determines HTML output order
#  Each element has fields:
#  'NAME' - unique name to access this parameter
#  'CONF_NAME' - how it appears in configuration file, '@' may be replaced by str(channel)
#  'TYPE' - either "I" for integer, F - float, T - text, B- boolean (may be false/true, 0/1 or enable{d}/disable{d} or tuple with valid options for value
#  'MANDATORY' - boolean: this parameter is mandatory (either directly specified or derived from some others)
#  'DERIVED' - TBD later
#  'DEFAULT' - default value (to be suggested on error? Use if not mandatory parameter?
#  'DESCRIPTION' - Parameter description - use in error message?
    def _choice_val(self,t,value):
        if isinstance(t,tuple) :
            for c in t:
                if isinstance(c,int):
                    try:
48
                        if c==int(value,0):
49 50 51 52 53
                            return c
                    except:
                        pass
                elif isinstance(c,float):
                    try:
54
                        if c==float(value):
55 56 57 58 59 60 61 62 63
                            return c
                    except:
                        pass
                elif isinstance(c, bool):
                    if value in self.BOOLEANS[1]:
                        return True
                    elif value in self.BOOLEANS[0]:
                        return False
                elif isinstance(c, str):
64 65 66 67 68 69
                    try:
                        if c.upper()==value.upper():
                            return c
                    except:
                        pass
#                    return value
70 71 72 73 74 75 76 77 78 79 80 81
            else:
                return None
                         
        
 
    def __init__(self,defines,channel=0):
        self.defs={}
        for i,feature in enumerate(defines):
            self.defs[feature['NAME']]=feature
            self.defs[feature['NAME']]['INDEX']=i
        self.channel=channel
        self.pars={}
82
        self.target={} # target values (as specified)
83
        self.config_names={}
84 85
        self.defined=set()
        self.calculated=set()
86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109
        for name in self.defs:
            cn=self.defs[name];
            self.config_names[cn['CONF_NAME'].replace('@',str(channel))]=name
                            
            
        
    def parse_features(self,raw_configs):
        for line in raw_configs:
            conf_name =  line['KEY']
            value =      line['VALUE']
            value=str(value).upper()
            try:
                name=self.config_names[conf_name]
            except:
                continue
            feature= self.defs[name]
            if (value=='HELP'):
                try:
                    print conf_name+': '+feature['DESCRIPTION']
                except:
                    print conf_name+': description is not available'
                continue
            if isinstance(feature['TYPE'], tuple):
                val=self._choice_val(feature['TYPE'],value)
110 111
                if val is None:
                    raise Exception(self.ERRORS['ERR_NOT_A_VARIANT']+': '+line['VALUE'] +' is not a valid variant for parameter '+
112 113 114 115 116 117 118
                                    conf_name+'. Valid are:'+str(feature['TYPE']))
                else:
                    value=val
            elif (feature['TYPE']=='I') or (feature['TYPE']=='H'):
                try:
                    value= int(value,0)
                except:
119 120 121 122
                    if value == 'Y':
                        value=1
                    else:    
                        raise Exception(self.ERRORS['ERR_NOT_AN_INTEGER']+': '+line['VALUE'] +' is not a valid INTEGER value for parameter '+ conf_name)
123
            elif (feature['TYPE']=='F'):
124 125 126 127 128 129 130
                if value == 'Y':
                    value=1.0
                else:    
                    try:
                        value= float(value)
                    except:
                        raise Exception(self.ERRORS['ERR_NOT_A_FLOAT']+': '+line['VALUE'] +' is not a valid FLOAT value for parameter '+ conf_name)
131 132 133 134 135 136 137 138 139 140 141
            elif (feature['TYPE']=='B'):
                if value in self.BOOLEANS[1]:
                    value=True
                elif value in self.BOOLEANS[0]:
                    value=False
                else:
#                    print line['VALUE'],type(line['VALUE'])
#                    print line['VALUE'] in self.BOOLEANS[1]
                    raise Exception(self.ERRORS['ERR_NOT_A_BOOLEAN']+': '+line['VALUE'] +' is not a valid boolean value for parameter '+ conf_name+
                                    '. Valid for "True" are:'+str(self.BOOLEANS[1])+', for "False" - '+str(self.BOOLEANS[0]))
            elif (feature['TYPE']=='T'):
142 143
                if value == 'Y':
                    value='1'
144 145
                pass #keep string value
            self.pars[name]=value
146
            self.defined.add(name)
147
            self.target[name]=value
148 149 150 151 152 153 154 155 156
    #check after calculating derivative parameters                    
    def check_missing_features(self):
        all_set=True
        for name in self.defs:
            if (not name in self.pars):
                if self.defs[name]['MANDATORY']:
                    all_set=False
                    print "Configuration file is missing mandatory parameter "+self.defs[name]['CONF_NAME']+': '+self.defs[name]['DESCRIPTION']
                else:
157
                    if not self.defs[name]['DEFAULT'] is None:
158 159
                    # use default parameter
#                    print 'Adding default : ',name,'=', self.defs[name]['DEFAULT']
160
                        self.pars[name]=self.defs[name]['DEFAULT']
161 162 163 164 165 166 167 168 169 170 171 172 173 174
        return all_set
    def get_par_names(self):
#        name_offs=sorted([(name,self.registers[name]['OFFS']) for name in self.registers], key = lambda l: l[1])
#        print '---self.registers=',self.registers 
#        unsorted_name_offs=[(name,self.defs[name]['OFFS']) for name in self.registers]
#        print '---unsorted_name_offs=',unsorted_name_offs 
#        name_offs=sorted(unsorted_name_offs, key = lambda l: l[1]) 
#        print '---name_offs=',name_offs 
#        return [n for n in name_offs]
# sort register names in the order of addresses
#        name_index=sorted([(name,self.defs[name]['INDEX']) for name in self.pars], key = lambda l: l[1])
#        return [n for n in sorted([(name,self.defs[name]['OFFS']) for name in self.registers], key = lambda l: l[1])]
        return [n[0] for n in sorted([(name,self.defs[name]['INDEX']) for name in self.pars], key = lambda l: l[1])]
#TODO: Use SELECT for options?
175 176 177 178 179
    def get_par_confname(self,name):
        try:
            return self.defs[name]['CONF_NAME']
        except:
            raise Exception (name+' not found in self.defs') # should not happen with wrong data, program bug
180 181 182 183 184
    def get_par_description(self,name):
        try:
            return self.defs[name]['DESCRIPTION']
        except:
            raise Exception (name+' not found in self.defs') # should not happen with wrong data, program bug
185

186 187 188 189 190 191 192 193 194 195
    def get_par_value_or_none(self,name):
        try:
            return self.pars[name]
        except:
            try: 
                _=self.defs[name]['CONF_NAME']
            except:    
                raise Exception (name+' not found in self.defs') # should not happen with wrong data, program bug
            return 

196 197 198 199
    def get_par_value(self,name):
        try:
            return self.pars[name]
        except:
200 201 202 203 204 205 206
#            print 'name=',name
#            print self.pars
            try: 
                config_name=self.defs[name]['CONF_NAME']
            except:    
                raise Exception (name+' not found in self.defs') # should not happen with wrong data, program bug
            raise Exception (config_name+' is not defined, nor calculated')
207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232

    def get_par_value_or_default(self,name):
        try:
            return self.pars[name]
        except:
#            print 'name=',name
#            print self.pars
            try: 
                config_name=self.defs[name]['CONF_NAME']
            except:    
                raise Exception (name+' not found in self.defs') # should not happen with wrong data, program bug
            try:
                return self.defs[name]['DEFAULT']
            except:
                raise Exception (config_name+' is not defined, nor calculated and no default is provided')
        
    def get_par_target(self,name):
        try:
            return self.target[name]
        except:
            try: 
                config_name=self.defs[name]['CONF_NAME']
            except:    
                raise Exception (name+' not found in self.defs') # should not happen with wrong data, program bug
            raise Exception ('Target value for '+config_name+' is not defined')
    
233 234 235 236 237 238 239 240 241 242 243
    def is_known(self,name): # specified or calculated
        return (name in self.defined) or (name in self.calculated)

    def is_mandatory(self,name):
        try:
            return self.defs[name]['MANDATORY']
        except:
            raise Exception (name+' not found in self.defs') # should not happen with wrong data, program bug
    def is_specified(self,name): # directly specified
        return name in self.defined

244 245 246 247
    def undefine_parameter(self,name):
        if name in self.pars:
            self.pars[name]=None

248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268
    
    def set_calculated_value(self,name,value,force=True):
        if (not force) and (name in self.defined):
            config_name=self.defs[name]['CONF_NAME']
            raise Exception (name+' ('+config_name+') is specifically defined in configuration file, value will not change') # should not happen with wrong data, program bug
        else:
            self.pars[name]=value
            self.calculated.add(name)
            if name in self.defined:
                self.defined.remove(name)

    def set_max_value(self,name,value):
#        print 'set_max_value (',name,',',value,')'
        if (not self.is_known(name)) or (value>self.pars[name]):
            self.set_calculated_value(name,value,True)

    def set_min_value(self,name,value):
        if (not self.is_known(name)) or (value<self.pars[name]):
            self.set_calculated_value(name,value,True)
            
                               
269
    def html_list_features(self,html_file):
270 271
        if not html_file:
            return
272
        html_file.write('<table border="1">\n')
273 274
#        html_file.write('<tr><th>Configuration name</th><th>Value<br/>(Target)</th><th>Type/<br/>Choices</th><th>Mandatory</th><th>Origin</th><th>Default</th><th>Description</th></tr>\n')
        html_file.write('<tr><th>Configuration name</th><th>Value (Target)</th><th>Type/<br/>Choices</th><th>Mandatory</th><th>Origin</th><th>Default</th><th>Description</th></tr>\n')
275 276
#        print  self.get_par_names()
#        for name in self.pars:
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
277
        row_class="even"
278 279 280 281
        for name in self.get_par_names():
#            name=    self.config_names[conf_name]
            feature= self.defs[name]
            value=   self.get_par_value(name)
282 283 284 285 286 287 288 289
            if value is None:
                value='None'
            else:    
                if isinstance(value,int):
                    if (feature['TYPE']=='H'):
                        value=hex(value)
                    else:
                        value=str(value)
290 291 292 293 294 295 296 297 298 299 300 301
            try:
                target_value=self.get_par_target(name)
                if isinstance(target_value,int):
                    if (feature['TYPE']=='H'):
                        target_value=hex(target_value)
                    else:
                        target_value=str(target_value)
                if value != target_value:  # Do not show target_value if it is the same as actual
#                    value+='<br/>('+str(target_value)+')'
                    value='<b>'+str(value)+'</b> ('+str(target_value)+')'
            except: # target_value is not set
                pass
302 303 304 305 306 307
            if name in self.defined:
                origin="Defined"
            elif name in self.calculated:
                origin="Calculated"
            else:    
                origin="Default"
308 309 310 311 312 313 314 315 316
            if isinstance (feature['TYPE'],tuple):
                par_type='<select>\n'
                for t in feature['TYPE']:
                    par_type+='  <option>'+str(t)+'</option>\n'
#                    par_type+=('','<br/>')[i>0]+str(t)
                par_type+='</select>\n'    
            else:        
                par_type={'H':'Integer','I':'Integer','F':'Float','B':'Boolean','T':'Text'}[feature['TYPE']]

317 318
#            if name=='BAUD_RATE':
#                print value
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
319 320
            if row_class=="odd": row_class="even" 
            else:                row_class="odd"
321 322 323 324
            if (feature['TYPE']=='H') and isinstance(feature['DEFAULT'],int) and (feature['DEFAULT']>9):
                sDefault=hex(feature['DEFAULT'])
            else:            
                sDefault=str(feature['DEFAULT'])
Oleg Dzhimiev's avatar
Oleg Dzhimiev committed
325
            html_file.write('<tr class="'+row_class+'"><td><b>'+feature['CONF_NAME']+'</b></td><td>'+str(value)+'</td><td>'+par_type+
326
                            '</td><td>'+('-','Y')[feature['MANDATORY']]+'</td><td>'+origin+'</td><td>'+sDefault+'</td><td>'+feature['DESCRIPTION']+'</td></tr>\n')
327 328 329
        html_file.write('</table>\n')