Automatizovaný billing podle RFID karet
Skript v Pythonu i Node.js stáhne charging reporty z MyBox Cloud API, seskupí energii a náklady podle čísla RFID karty a vyexportuje CSV.
Kompletní průvodce automatizací vyúčtování nabíjené energie na konkrétní uživatele podle jejich RFID karet přes MyBox Cloud API.
Jak to funguje
Každá nabíjecí stanice MyBox eviduje nabíjecí relace. Pokud se uživatel autorizuje RFID kartou, API vrací v každém charging reportu:
| Pole | Popis | Příklad |
|---|---|---|
user_id | Unikátní ID RFID karty | "0A896C4E" |
user_tag_title | Jméno přiřazené ke kartě v MyBox Cloud | "Jan Novák" |
energy_delivered | Dodaná energie v kWh | "26.00" |
cost | Náklady za relaci | "205.41" |
currency | Měna | "CZK" |
device_id | ID nabíjecí stanice | "abc1-def2-ghi3-jkl4" |
start_time | Začátek nabíjení (UTC) | "2025-09-15T05:33:53.000Z" |
end_time | Konec nabíjení (UTC) | "2025-09-15T14:39:22.000Z" |
tariffs | Tarify přiřazené ke stanici | Pole tarifů s cenami |
Postup automatizace:
- Získat seznam zařízení --
GET /external/device - Stáhnout charging reporty pro každé zařízení za období --
GET /external/charging-reports/device/{id} - Seskupit podle
user_id(RFID karty) a sumarizovat energii a náklady - Exportovat do CSV, Excel nebo odeslat do fakturačního systému
Potřebné endpointy
1. Seznam zařízení
GET /admin-panel/v1/external/deviceVrací všechna vaše zařízení. Z odpovědi potřebujete pole identifier pro další volání.
2. Charging reporty pro zařízení
GET /admin-panel/v1/external/charging-reports/device/{deviceId}| Parametr | Typ | Popis |
|---|---|---|
deviceId | path | Identifikátor zařízení |
startDate | query | Začátek období (ISO 8601) |
endDate | query | Konec období (ISO 8601) |
limit | query | Počet záznamů (default: 100, max: 1000) |
offset | query | Posun pro stránkování |
Odpověď obsahuje meta.totalCount pro stránkování.
3. Reporty podle vlastníka (alternativa)
GET /admin-panel/v1/external/charging-reports/owner/{email}Umožňuje filtrovat reporty přímo podle emailu vlastníka.
Struktura charging reportu
Každý report z API vypadá takto (zkráceno na relevantní pole pro billing):
{
"id": 1234567890123456,
"type": "network",
"session_id": "047",
"user_id": "0A896C4E",
"user_tag_title": "Jan Novák",
"device_id": "abc1-def2-ghi3-jkl4",
"device_title": "Nabíječka - Budova A",
"status": "Finished",
"energy_delivered": "26.00",
"duration": "9h 5m 29s",
"charging_time": 32729,
"start_meter_readings": "601.00",
"actual_meter_readings": "627.00",
"cost": "205.41",
"currency": "CZK",
"price": "0.00",
"connector": 2,
"start_time": "2025-09-15T05:33:53.000Z",
"end_time": "2025-09-15T14:39:22.000Z",
"site": {
"id": 9876543210987654,
"title": "Centrála Praha",
"address": "Hlavní 123, Praha 110 00"
},
"companies": [
{
"id": 5555555555555555,
"title": "EnergyTech s.r.o."
}
],
"tariffs": [
{
"id": 2222222222222222,
"title": "Management",
"cost": [
{ "start": "00", "end": "14", "price": 7.7 },
{ "start": "14", "end": "24", "price": 8.8 }
],
"currency": "CZK",
"timezone": "Europe/Prague",
"tag_groups": null,
"user_tags": null
}
],
"owner": {
"id": 3333333333333333,
"email": "admin@example.com",
"first_name": "Firma",
"last_name": "Company Account"
}
}Klíčová pole pro billing
user_id-- identifikátor RFID karty (hex string)user_tag_title-- jméno uživatele přiřazené ke kartě v MyBox Cloudenergy_delivered-- dodaná energie v kWh (string, nutno převést na float)cost-- celkové náklady za relaci (string, nutno převést na float)charging_time-- doba nabíjení v sekundách (number)tariffs-- pole tarifů s cenami za kWh podle časových pásem
Kompletní implementace
Python - měsíční billing
#!/usr/bin/env python3
"""
MyBox RFID Billing - automatizovaný billing energie podle RFID karet.
Použití:
python rfid_billing.py # Billing za poslední měsíc
python rfid_billing.py --months 3 # Billing za poslední 3 měsíce
python rfid_billing.py --from 2025-09-01 --to 2025-09-30
"""
import requests
from requests.auth import HTTPBasicAuth
from datetime import datetime, timedelta, timezone
from collections import defaultdict
import csv
import argparse
import json
# ─── Konfigurace ──────────────────────────────────────────────
API_KEY = "váš_api_key"
API_SECRET = "váš_api_secret"
BASE_URL = "https://cloud.mybox.pro/admin-panel/v1"
AUTH = HTTPBasicAuth(API_KEY, API_SECRET)
HEADERS = {"Accept": "application/json"}
# ─── API funkce ───────────────────────────────────────────────
def get_all_devices():
"""Získá seznam všech nabíjecích stanic."""
response = requests.get(
f"{BASE_URL}/external/device",
auth=AUTH,
headers=HEADERS
)
response.raise_for_status()
return response.json()['data']
def get_charging_reports(device_id, start_date, end_date):
"""
Získá všechny charging reporty pro zařízení.
Automaticky stránkuje pomocí meta.totalCount.
"""
all_reports = []
offset = 0
limit = 100
while True:
response = requests.get(
f"{BASE_URL}/external/charging-reports/device/{device_id}",
auth=AUTH,
params={
'startDate': start_date,
'endDate': end_date,
'limit': limit,
'offset': offset
},
headers=HEADERS
)
response.raise_for_status()
data = response.json()
reports = data['data']
total = data['meta']['totalCount']
all_reports.extend(reports)
if len(all_reports) >= total:
break
offset += limit
return all_reports
# ─── Billing logika ──────────────────────────────────────────
def generate_billing(start_date, end_date):
"""
Vygeneruje billing seskupený podle RFID karet.
Projde všechna zařízení a stáhne reporty.
"""
billing = defaultdict(lambda: {
'name': '',
'sessions': 0,
'energy_kwh': 0.0,
'cost': 0.0,
'currency': '',
'charging_seconds': 0,
'devices': set(),
'first_charge': None,
'last_charge': None,
'sessions_detail': []
})
devices = get_all_devices()
print(f"Nalezeno {len(devices)} zařízení")
print(f"Období: {start_date} -- {end_date}\n")
for i, device in enumerate(devices):
device_id = device['identifier']
device_title = device.get('user_title') or device.get('title', device_id)
reports = get_charging_reports(device_id, start_date, end_date)
if reports:
print(f" [{i+1}/{len(devices)}] {device_title}: {len(reports)} relací")
for r in reports:
if r['status'] != 'Finished':
continue
rfid = r.get('user_id', 'unknown')
energy = float(r.get('energy_delivered', 0) or 0)
cost = float(r.get('cost', 0) or 0)
charging_time = int(r.get('charging_time', 0) or 0)
start_time = r.get('start_time', '')
billing[rfid]['name'] = r.get('user_tag_title', 'Neznámý')
billing[rfid]['sessions'] += 1
billing[rfid]['energy_kwh'] += energy
billing[rfid]['cost'] += cost
billing[rfid]['currency'] = r.get('currency', 'CZK')
billing[rfid]['charging_seconds'] += charging_time
billing[rfid]['devices'].add(device_id)
# Sledování časového rozsahu
if start_time:
if billing[rfid]['first_charge'] is None or start_time < billing[rfid]['first_charge']:
billing[rfid]['first_charge'] = start_time
if billing[rfid]['last_charge'] is None or start_time > billing[rfid]['last_charge']:
billing[rfid]['last_charge'] = start_time
# Detail relace
billing[rfid]['sessions_detail'].append({
'date': start_time[:10] if start_time else '',
'device': device_title,
'energy_kwh': energy,
'cost': cost,
'duration_min': round(charging_time / 60, 1)
})
return billing
# ─── Výstup ──────────────────────────────────────────────────
def format_duration(seconds):
"""Převede sekundy na lidsky čitelný formát."""
hours = seconds // 3600
minutes = (seconds % 3600) // 60
if hours > 0:
return f"{hours}h {minutes}m"
return f"{minutes}m"
def print_billing_summary(billing):
"""Vypíše souhrnný billing do konzole."""
print(f"\n{'='*95}")
print(f"{'RFID karta':<16} {'Jméno':<22} {'Relací':>7} "
f"{'Energie (kWh)':>14} {'Náklady':>11} {'Doba':>10} {'Stanic':>7}")
print(f"{'='*95}")
total_energy = 0.0
total_cost = 0.0
total_sessions = 0
currency = 'CZK'
for rfid, info in sorted(billing.items(), key=lambda x: x[1]['energy_kwh'], reverse=True):
currency = info['currency']
duration = format_duration(info['charging_seconds'])
print(f"{rfid:<16} {info['name']:<22} {info['sessions']:>7} "
f"{info['energy_kwh']:>14.2f} {info['cost']:>8.2f} {currency} "
f"{duration:>10} {len(info['devices']):>7}")
total_energy += info['energy_kwh']
total_cost += info['cost']
total_sessions += info['sessions']
print(f"{'='*95}")
print(f"{'CELKEM':<16} {'':<22} {total_sessions:>7} "
f"{total_energy:>14.2f} {total_cost:>8.2f} {currency}")
print()
def print_user_detail(billing, rfid):
"""Vypíše detail relací pro konkrétní RFID kartu."""
if rfid not in billing:
print(f"RFID karta {rfid} nenalezena.")
return
info = billing[rfid]
print(f"\nDetail: {info['name']} (RFID: {rfid})")
print(f"Období: {info['first_charge'][:10]} -- {info['last_charge'][:10]}")
print(f"Celkem: {info['energy_kwh']:.2f} kWh | {info['cost']:.2f} {info['currency']} | "
f"{info['sessions']} relací | {format_duration(info['charging_seconds'])}")
print()
print(f" {'Datum':<12} {'Stanice':<25} {'Energie':>10} {'Náklady':>10} {'Doba':>8}")
print(f" {'-'*70}")
for s in sorted(info['sessions_detail'], key=lambda x: x['date']):
print(f" {s['date']:<12} {s['device']:<25} {s['energy_kwh']:>8.2f} kWh"
f" {s['cost']:>8.2f} {s['duration_min']:>6.0f} min")
# ─── Export ──────────────────────────────────────────────────
def export_summary_csv(billing, filename='billing_summary.csv'):
"""Exportuje souhrnný billing do CSV."""
with open(filename, 'w', newline='', encoding='utf-8') as f:
writer = csv.writer(f, delimiter=';')
writer.writerow([
'RFID karta', 'Jméno', 'Počet relací',
'Dodaná energie (kWh)', 'Náklady', 'Měna',
'Doba nabíjení', 'Počet stanic',
'První nabíjení', 'Poslední nabíjení'
])
for rfid, info in sorted(billing.items(), key=lambda x: x[1]['energy_kwh'], reverse=True):
writer.writerow([
rfid,
info['name'],
info['sessions'],
f"{info['energy_kwh']:.2f}",
f"{info['cost']:.2f}",
info['currency'],
format_duration(info['charging_seconds']),
len(info['devices']),
info['first_charge'][:10] if info['first_charge'] else '',
info['last_charge'][:10] if info['last_charge'] else ''
])
print(f"Souhrnný CSV: {filename}")
def export_detail_csv(billing, filename='billing_detail.csv'):
"""Exportuje detailní billing (každá relace) do CSV."""
with open(filename, 'w', newline='', encoding='utf-8') as f:
writer = csv.writer(f, delimiter=';')
writer.writerow([
'RFID karta', 'Jméno', 'Datum', 'Stanice',
'Energie (kWh)', 'Náklady', 'Doba (min)'
])
for rfid, info in sorted(billing.items(), key=lambda x: x[1]['name']):
for s in sorted(info['sessions_detail'], key=lambda x: x['date']):
writer.writerow([
rfid,
info['name'],
s['date'],
s['device'],
f"{s['energy_kwh']:.2f}",
f"{s['cost']:.2f}",
f"{s['duration_min']:.0f}"
])
print(f"Detailní CSV: {filename}")
def export_json(billing, filename='billing.json'):
"""Exportuje billing do JSON."""
export = {}
for rfid, info in billing.items():
export[rfid] = {
'name': info['name'],
'sessions': info['sessions'],
'energy_kwh': round(info['energy_kwh'], 2),
'cost': round(info['cost'], 2),
'currency': info['currency'],
'charging_seconds': info['charging_seconds'],
'devices_count': len(info['devices']),
'first_charge': info['first_charge'],
'last_charge': info['last_charge'],
'sessions_detail': info['sessions_detail']
}
with open(filename, 'w', encoding='utf-8') as f:
json.dump(export, f, indent=2, ensure_ascii=False)
print(f"JSON export: {filename}")
# ─── CLI ─────────────────────────────────────────────────────
def main():
parser = argparse.ArgumentParser(description='MyBox RFID Billing')
parser.add_argument('--months', type=int, default=1, help='Počet měsíců zpět (default: 1)')
parser.add_argument('--from', dest='date_from', help='Počáteční datum (YYYY-MM-DD)')
parser.add_argument('--to', dest='date_to', help='Koncové datum (YYYY-MM-DD)')
parser.add_argument('--detail', help='Zobrazit detail pro RFID kartu')
parser.add_argument('--export', choices=['csv', 'json', 'all'], default='csv',
help='Formát exportu (default: csv)')
args = parser.parse_args()
# Časový rozsah
if args.date_from and args.date_to:
start_date = f"{args.date_from}T00:00:00Z"
end_date = f"{args.date_to}T23:59:59Z"
else:
end = datetime.now(timezone.utc)
start = end - timedelta(days=args.months * 30)
start_date = start.strftime('%Y-%m-%dT00:00:00Z')
end_date = end.strftime('%Y-%m-%dT23:59:59Z')
# Generování billingu
billing = generate_billing(start_date, end_date)
print_billing_summary(billing)
# Detail pro konkrétní kartu
if args.detail:
print_user_detail(billing, args.detail)
# Export
if args.export in ('csv', 'all'):
export_summary_csv(billing)
export_detail_csv(billing)
if args.export in ('json', 'all'):
export_json(billing)
if __name__ == '__main__':
main()# .env soubor - uložte vedle skriptu
MYBOX_API_KEY=váš_api_key
MYBOX_API_SECRET=váš_api_secretPokud chcete načítat credentials z .env:
import os
from dotenv import load_dotenv
load_dotenv()
API_KEY = os.getenv('MYBOX_API_KEY')
API_SECRET = os.getenv('MYBOX_API_SECRET')Spuštění
# Billing za poslední měsíc
python rfid_billing.py
# Billing za poslední 3 měsíce
python rfid_billing.py --months 3
# Billing za konkrétní období
python rfid_billing.py --from 2025-09-01 --to 2025-09-30
# Detail konkrétní RFID karty
python rfid_billing.py --detail 0A896C4E
# Export do všech formátů
python rfid_billing.py --export allPříklad výstupu
Nalezeno 5 zařízení
Období: 2025-09-01T00:00:00Z -- 2025-09-30T23:59:59Z
[1/5] Nabíječka - Budova A: 28 relací
[2/5] Nabíječka - Parking: 16 relací
[5/5] Nabíječka - Recepce: 3 relací
===============================================================================================
RFID karta Jméno Relací Energie (kWh) Náklady Doba Stanic
===============================================================================================
F41682BF Michal Fojtík 22 170.41 1320.23 CZK 38h 12m 2
0A896C4E Jan Novák 7 60.41 342.38 CZK 14h 30m 1
A3B5C7D9 Petr Svoboda 5 58.63 183.98 CZK 11h 5m 1
E2F4A6B8 Marie Dvořáková 10 55.55 840.67 CZK 12h 48m 3
C1D3E5F7 Tomáš Černý 2 17.46 52.38 CZK 4h 20m 1
B8A6C4D2 Tereza Malá 1 3.12 9.36 CZK 45m 1
===============================================================================================
CELKEM 47 365.58 2749.00 CZK
Souhrnný CSV: billing_summary.csv
Detailní CSV: billing_detail.csvDetail pro konkrétní kartu:
Detail: Jan Novák (RFID: 0A896C4E)
Období: 2025-09-03 -- 2025-09-27
Celkem: 60.41 kWh | 342.38 CZK | 7 relací | 14h 30m
Datum Stanice Energie Náklady Doba
----------------------------------------------------------------------
2025-09-03 Nabíječka - Budova A 12.50 68.75 180 min
2025-09-07 Nabíječka - Budova A 8.30 49.80 120 min
2025-09-11 Nabíječka - Budova A 11.20 61.60 165 min
2025-09-14 Nabíječka - Budova A 6.80 37.40 95 min
2025-09-18 Nabíječka - Budova A 9.41 56.46 140 min
2025-09-22 Nabíječka - Budova A 7.00 38.50 105 min
2025-09-27 Nabíječka - Budova A 5.20 29.87 65 minNode.js implementace
const axios = require('axios');
const fs = require('fs');
const API_KEY = 'váš_api_key';
const API_SECRET = 'váš_api_secret';
const BASE_URL = 'https://cloud.mybox.pro/admin-panel/v1';
const api = axios.create({
baseURL: BASE_URL,
auth: { username: API_KEY, password: API_SECRET },
headers: { 'Accept': 'application/json' }
});
async function getAllDevices() {
const { data } = await api.get('/external/device');
return data.data;
}
async function getChargingReports(deviceId, startDate, endDate) {
const allReports = [];
let offset = 0;
const limit = 100;
while (true) {
const { data } = await api.get(
`/external/charging-reports/device/${deviceId}`,
{ params: { startDate, endDate, limit, offset } }
);
allReports.push(...data.data);
if (allReports.length >= data.meta.totalCount) break;
offset += limit;
}
return allReports;
}
async function generateBilling(monthsBack = 1) {
const endDate = new Date().toISOString();
const startDate = new Date(
Date.now() - monthsBack * 30 * 24 * 60 * 60 * 1000
).toISOString();
const billing = {};
const devices = await getAllDevices();
console.log(`Nalezeno ${devices.length} zařízení`);
for (const device of devices) {
const reports = await getChargingReports(device.identifier, startDate, endDate);
for (const r of reports) {
if (r.status !== 'Finished') continue;
const rfid = r.user_id || 'unknown';
if (!billing[rfid]) {
billing[rfid] = {
name: '', sessions: 0, energyKwh: 0, cost: 0,
currency: '', chargingSeconds: 0, devices: new Set(),
sessionsDetail: []
};
}
const b = billing[rfid];
b.name = r.user_tag_title || 'Neznámý';
b.sessions++;
b.energyKwh += parseFloat(r.energy_delivered || 0);
b.cost += parseFloat(r.cost || 0);
b.currency = r.currency || 'CZK';
b.chargingSeconds += parseInt(r.charging_time || 0);
b.devices.add(device.identifier);
b.sessionsDetail.push({
date: (r.start_time || '').slice(0, 10),
device: device.user_title || device.title,
energyKwh: parseFloat(r.energy_delivered || 0),
cost: parseFloat(r.cost || 0),
durationMin: Math.round((parseInt(r.charging_time || 0)) / 60)
});
}
}
return billing;
}
function exportCsv(billing, filename = 'billing_summary.csv') {
const header = 'RFID karta;Jméno;Počet relací;Energie (kWh);Náklady;Měna;Počet stanic\n';
const rows = Object.entries(billing)
.sort(([, a], [, b]) => b.energyKwh - a.energyKwh)
.map(([rfid, info]) =>
`${rfid};${info.name};${info.sessions};` +
`${info.energyKwh.toFixed(2)};${info.cost.toFixed(2)};` +
`${info.currency};${info.devices.size}`
)
.join('\n');
fs.writeFileSync(filename, header + rows, 'utf-8');
console.log(`CSV export: ${filename}`);
}
// Spuštění
generateBilling(1).then(billing => {
// Souhrnný výpis
const sorted = Object.entries(billing)
.sort(([, a], [, b]) => b.energyKwh - a.energyKwh);
console.log(`\nRFID karta Jméno Relací Energie Náklady`);
console.log('-'.repeat(75));
for (const [rfid, info] of sorted) {
console.log(
`${rfid.padEnd(16)} ${info.name.padEnd(22)} ` +
`${String(info.sessions).padStart(6)} ` +
`${info.energyKwh.toFixed(2).padStart(10)} kWh ` +
`${info.cost.toFixed(2).padStart(10)} ${info.currency}`
);
}
exportCsv(billing);
});Tarify a tag groups
API vrací v každém charging reportu pole tariffs s kompletní tarifní strukturou. Tarify mohou být přiřazeny:
- K zařízením -- pole
tariffs[].devices - K skupinám zařízení -- pole
tariffs[].device_groups - K RFID kartám -- pole
tariffs[].user_tags - K skupinám RFID karet -- pole
tariffs[].tag_groups
Příklad tarifu
{
"id": 2222222222222222,
"title": "Management",
"cost": [
{ "start": "00", "end": "14", "price": 7.7 },
{ "start": "14", "end": "24", "price": 8.8 }
],
"currency": "CZK",
"timezone": "Europe/Prague",
"tag_groups": null,
"user_tags": null,
"devices": [
{ "identifier": "abc1-def2-ghi3-jkl4" }
],
"device_groups": []
}Pole cost obsahuje cenová pásma -- hodiny dne (start/end) a cenu za kWh v daném pásmu.
Konfigurace tag groups
Přiřazení tarifů ke skupinám RFID karet (tag_groups) se provádí přes webové rozhraní MyBox Cloud. Jakmile jsou tag groups nakonfigurovány, budou se objevovat v poli tariffs[].tag_groups v API odpovědích.
Automatizace pomocí CRON
Pro pravidelný měsíční billing nastavte CRON job:
# Každý 1. den v měsíci v 6:00 ráno - billing za předchozí měsíc
0 6 1 * * cd /path/to/billing && python rfid_billing.py --months 1 --export all >> /var/log/mybox-billing.log 2>&1Odeslání reportu emailem
import smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.base import MIMEBase
from email.mime.text import MIMEText
from email import encoders
def send_billing_email(recipients, summary_file, detail_file):
"""Odešle billing report emailem."""
msg = MIMEMultipart()
msg['From'] = 'billing@example.com'
msg['To'] = ', '.join(recipients)
msg['Subject'] = f'MyBox Billing Report - {datetime.now().strftime("%B %Y")}'
body = "V příloze naleznete měsíční vyúčtování nabíjení podle RFID karet."
msg.attach(MIMEText(body, 'plain', 'utf-8'))
for filepath in [summary_file, detail_file]:
with open(filepath, 'rb') as f:
part = MIMEBase('application', 'octet-stream')
part.set_payload(f.read())
encoders.encode_base64(part)
part.add_header('Content-Disposition', f'attachment; filename={filepath}')
msg.attach(part)
with smtplib.SMTP('smtp.example.com', 587) as server:
server.starttls()
server.login('billing@example.com', 'password')
server.send_message(msg)
print(f"Report odeslán na: {', '.join(recipients)}")Řešení problémů
RFID karta se nezobrazuje v reportech
- Ověřte, že karta je registrovaná v MyBox Cloud
- Zkontrolujte, že stanice má povolenu RFID autorizaci (konfigurace
authentication_methodsmusí obsahovat"rfid") - Relace bez RFID autorizace mají
user_idprázdné nebo s generickým ID
Náklady jsou nulové (cost: "0.00")
- Zkontrolujte, zda jsou k zařízení přiřazeny tarify
- Ověřte platnost tarifů (
valid_from/valid_to) - Pokud stanice nemá tarif,
costbude vždy0.00-- energii (energy_delivered) ale dostanete vždy
Stránkování -- nedostávám všechna data
- API vrací max 1000 záznamů na request
- Používejte
meta.totalCountaoffsetpro iteraci - Příklad správného stránkování je ve skriptu výše
Rate limiting
- API limit: 100 požadavků za minutu
- Pro velký počet zařízení přidejte delay mezi requesty:
import time
for device in devices:
reports = get_charging_reports(device['identifier'], start, end)
time.sleep(0.5) # 500ms pauza mezi zařízenímiPotřebujete pomoc?
- Email: filip@mybox.eco
- Dokumentace: Charging Reports API
- API Explorer pro rychlé testování
Produkt
- Nabíjení pro firemní flotily — účtování a správa vozového parku.