Skip to content

Commit fddf70a

Browse files
committed
Update to 2.2.0
1 parent 2552b3b commit fddf70a

File tree

2 files changed

+107
-34
lines changed

2 files changed

+107
-34
lines changed

CHANGELOG.md

+6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# CHANGELOG.md
22

3+
## 2.2.0
4+
* Add support for fixing powercalc entities
5+
* Fixing core.restore_state and powercalc_group is ignored if files do not exist
6+
* Fixing states and statistiscs: Now searches first valid value and uses it as a starting point
7+
(Previously, skipped if first value was not valid)
8+
39
## 2.1.2
410
* Table statistics: Update sum/state if value is NULL
511
* FixLastValidState: Add checks for types

HA_FixNegativeStatistics.py

+101-34
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,11 @@
1212
import sys
1313
import shutil
1414
import sqlite3
15-
from decimal import Decimal, InvalidOperation
15+
from decimal import Decimal
1616
from datetime import datetime
1717

1818
__author__ = "Sebastian Hollas"
19-
__version__ = "2.1.2"
19+
__version__ = "2.2.0"
2020

2121
####################################################################################
2222
# USER INPUT REQUIRED !
@@ -36,9 +36,11 @@
3636
# Build Filepaths
3737
ENTITIES_FILE = os.path.join(HA_CONFIG_ROOT, "entities.list")
3838
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")
3940

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!)")
4244

4345
# Open MySQL server connection if user provided DB_SERVER information
4446
if all(DB_SERVER.values()):
@@ -80,11 +82,21 @@ def main():
8082
if input().lower() != "yes":
8183
sys.exit("Execution stopped by user!")
8284

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")
88100

89101
if not os.path.isfile(ENTITIES_FILE):
90102
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):
156168
recalculateStates(metadata_id=metadata_id_states)
157169

158170
# 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)
160173

161174
# Store database on disk
162175
db.commit()
@@ -171,15 +184,18 @@ def recalculateStatistics(metadata_id: int, key: str) -> str:
171184
SqlExec(f"SELECT id,{key} FROM statistics WHERE metadata_id=? ORDER BY created_ts", (metadata_id,))
172185
result = cur.fetchall()
173186

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
180188

181189
# 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
183199

184200
# Get previous entry
185201
_, pre_value = result[index]
@@ -242,16 +258,21 @@ def recalculateStates(metadata_id: int):
242258
(metadata_id,))
243259
result = cur.fetchall()
244260

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
252263

253264
# 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+
255276
pre_state_id, pre_state, _, _ = result[index]
256277

257278
if old_state_id is None:
@@ -293,14 +314,21 @@ def recalculateStates(metadata_id: int):
293314
print(" Nothing was modified!")
294315

295316

296-
def fixLastValidState(entity_id: str, lastValidState: str):
317+
def fixLastValidState_Riemann(entity_id: str, lastValidState: str):
318+
297319
print(" Fixing last valid state in core.restore_state")
298-
modificationDone = False
299320

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
301327
with open(RESTORE_STATE_PATH, "r") as file:
302328
restore_state = json.load(file)
303329

330+
modificationDone = False
331+
304332
# Loop over json
305333
for state in restore_state["data"]:
306334

@@ -309,23 +337,23 @@ def fixLastValidState(entity_id: str, lastValidState: str):
309337
continue
310338

311339
# Modify state to new value
312-
if state["state"].get("state", "") != lastValidState:
340+
if (state_val := state["state"].get("state", "")) and state_val != lastValidState:
313341
modificationDone = True
314-
print(f" Updating state/state {state['state']['state']} -> {lastValidState}")
342+
print(f" Updating state/state ({state['state']['state']} -> {lastValidState})")
315343
state["state"]["state"] = lastValidState
316344

317345
if (extra_data := state["extra_data"]) and isinstance(extra_data, dict):
318346

319-
if extra_data.get("last_valid_state", "") != lastValidState:
347+
if (lastValid_val := extra_data.get("last_valid_state", "")) and lastValid_val != lastValidState:
320348
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})")
322350
extra_data["last_valid_state"] = lastValidState
323351

324352
if (nativeValue := extra_data.get("native_value", dict())) and isinstance(nativeValue, dict):
325353

326-
if nativeValue.get("decimal_str", "") != lastValidState:
354+
if (decStr_val := nativeValue.get("decimal_str", "")) and decStr_val != lastValidState:
327355
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})")
329357
nativeValue["decimal_str"] = lastValidState
330358

331359
break
@@ -339,6 +367,45 @@ def fixLastValidState(entity_id: str, lastValidState: str):
339367
print(" Nothing was modified!")
340368

341369

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+
342409
def SqlExec(SqlQuery: str, arguments: tuple):
343410
if not isinstance(db, sqlite3.Connection):
344411
# Replace placeholder for module PyMySQL

0 commit comments

Comments
 (0)