12
12
import sys
13
13
import shutil
14
14
import sqlite3
15
- from decimal import Decimal , InvalidOperation
15
+ from decimal import Decimal
16
16
from datetime import datetime
17
17
18
18
__author__ = "Sebastian Hollas"
19
- __version__ = "2.1.2 "
19
+ __version__ = "2.2.0 "
20
20
21
21
####################################################################################
22
22
# USER INPUT REQUIRED !
36
36
# Build Filepaths
37
37
ENTITIES_FILE = os .path .join (HA_CONFIG_ROOT , "entities.list" )
38
38
RESTORE_STATE_PATH = os .path .join (HA_CONFIG_ROOT , ".storage" , "core.restore_state" )
39
+ POWERCALC_GROUP_PATH = os .path .join (HA_CONFIG_ROOT , ".storage" , "powercalc_group" )
39
40
40
- if not os .path .isfile (RESTORE_STATE_PATH ):
41
- sys .exit (f"File { RESTORE_STATE_PATH } does not exist! (Path to HomeAssistant config valid?)" )
41
+ # Check for valid HomeAssistant root
42
+ if not os .path .isfile (os .path .join (HA_CONFIG_ROOT , "configuration.yaml" )):
43
+ sys .exit (f"{ HA_CONFIG_ROOT } seems not to be the config root of HomeAssistant (configuration.yaml not found!)" )
42
44
43
45
# Open MySQL server connection if user provided DB_SERVER information
44
46
if all (DB_SERVER .values ()):
@@ -80,11 +82,21 @@ def main():
80
82
if input ().lower () != "yes" :
81
83
sys .exit ("Execution stopped by user!" )
82
84
83
- # Check that no backup file exists
84
- if os .path .isfile (f"{ RESTORE_STATE_PATH } .BAK" ):
85
- sys .exit ("core.restore_state backup file already exists!" )
86
- # Create core.restore_state backup
87
- shutil .copyfile (RESTORE_STATE_PATH , f"{ RESTORE_STATE_PATH } .BAK" )
85
+ # Check if file exists
86
+ if os .path .isfile (RESTORE_STATE_PATH ):
87
+ # Check that no backup file exists
88
+ if os .path .isfile (f"{ RESTORE_STATE_PATH } .BAK" ):
89
+ sys .exit ("core.restore_state backup file already exists!" )
90
+ # Create core.restore_state backup
91
+ shutil .copyfile (RESTORE_STATE_PATH , f"{ RESTORE_STATE_PATH } .BAK" )
92
+
93
+ # Check if file exists
94
+ if os .path .isfile (POWERCALC_GROUP_PATH ):
95
+ # Check that no backup file exists
96
+ if os .path .isfile (f"{ POWERCALC_GROUP_PATH } .BAK" ):
97
+ sys .exit ("powercalc_group backup file already exists!" )
98
+ # Create powercalc_group backup
99
+ shutil .copyfile (POWERCALC_GROUP_PATH , f"{ POWERCALC_GROUP_PATH } .BAK" )
88
100
89
101
if not os .path .isfile (ENTITIES_FILE ):
90
102
sys .exit (f"File { ENTITIES_FILE } does not exist! (Run with --list first and remove unwanted entities)" )
@@ -156,7 +168,8 @@ def fixDatabase(ENTITIES: list):
156
168
recalculateStates (metadata_id = metadata_id_states )
157
169
158
170
# Fix last valid state to current state
159
- fixLastValidState (entity_id = entity_id , lastValidState = lastValidState )
171
+ fixLastValidState_Riemann (entity_id = entity_id , lastValidState = lastValidState )
172
+ fixLastValidState_PowerCalc (entity_id = entity_id , lastValidState = lastValidState )
160
173
161
174
# Store database on disk
162
175
db .commit ()
@@ -171,15 +184,18 @@ def recalculateStatistics(metadata_id: int, key: str) -> str:
171
184
SqlExec (f"SELECT id,{ key } FROM statistics WHERE metadata_id=? ORDER BY created_ts" , (metadata_id ,))
172
185
result = cur .fetchall ()
173
186
174
- # Get first value from database; this is our starting point
175
- try :
176
- current_value = Decimal (str (result [0 ][1 ]))
177
- except ValueError :
178
- sys .exit (f" [ERROR]: Cannot fix this entity because first entry in table 'statistics' for { key } is not a number! Sorry!\n "
179
- f"No changes were committed into the database!" )
187
+ current_value = None
180
188
181
189
# Loop over all entries starting with the second entry
182
- for index , (idx , value ) in enumerate (result [1 :]):
190
+ for index , (idx , value ) in enumerate (result ):
191
+
192
+ # Find first valid value
193
+ if current_value is None :
194
+ if value and str (value ).replace ("." , "" , 1 ).isdigit ():
195
+ # Get first valid value from database; this is our starting point
196
+ current_value = Decimal (str (value ))
197
+
198
+ continue
183
199
184
200
# Get previous entry
185
201
_ , pre_value = result [index ]
@@ -242,16 +258,21 @@ def recalculateStates(metadata_id: int):
242
258
(metadata_id ,))
243
259
result = cur .fetchall ()
244
260
245
- # Get first value from database; this is our starting point
246
- try :
247
- current_state = Decimal (str (result [0 ][1 ]))
248
- attributes_id = result [0 ][3 ]
249
- except InvalidOperation :
250
- sys .exit (f" [ERROR]: Cannot fix this entity because first entry in table 'states' is not a number! first entry: { result [0 ][3 ]} \n "
251
- f"No changes were committed into the database!" )
261
+ current_state = None
262
+ attributes_id = None
252
263
253
264
# Loop over all entries starting with the second entry
254
- for index , (state_id , state , old_state_id , attr_id ) in enumerate (result [1 :]):
265
+ for index , (state_id , state , old_state_id , attr_id ) in enumerate (result ):
266
+
267
+ # Find first valid value
268
+ if current_state is None :
269
+ if state and state .replace ("." , "" , 1 ).isdigit ():
270
+ # Get first valid values from database; this is our starting point
271
+ current_state = Decimal (str (state ))
272
+ attributes_id = attr_id
273
+
274
+ continue
275
+
255
276
pre_state_id , pre_state , _ , _ = result [index ]
256
277
257
278
if old_state_id is None :
@@ -293,14 +314,21 @@ def recalculateStates(metadata_id: int):
293
314
print (" Nothing was modified!" )
294
315
295
316
296
- def fixLastValidState (entity_id : str , lastValidState : str ):
317
+ def fixLastValidState_Riemann (entity_id : str , lastValidState : str ):
318
+
297
319
print (" Fixing last valid state in core.restore_state" )
298
- modificationDone = False
299
320
300
- # Read core.restore_state
321
+ # Check if file exists
322
+ if not os .path .isfile (RESTORE_STATE_PATH ):
323
+ print (f" File { RESTORE_STATE_PATH } not found. Skipping!" )
324
+ return
325
+
326
+ # Read core.restore_state file as json object
301
327
with open (RESTORE_STATE_PATH , "r" ) as file :
302
328
restore_state = json .load (file )
303
329
330
+ modificationDone = False
331
+
304
332
# Loop over json
305
333
for state in restore_state ["data" ]:
306
334
@@ -309,23 +337,23 @@ def fixLastValidState(entity_id: str, lastValidState: str):
309
337
continue
310
338
311
339
# Modify state to new value
312
- if state ["state" ].get ("state" , "" ) != lastValidState :
340
+ if ( state_val := state ["state" ].get ("state" , "" )) and state_val != lastValidState :
313
341
modificationDone = True
314
- print (f" Updating state/state { state ['state' ]['state' ]} -> { lastValidState } " )
342
+ print (f" Updating state/state ( { state ['state' ]['state' ]} -> { lastValidState } ) " )
315
343
state ["state" ]["state" ] = lastValidState
316
344
317
345
if (extra_data := state ["extra_data" ]) and isinstance (extra_data , dict ):
318
346
319
- if extra_data .get ("last_valid_state" , "" ) != lastValidState :
347
+ if ( lastValid_val := extra_data .get ("last_valid_state" , "" )) and lastValid_val != lastValidState :
320
348
modificationDone = True
321
- print (f" Updating extra_data/last_valid_state { extra_data ['last_valid_state' ]} -> { lastValidState } " )
349
+ print (f" Updating extra_data/last_valid_state ( { extra_data ['last_valid_state' ]} -> { lastValidState } ) " )
322
350
extra_data ["last_valid_state" ] = lastValidState
323
351
324
352
if (nativeValue := extra_data .get ("native_value" , dict ())) and isinstance (nativeValue , dict ):
325
353
326
- if nativeValue .get ("decimal_str" , "" ) != lastValidState :
354
+ if ( decStr_val := nativeValue .get ("decimal_str" , "" )) and decStr_val != lastValidState :
327
355
modificationDone = True
328
- print (f" Updating extra_data/native_value/decimal_str { nativeValue ['decimal_str' ]} -> { lastValidState } " )
356
+ print (f" Updating extra_data/native_value/decimal_str ( { nativeValue ['decimal_str' ]} -> { lastValidState } ) " )
329
357
nativeValue ["decimal_str" ] = lastValidState
330
358
331
359
break
@@ -339,6 +367,45 @@ def fixLastValidState(entity_id: str, lastValidState: str):
339
367
print (" Nothing was modified!" )
340
368
341
369
370
+ def fixLastValidState_PowerCalc (entity_id : str , lastValidState : str ):
371
+ print (" Fixing last valid state in powercalc_group" )
372
+
373
+ # Check if file exists
374
+ if not os .path .isfile (POWERCALC_GROUP_PATH ):
375
+ print (f" File { POWERCALC_GROUP_PATH } not found. Skipping!" )
376
+ return
377
+
378
+ # Read powercalc_group file as json object
379
+ with open (POWERCALC_GROUP_PATH , "r" ) as file :
380
+ powercalc = json .load (file )
381
+
382
+ modificationDone = False
383
+
384
+ # Loop over json
385
+ for group_name , group in powercalc ["data" ].items ():
386
+
387
+ if not isinstance (group , dict ):
388
+ continue
389
+
390
+ for sensor , content in group .items ():
391
+ # Search for entity_id
392
+ if sensor != entity_id :
393
+ continue
394
+
395
+ if (state_val := content .get ("state" , "" )) and state_val != lastValidState :
396
+ modificationDone = True
397
+ print (f" Updating { group_name } /{ sensor } /state ({ state_val } -> { lastValidState } )" )
398
+ content ["state" ] = lastValidState
399
+
400
+ if modificationDone :
401
+ # Write modified json
402
+ with open (POWERCALC_GROUP_PATH , "w" ) as file :
403
+ json .dump (powercalc , file , indent = 2 , ensure_ascii = False )
404
+
405
+ else :
406
+ print (" Nothing was modified!" )
407
+
408
+
342
409
def SqlExec (SqlQuery : str , arguments : tuple ):
343
410
if not isinstance (db , sqlite3 .Connection ):
344
411
# Replace placeholder for module PyMySQL
0 commit comments