Skip to main content

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.

Note: Version 3.0.0 is not backwards-compatible with v1.x or v2.x.


Table of Contents

  1. Installation
  2. Quick Start
  3. Authentication
  4. Available Servers
  5. Response Format: JSON vs Pandas
  6. Reading Data
  7. Writing Data
  8. Managing Elements
  9. User & Permission Management
  10. API Key Management
  11. Types Reference
  12. Error Handling
  13. Utilities
  14. Full Working Example
  15. 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 support
  • requests >= 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.1 Added method for getting criteria in any element type + Added serial number when creating a demeter watermeter.
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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

hidroconta-3.1.1.tar.gz (22.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

hidroconta-3.1.1-py3-none-any.whl (16.4 kB view details)

Uploaded Python 3

File details

Details for the file hidroconta-3.1.1.tar.gz.

File metadata

  • Download URL: hidroconta-3.1.1.tar.gz
  • Upload date:
  • Size: 22.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.6

File hashes

Hashes for hidroconta-3.1.1.tar.gz
Algorithm Hash digest
SHA256 efc4b7dc072ec7e56bf79f7e0affe55de81f9bc9e1a45e68a45153c4dc68159e
MD5 177e12eb004d7f355b277b3af3d68164
BLAKE2b-256 dc5fc5d1039fb27368ca63e2cd5e32eabf8ed69dd2a7e58547120593bcbfe2d1

See more details on using hashes here.

File details

Details for the file hidroconta-3.1.1-py3-none-any.whl.

File metadata

  • Download URL: hidroconta-3.1.1-py3-none-any.whl
  • Upload date:
  • Size: 16.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.6

File hashes

Hashes for hidroconta-3.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 a2246399c9b67caddb09fcc3cab8fb765b8d967bcfae8450e519d8b71366e68b
MD5 a821367a109968437a24b22e4f03a038
BLAKE2b-256 83266d8fc2c8e4c49ba022f4bd5be06e235be8360ebb8d0374e2bce6759faafd

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page