DMeter360 REST API Wrapper for Python, provided by Hidroconta S.A.U.
Project description
HIDROCONTA - DMeter360 REST API Python Wrapper
A Python library that makes it easy to interact with Hidroconta's DMeter360 REST API. It wraps all API calls into a clean, object-oriented interface and integrates with pandas for efficient large-scale data handling.
- PyPI: https://pypi.org/project/hidroconta/
- Current version: 3.1.0 (22/06/2026)
- Contact: javier.lopez@hidroconta.com
Note: Version 3.0.0 is not backwards-compatible with v1.x or v2.x.
Table of Contents
- Installation
- Quick Start
- Authentication
- Available Servers
- Response Format: JSON vs Pandas
- Reading Data
- Writing Data
- Managing Elements
- User & Permission Management
- API Key Management
- Types Reference
- Error Handling
- Utilities
- Full Working Example
- Version History
Installation
Make sure you have Python 3.9 or higher installed.
pip install hidroconta
Dependencies (installed automatically):
pandas >= 1.3.5— for DataFrame supportrequests >= 2.26.0— for HTTP calls
Quick Start
This is the minimal code needed to connect and retrieve data:
import hidroconta.api as demeter
import hidroconta.endpoints as endpoints
# Connect to the main server using an API key
with demeter.DemeterClient(endpoints.Server.MAIN) as client:
client.login(api_key='YOUR_API_KEY')
# Search for elements matching 'SAT'
result = client.search(text='SAT', pandas=True)
print(result)
The with statement ensures the session is properly closed (logout + cleanup) when the block ends, even if an error occurs.
Authentication
API Key Login (Recommended)
The simplest and most secure way to authenticate. API keys can be generated from within the library (see API Key Management).
with demeter.DemeterClient(endpoints.Server.MAIN) as client:
client.login(api_key='YOUR_API_KEY_HERE')
Username & Password Login
with demeter.DemeterClient(endpoints.Server.MAIN) as client:
client.login(username='your_username', password='your_password')
Multi-Factor Authentication (MFA)
If MFA is enabled on your account, logging in will raise a MfaRequiredException. You must then validate the MFA code to complete login.
Email OTP:
import hidroconta.api as demeter
import hidroconta.endpoints as endpoints
with demeter.DemeterClient(endpoints.Server.MAIN) as client:
try:
client.login(username='your_username', password='your_password')
except demeter.MfaRequiredException as mfa:
# mfa.mfa_token contains the token needed for validation
client.validate_mfa_email(
code='123456', # The 6-digit code sent to your email
mfa_token=mfa.mfa_token,
device_nickname='My PC' # A friendly name for this device
)
TOTP (Authenticator App):
except demeter.MfaRequiredException as mfa:
client.validate_mfa_totp(
code='123456', # Code from your authenticator app
mfa_token=mfa.mfa_token,
device_nickname='My PC'
)
Available Servers
Choose the server that corresponds to your DMeter360 deployment:
| Server Name | URL |
|---|---|
Server.MAIN |
https://demeter2.hidroconta.com/Demeter2/v2/ |
Server.GESTAGUA |
https://demeter-gestagua.hidroconta.com/Demeter2/v2/ |
Server.TUDELA |
https://demeter-tudela-de-duero.hidroconta.com/Demeter2/v2/ |
Server.AIC |
https://demeter-aic.hidroconta.com/Demeter2/v2/ |
Server.UTEBO |
https://demeter-utebo.hidroconta.com/Demeter2/v2/ |
Server.TEST |
https://demeter2-test-ovh.hidroconta.com/Demeter2/v2/ |
import hidroconta.endpoints as endpoints
# Example: connect to GESTAGUA server
with demeter.DemeterClient(endpoints.Server.GESTAGUA) as client:
client.login(api_key='YOUR_API_KEY')
Response Format: JSON vs Pandas
All data-retrieval methods support two return formats:
| Parameter | Return type | When to use |
|---|---|---|
pandas=False (default) |
Raw JSON string | Direct API responses, custom parsing |
pandas=True |
pandas.DataFrame |
Data analysis, large datasets, CSV export |
# Returns raw JSON string
json_data = client.get_rtus()
# Returns a pandas DataFrame
df = client.get_rtus(pandas=True)
print(df.head())
Reading Data
Search
Search across multiple element types by text. This is the fastest way to find an element when you know part of its name or code.
import hidroconta.types as tp
df = client.search(
text='SAT-001', # Text to search (name, code, etc.)
element_types=[tp.Element.COUNTER, # Which element types to include
tp.Element.RTU,
tp.Element.ANALOG_INPUT],
status=tp.Status.ENABLED, # Filter by status (ENABLED, DISABLED, ALL)
pandas=True
)
print(df)
Parameters:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
text |
str |
Yes | — | Search text |
element_types |
list[Element] |
No | All types | Element types to search |
status |
Status |
No | Status.ALL |
Filter by enabled/disabled |
pandas |
bool |
No | False |
Return DataFrame instead of JSON |
Get Elements
Retrieve all elements of a given type, or a single element by its ID.
Each device type has its own method. All follow the same pattern: call with no arguments to get all, or pass element_id to get one.
# Get all RTUs
all_rtus = client.get_rtus(pandas=True)
# Get a specific RTU by its ID
one_rtu = client.get_rtus(element_id=17512, pandas=True)
# Get all counters
counters = client.get_counters(pandas=True)
# Get all analog inputs
analog_inputs = client.get_analog_inputs(pandas=True)
# Get all digital inputs
digital_inputs = client.get_digital_inputs(pandas=True)
# IRIS devices (IoT connectivity)
iris_nb = client.get_iris_nb(pandas=True) # NB-IoT
iris_lw = client.get_iris_lw(pandas=True) # LoRaWAN
iris_sigfox = client.get_iris_sigfox(pandas=True) # Sigfox
iris_gprs = client.get_iris_gprs(pandas=True) # GPRS
# Other device types
installations = client.get_installations(pandas=True)
centinel = client.get_centinel(pandas=True)
centaurus = client.get_centaurus(pandas=True)
nautilus = client.get_nautilus(pandas=True) # Nautilus Plus
Available get methods summary:
| Method | Device Type |
|---|---|
get_rtus() |
RTU data loggers |
get_counters() |
Water meter counters |
get_analog_inputs() |
Analog input sensors |
get_digital_inputs() |
Digital input sensors |
get_iris_nb() |
IRIS NB-IoT devices |
get_iris_lw() |
IRIS LoRaWAN devices |
get_iris_sigfox() |
IRIS Sigfox devices |
get_iris_gprs() |
IRIS GPRS devices |
get_installations() |
Installations |
get_centinel() |
Centinel devices |
get_centaurus() |
Centaurus devices |
get_nautilus() |
Nautilus Plus meters |
Get Historical Data
Retrieve time-series historical readings for one or more elements. You must specify the type of data using subtype and subcode from hidroconta.types.
import datetime
import hidroconta.types as tp
# Get the last 7 days of counter readings
end = datetime.datetime.now()
start = end - datetime.timedelta(days=7)
df = client.get_historics(
start_date=start,
end_date=end,
element_ids=[1001, 1002, 1003], # List of element IDs
subtype=tp.CounterGlobalHist.subtype, # Data type category
subcode=[tp.CounterGlobalHist.subcode], # Data subtype (list)
pandas=True
)
print(df)
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
start_date |
datetime |
Yes | Start of the time range |
end_date |
datetime |
Yes | End of the time range |
element_ids |
list[int] |
Yes | List of element IDs to query |
subtype |
int |
Yes | Historical data type (HistType.subtype) |
subcode |
list[int] |
Yes | Historical data subcode ([HistType.subcode]) |
pandas |
bool |
No | Return DataFrame instead of JSON |
Common historical data types:
# Counter total volume
subtype=tp.CounterGlobalHist.subtype, subcode=[tp.CounterGlobalHist.subcode]
# Flow rate
subtype=tp.FlowHist.subtype, subcode=[tp.FlowHist.subcode]
# Analog input (pressure, temperature, etc.)
subtype=tp.AnalogInputHist.subtype, subcode=[tp.AnalogInputHist.subcode]
# IRIS counter total volume
subtype=tp.IrisCounterGlobalHist.subtype, subcode=[tp.IrisCounterGlobalHist.subcode]
# Nautilus Plus - positive volume
subtype=tp.NautilusPlusPositiveCounterGlobalHist.subtype, subcode=[tp.NautilusPlusPositiveCounterGlobalHist.subcode]
# Custom type (if you know the exact subtype/subcode values)
subtype=tp.CustomHist(subtype=99, subcode=1).subtype, subcode=[tp.CustomHist(subtype=99, subcode=1).subcode]
Get Minute Consumption
Retrieve consumption data aggregated by minute intervals (for flow/volume analysis).
import datetime
end = datetime.datetime.now()
start = end - datetime.timedelta(hours=24)
df = client.get_minute_consumption(
start_date=start,
end_date=end,
element_ids=[1001],
period_value=15, # Aggregation period in minutes
min_interval=1, # Minimum data interval
pandas=True
)
print(df)
Writing Data
Update Counter Global Value
Update the total accumulated volume for a counter. Used to synchronize a physical meter reading with the system.
import datetime
client.global_update(
rtu_id=12345, # ID of the RTU the counter is attached to
timestamp=datetime.datetime.now(), # When the reading was taken
liters=1500000, # Total accumulated volume in liters
position=0, # Counter position (0-3)
expansion=0 # Expansion board index (usually 0)
)
Parameters:
| Parameter | Type | Description |
|---|---|---|
rtu_id |
int |
RTU identifier |
timestamp |
datetime |
Timestamp of the reading |
liters |
int |
Total accumulated volume in liters |
position |
int |
Counter position on the RTU (0–3) |
expansion |
int |
Expansion board index (0 = main board) |
Update Analog Input Value
Update the current value of an analog sensor (e.g., a pressure or level sensor).
import datetime
client.update_analog_input_value(
rtu_id=12345,
position=1, # Analog input channel (0-based)
expansion=0,
value=4.75, # Sensor reading
timestamp=datetime.datetime.now()
)
Update Nautilus+ Values
Update all measurement values for a Nautilus Plus smart water meter in a single call.
import datetime
client.update_nautilus_values(
rtu_id=863266050913899, # RTU ID for the Nautilus+ device
positive_value_liters=2000, # Positive (forward) counter total
positive_flow_volume_militers=100, # Positive flow volume (milliliters)
negative_value_liters=800, # Negative (reverse) counter total
negative_flow_volume_militers=50,
net_value_liters=1200, # Net counter (positive - negative)
net_flow_volume_militers=50,
flow_rate_liters_per_second=12, # Current flow rate
water_temperature_celsius=16.0, # Water temperature
pressure_bars=3.5, # Water pressure
timestamp=datetime.datetime.now() # Optional, defaults to now
)
Add Historical Records
Manually push one or more historical data records for an RTU. Use the Hist class to build each record.
import datetime
import hidroconta.hist as hist
import hidroconta.types as tp
# Build a historical record
record = hist.Hist(
timestamp=datetime.datetime(2024, 6, 1, 12, 0, 0),
value=1000, # Reading value
position=0, # Counter position
expansion=0,
type=tp.CounterGlobalHist # Type of data
)
client.add_historics(
rtu_id=12345,
historics=[record] # List of Hist objects
)
Managing Elements
Create Water Meter Counter
Create a new water meter counter on an existing RTU. Returns a DataFrame with the newly created counter details.
df = client.create_demeter_watermeter(
rtu_id=12345, # RTU to attach the counter to
code='METER_ZONE_A_01' # Unique code for the new counter
)
print(df)
Delete Element
Delete an element by its ID and type. A confirmation prompt is shown by default.
import hidroconta.types as tp
# Delete a counter with confirmation prompt
client.delete_element(
element_id=9999,
type=tp.Element.COUNTER
)
# Delete without confirmation (use with caution)
client.delete_element(
element_id=9999,
type=tp.Element.COUNTER,
confirmation=True
)
Warning: Deletion is permanent. Always verify the element ID before using
confirmation=True.
User & Permission Management
Create User
Create a single new user in the system.
import hidroconta.types as tp
client.create_user(
username='john.doe',
role=tp.Role.BASIC, # User role
installations=[101, 102], # Installations the user can access
description='Field technician zone A',
access=tp.Access.COMMON # Access level
)
Available roles:
| Role | Description |
|---|---|
Role.BASIC |
Read-only access to assigned elements |
Role.INSTALLER |
Can manage devices in assigned installations |
Role.MANUFACTURING |
Manufacturing/setup operations |
Role.ADMIN |
Full administrative access |
Role.ADVANCED |
Advanced operations |
Role.HIDROCONTA |
Hidroconta internal role |
Access levels:
| Access | Description |
|---|---|
Access.COMMON |
Default standard access |
Access.ALL_READ |
Read access to all elements |
Access.ALL_WRITE |
Write access to all elements |
Create Users from CSV
Create multiple users at once from a CSV file and automatically grant them permissions. This is useful for onboarding many users in bulk.
client.create_user_with_permission(
file_route='C:/users/new_users.csv'
)
The CSV file should have one user per row with columns for username, role, installations, and description (contact support for the exact column format).
Grant Permissions
Grant a user access to a specific element.
# Grant read permission
client.grant_basic_permission(
user_id=501,
element_code='METER_ZONE_A_01',
write=False # False = read only, True = read + write
)
# Grant write permission
client.grant_basic_permission(
user_id=501,
element_code='METER_ZONE_A_01',
write=True
)
API Key Management
API keys allow programmatic access without exposing a password.
Generate a New API Key
# Generates a new API key for the specified username
client.generate_api_key(
username='john.doe',
description='Integration server key'
)
Revoke an API Key
# Revoke the current API key for a user
client.revoke_api_key(username='john.doe')
Generate a Secure Password
A helper utility to generate a strong random password (useful when creating new users programmatically):
import hidroconta.api as demeter
password = demeter.generate_secure_password(length=16)
print(password) # e.g. "aB3.xZ7mKqL1wP9s"
Types Reference
Import types with:
import hidroconta.types as tp
Element Types
tp.Element.RTU
tp.Element.COUNTER
tp.Element.ANALOG_INPUT
tp.Element.DIGITAL_INPUT
tp.Element.DIGITAL_OUTPUT
tp.Element.IRIS
tp.Element.IRIS_NBIOT
tp.Element.IRIS_LORAWAN
tp.Element.IRIS_SIGFOX
tp.Element.IRIS_3COM
tp.Element.HYDRANT
tp.Element.VALVE
tp.Element.CENTINEL
tp.Element.WMBUS_COUNTER
tp.Element.CENTAURUS
tp.Element.CENTAURUS_NBIOT
tp.Element.CENTAURUS_3COM
tp.Element.NAUTILUS_PLUS
Historical Data Types
Each type has two attributes: .subtype (the category) and .subcode (the specific measurement).
| Type | Measurement |
|---|---|
tp.CounterGlobalHist |
Total accumulated volume from a counter |
tp.FlowHist |
Flow rate from a counter |
tp.AnalogInputHist |
Analog sensor reading (pressure, level, etc.) |
tp.IrisCounterGlobalHist |
Total volume from an IRIS device |
tp.NautilusPlusPositiveCounterGlobalHist |
Nautilus+ forward total volume |
tp.NautilusPlusNegativeCounterGlobalHist |
Nautilus+ reverse total volume |
tp.NautilusPlusNetCounterGlobalHist |
Nautilus+ net volume |
tp.NautilusPlusFlowRateHist |
Nautilus+ flow rate |
tp.NautilusPlusWaterTemperatureHist |
Nautilus+ water temperature |
tp.NautilusPlusPressureHist |
Nautilus+ pressure |
tp.CustomHist(subtype, subcode) |
Custom type with manual subtype/subcode |
Status
tp.Status.ENABLED # Only active elements
tp.Status.DISABLED # Only inactive elements
tp.Status.ALL # All elements regardless of status
Roles
tp.Role.BASIC
tp.Role.INSTALLER
tp.Role.MANUFACTURING
tp.Role.ADMIN
tp.Role.ADVANCED
tp.Role.HIDROCONTA
Error Handling
Always wrap your API calls in try/except blocks for production code.
DemeterStatusCodeException
Raised when the server returns an unexpected HTTP status code (any code that is not 200 or 201).
import hidroconta.api as demeter
with demeter.DemeterClient(endpoints.Server.MAIN) as client:
client.login(api_key='YOUR_API_KEY')
try:
df = client.get_rtus(element_id=99999, pandas=True)
except demeter.DemeterStatusCodeException as e:
print(f'API Error: {e}')
# e contains the HTTP status code and error message from the server
MfaRequiredException
Raised during login when the account has MFA enabled. Contains the mfa_token needed to complete authentication.
try:
client.login(username='user', password='pass')
except demeter.MfaRequiredException as mfa:
# Complete the login with the MFA code
client.validate_mfa_email(
code=input('Enter your email OTP: '),
mfa_token=mfa.mfa_token,
device_nickname='My Script'
)
Verbose Mode (Debugging)
Enable verbose output to see detailed request/response information, useful for debugging:
with demeter.DemeterClient(endpoints.Server.TEST) as client:
client.set_verbose(True) # Enable debug output
client.login(api_key='YOUR_API_KEY')
# All subsequent requests will print detailed logs
Utilities
Timestamp Formatting
The API uses a specific date format (DD/MM/YYYY HH:MM:SS). The hidroconta.time module handles conversions:
import hidroconta.time as htime
import datetime
# Convert a datetime object to the API's string format
ts_string = htime.strftime_demeter(datetime.datetime.now())
# Result: '22/06/2026 14:30:00'
# Parse a timestamp string from the API into a datetime object
ts_datetime = htime.strptime_demeter('22/06/2026 14:30:00')
# Result: datetime.datetime(2026, 6, 22, 14, 30, 0)
Full Working Example
A complete script showing the most common operations together:
import datetime
import hidroconta.api as demeter
import hidroconta.types as tp
import hidroconta.endpoints as endpoints
# ─── Connect ────────────────────────────────────────────────────────────────
with demeter.DemeterClient(endpoints.Server.MAIN) as client:
client.login(api_key='YOUR_API_KEY')
# ─── Search ─────────────────────────────────────────────────────────────
print("--- Searching for elements ---")
df_search = client.search(
text='ZONE-A',
element_types=[tp.Element.COUNTER, tp.Element.RTU],
status=tp.Status.ENABLED,
pandas=True
)
print(df_search)
# ─── Get all RTUs ────────────────────────────────────────────────────────
print("\n--- All RTUs ---")
df_rtus = client.get_rtus(pandas=True)
print(df_rtus.head())
# ─── Get historical data ─────────────────────────────────────────────────
print("\n--- Historical counter data (last 24h) ---")
end = datetime.datetime.now()
start = end - datetime.timedelta(days=1)
df_hist = client.get_historics(
start_date=start,
end_date=end,
element_ids=[1001, 1002],
subtype=tp.CounterGlobalHist.subtype,
subcode=[tp.CounterGlobalHist.subcode],
pandas=True
)
print(df_hist)
# ─── Update a counter ────────────────────────────────────────────────────
print("\n--- Updating counter ---")
try:
client.global_update(
rtu_id=12345,
timestamp=datetime.datetime.now(),
liters=1500000,
position=0,
expansion=0
)
print("Counter updated successfully.")
except demeter.DemeterStatusCodeException as e:
print(f"Failed to update counter: {e}")
# ─── Error handling example ──────────────────────────────────────────────
print("\n--- Attempting to get a non-existent RTU ---")
try:
df = client.get_rtus(element_id=99999, pandas=True)
except demeter.DemeterStatusCodeException as e:
print(f"Expected error: {e}")
Version History
| Version | Changes |
|---|---|
| 3.1.0 | Added demeter watermeter creation. |
| 3.0.0 | Full OOP refactor. API key login + MFA validation. Not backwards-compatible. |
| 2.0.0 | Added Nautilus+ device compatibility |
| 1.8.0 | Fixed history consumption endpoint for legacy values |
| 1.7.1 | Fixed error in get_criteria |
| 1.7.0 | Added IrisCounterGlobalHist type |
| 1.6.1 | Fixed FlowlHist typo (renamed to FlowHist) |
| 1.6.0 | Added FlowHist and CustomHist historical types |
For further assistance, contact javier.lopez@hidroconta.com
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file hidroconta-3.1.0.tar.gz.
File metadata
- Download URL: hidroconta-3.1.0.tar.gz
- Upload date:
- Size: 22.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3d1ef09d9bdfb548e8277346fac40d0830a9045ed265866051b53af7dbbc982b
|
|
| MD5 |
f1737c588ec2738b921e46d73dff6b3e
|
|
| BLAKE2b-256 |
cbd7f9b9e97106f9af32f7f2fc3e186b067fcfe41788b19f072766c8b2618a53
|
File details
Details for the file hidroconta-3.1.0-py3-none-any.whl.
File metadata
- Download URL: hidroconta-3.1.0-py3-none-any.whl
- Upload date:
- Size: 16.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4efc92bd4de239ad8cc3aebaa75864aa111b3e886686e77d98f6c9f0295c9839
|
|
| MD5 |
5500cd0b7a6afded7b7d6cc4c7ec9e87
|
|
| BLAKE2b-256 |
41d23c3be6295144c979d5674598080448a022c1d05b230d32193565977c1012
|