Add API interaction for charger data retrieval and CSV export functionality

- Implemented login and logout endpoints for user authentication.
- Added functionality to fetch charger information and transaction data.
- Introduced methods to parse and save raw API data into CSV format.
- Enhanced logging for debugging and tracking API responses.
- Included environment variable loading for sensitive configuration.
This commit is contained in:
Mark Kors
2026-07-12 17:42:14 +02:00
parent 5f0f30b788
commit 5476b5b15d
5 changed files with 14726 additions and 25 deletions

View File

@@ -48,31 +48,61 @@ def fetch_page(offset):
def get_all_raw():
"""Haalt alle transactiepagina's op via paginering.
Stopt zodra de record-nummers terugvallen (circulaire buffer bereikt).
Een niet-afgesloten transactie kan een "gat" in de recordnummering
achterlaten (een gereserveerde maar nooit weggeschreven slot). Een
pagina die op zo'n gat begint komt leeg terug, ook al staat er verderop
gewoon weer geldige data. Daarom wordt bij een lege pagina niet meteen
gestopt, maar met oplopende stappen verderop geprobeerd of de data
doorloopt, voordat we concluderen dat het echt het einde is.
"""
import sys, itertools
spin = itertools.cycle('|/-\\')
debug = os.getenv("DEBUG") == "1"
MAX_GAP = 5000 # verst dat we een leeg/kapot record proberen te overspringen
all_raw = ""
offset = 0
while True:
#sys.stdout.write(next(spin) + '\b')
#sys.stdout.flush()
raw = fetch_page(offset)
stripped = raw.strip().rstrip('}').strip()
if debug:
print(f"\n[DEBUG] offset={offset} len(raw)={len(raw)} stripped_preview={stripped[:80]!r}")
if not stripped or stripped in ('{"version":2,', '{"version":2'):
if debug:
print(f"[DEBUG] stop: lege/afgesloten pagina bij offset={offset}")
break
offsets = re.findall(r'^(\d+)_\w+:', raw, re.MULTILINE)
if not offsets:
# Mogelijk een gat door een niet-afgesloten transactie: probeer
# verderop met oplopende stappen of de data daar doorloopt.
gap = 1
while gap <= MAX_GAP:
probe_offset = offset + gap
probe_raw = fetch_page(probe_offset)
probe_offsets = re.findall(r'^(\d+)_\w+:', probe_raw, re.MULTILINE)
if debug:
print(f"[DEBUG] gat-probe offset={probe_offset} -> {'data gevonden' if probe_offsets else 'nog leeg'}")
if probe_offsets:
offset, raw, offsets = probe_offset, probe_raw, probe_offsets
break
gap *= 4
else:
if debug:
print(f"[DEBUG] stop: geen data gevonden binnen {MAX_GAP} na offset={offset}")
break
all_raw += raw
offsets = re.findall(r'^(\d+)_\w+:', raw, re.MULTILINE)
if not offsets:
break
next_offset = max(int(x) for x in offsets) + 1
if debug:
print(f"[DEBUG] gevonden offsets {min(int(x) for x in offsets)}..{max(int(x) for x in offsets)}, next_offset={next_offset}")
if next_offset <= offset:
if debug:
print(f"[DEBUG] stop: next_offset({next_offset}) <= offset({offset}) — geen voortgang")
break
offset = next_offset
@@ -85,7 +115,8 @@ def get_all_raw():
def parse_transactions(raw):
transactions = []
current_tx = None
stop_parsing = False
last_offset = None
seen_starts = set()
# Verwijder pagina-headers eerst, zodat }{"version":2,305085_ correct wordt gesplitst
raw = re.sub(r'\{"version":\d+,\s*', '', raw)
@@ -94,9 +125,6 @@ def parse_transactions(raw):
raw = re.sub(r'\}\s*(\d+_)', r'\n\1', raw)
for line in raw.splitlines():
if stop_parsing:
break
line = line.strip().rstrip('}').strip()
if not line:
continue
@@ -105,7 +133,17 @@ def parse_transactions(raw):
if not match:
continue
_, record_type, data = match.groups()
offset_str, record_type, data = match.groups()
offset = int(offset_str)
# Cirkelbuffer: recordnummers lopen monotoon op. Val terug = oude/herhaalde
# data (buffer-wraparound) → stop met parsen. Timestamps zijn hiervoor niet
# betrouwbaar: na een onverwachte reset (bv. door een niet-afgesloten
# transactie) kan de klok van de paal tijdelijk terugvallen, waardoor latere,
# geldige transacties anders ten onrechte als "oude data" werden gezien.
if last_offset is not None and offset < last_offset:
break
last_offset = offset
if record_type == 'txstart2':
m = re.match(
@@ -116,11 +154,16 @@ def parse_transactions(raw):
)
if m:
start_time = datetime.strptime(m.group(3), '%Y-%m-%d %H:%M:%S')
# Cirkelbuffer: als de starttijd eerder is dan de hoogste geziene starttijd,
# zijn we in herhaalde data beland → stop met parsen
latest_start = max((tx['start_time'] for tx in transactions), default=None)
if latest_start and start_time < latest_start:
# Inhoudelijke dedup: de paal kan bij het overspringen van een gat
# (zie get_all_raw) oude data teruggeven onder een nieuw, oplopend
# offset-label. Komt dezelfde sessie-signatuur al voor, dan is de
# cirkelbuffer echt rond → stop met parsen.
start_key = (m.group(1), int(m.group(2)), start_time, float(m.group(4)))
if start_key in seen_starts:
break
seen_starts.add(start_key)
current_tx = {
'rfid': m.group(1),
'socket': int(m.group(2)),
@@ -227,23 +270,33 @@ def save_as_csv(raw, device_id, filename=None):
raw = re.sub(r'\{"version":\d+,\s*', '', raw)
raw = re.sub(r'\}\s*(\d+_)', r'\n\1', raw)
latest_start = None
last_offset = None
seen_starts = set()
for line in raw.splitlines():
line = line.rstrip('}').strip()
if not line:
continue
# Cirkelbuffer: recordnummers lopen monotoon op; val terug = oude/herhaalde data
offset_match = re.match(r'^(\d+)_', line)
if offset_match:
offset = int(offset_match.group(1))
if last_offset is not None and offset < last_offset:
break
last_offset = offset
# Verwijder offset-nummers: "76_mv:" → "mv:", "0_txstart2:" → "txstart2:"
line = re.sub(r'^\d+_', '', line)
# Cirkelbuffer: stop als txstart2 terug in de tijd gaat
# Inhoudelijke dedup: de paal kan bij een gat in de nummering oude data
# teruggeven onder een nieuw, oplopend offset-label (zie get_all_raw).
# Komt dezelfde txstart2-payload al voor, dan is de cirkelbuffer echt
# rond → stop.
if line.startswith('txstart2:'):
m = re.search(r'(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})', line)
if m:
start_time = datetime.strptime(m.group(1), '%Y-%m-%d %H:%M:%S')
if latest_start and start_time < latest_start:
break
latest_start = start_time
payload = line[len('txstart2:'):].strip()
if payload in seen_starts:
break
seen_starts.add(payload)
lines.append(line)