Inficold Integration Guide
This guide explains how to connect Inficold cold rooms to Coldtivate. It covers:
- System administrators: setup, configuration and troubleshooting.
- Developers: how the integration works in the Base API and the mobile app.
For how sensors work in general, see Integrations & Data Collection.
1. How the integration works
Inficold devices report their readings to Inficold's own cloud. Coldtivate never connects to the devices themselves. It uses two Inficold interfaces, each for one job:
| Interface | Used for | Credentials |
|---|---|---|
Inficold dashboard (infilog.com/devicedashboard) |
Finding which devices a user's Inficold account can see. Used only while a sensor is being set up. | The user's own dashboard username and password, typed in the app. Used once and never stored. |
Official history API (infilog.com/api/coldtivate/get_scr_data.php) |
Checking the selected device and its chambers, and reading temperature and humidity. | One Coldtivate bearer token, set on the server (INFICOLD_API_TOKEN). Never sent to the app. |
The official API is a minimal, read-only endpoint Inficold built for Coldtivate. Its published spec (Inficold Sensor Data API – Coldtivate Integration) only covers reading the history of one device for a date range:
- Request:
id(device ID),dateand optionalend_date(YYYY-MM-DD), and optionallimit. - Response:
status,messageanddata, where each record hasunit_id,log_id,timestamp,temperature_chamber_1/_2andhumidity_chamber_1/_2.
It can't list devices, filter by time of day, report a timezone or push readings. That's why devices are found through the dashboard, each poll downloads whole local days, timestamps fall back to Asia/Kolkata, and readings are polled. The adapter also relies on fields the spec doesn't include (see Polling and data).
| Step | What happens |
|---|---|
| 1 | Inficold installs the device, connects it to its cloud and enables it for Coldtivate's API token. |
| 2 | A Registered Employee chooses Inficold in the app and signs in with their Inficold dashboard account. |
| 3 | The Base API logs into the dashboard and returns the account's devices by name and ID. It makes no API calls yet. |
| 4 | The user picks a device and presses Test connection. The Base API checks that one device live through the official API: is it authorized, and which chambers does it have? |
| 5 | The user picks a chamber and saves the cooling unit. |
| 6 | Every 4 hours, the Base API polls the official API for each linked chamber and stores its temperature and humidity. It never uses the dashboard for this. |
| 7 | The readings appear in the app under Cooling Units → Room Conditions. |
Key facts:
- One chamber is one cooling unit. A device has one to three chambers, and each chamber is identified as
<device_id>:<chamber>, for example186:2. Users never type this. - Dashboard credentials are transient. They're used for one discovery request and are never saved, logged or sent back to the app.
- A device must pass both checks. It must be visible in the user's dashboard and accessible with Coldtivate's API token. A device that fails the second check is reported by name and ID when it's selected, so the user can give support the exact device.
- Each request is bounded. Listing devices makes no API calls, and the connection test makes one, so large accounts never produce long requests.
- Readings: temperature (°C) and relative humidity (%) are read for each chamber.
- History: when a chamber is linked, the last 24 hours are imported. Older history isn't imported.
2. Requirements
Devices and accounts
Coldtivate has no hardware setup step. Before a company can link devices, confirm with Inficold that:
| Item | Why |
|---|---|
| The customer has an Inficold dashboard account that lists their devices. | The account is how Coldtivate finds the customer's devices. |
| Each device is enabled for Coldtivate's API token. | Selecting a device the token can't read shows its name and ID with a "contact support" message. |
| The device timezone is known. | Inficold timestamps carry no timezone. The default is Asia/Kolkata. |
| Each device is online and reporting. | A device that reports nothing appears as a sensor with no data. |
Network
- Outbound access: the Base API web and Celery worker containers need outbound HTTPS access to
infilog.com, for both the dashboard and the history API. No inbound connection from Inficold is needed. - Celery: the worker and beat must be running. Beat queues sensor polling every 4 hours.
- Redis: the Base API cache must be available. Device details (name, chamber count) are cached there.
Settings
Only values that differ between deployments are environment variables on the Base API:
| Variable | Default | Purpose |
|---|---|---|
INFICOLD_API_TOKEN |
(empty) | Secret. Coldtivate's bearer token for the official API. Required for discovery, testing, saving and polling. If it's empty, Inficold setup and polling fail with "Inficold API token is not configured", and the rest of the platform is unaffected. Set it per environment (development, staging, production). |
INFICOLD_API_URL |
https://infilog.com/api/coldtivate/get_scr_data.php |
Official history endpoint. Configurable so mock-server and staging stress runs can point elsewhere. |
Stable provider URLs and implementation defaults are constants in the code, following the other sensor providers:
| Constant | Value | Purpose |
|---|---|---|
DASHBOARD_LOGIN_URL (inficold.py) |
https://infilog.com/devicedashboard/index.php |
Dashboard login page. |
DASHBOARD_URL (inficold.py) |
https://infilog.com/devicedashboard/index.php?md=User&sd=Dashboard |
Dashboard page the devices are read from. |
DASHBOARD_TIMEOUT_SECONDS (inficold.py) |
10 |
Timeout for each dashboard request. |
DEFAULT_TIMEZONE (inficold.py) |
Asia/Kolkata |
Timezone used to read Inficold timestamps. |
METADATA_CACHE_TTL_SECONDS (inficold.py) |
24 hours | How long cached device details are used without refreshing. |
METADATA_STALE_TTL_SECONDS (inficold.py) |
7 days | How long cached device details may still be used if Inficold is briefly unreachable. |
INFICOLD_DISCOVERY_TOKEN_MAX_AGE_SECONDS (inficold.py) |
15 minutes | Lifetime of each discovery ticket: from signing in to testing a device, and from the test to saving the cooling unit. |
Limitation: the design supports one Inficold API token per deployment. Supporting several Inficold accounts with different tokens would need a mapping from devices or companies to tokens.
3. Configuration
Step 1: set the API token
Set INFICOLD_API_TOKEN through the environment's normal secret mechanism, then restart the Base API web and Celery containers. Nothing is configured in the Django admin.
Step 2: link a chamber to a cooling unit
In the mobile app, as a Registered Employee of the company:
- Create or edit the cooling unit.
- Turn on Sensor available and choose inficold.
- Enter the Inficold dashboard username and password, then press Confirm.
- The app lists the account's devices by name and ID, for example Panposh 20MT Rourkela / ID 186. Use the search box to filter them, then select one.
- Press Test connection. The app checks the device against Inficold, showing Testing connection… while it runs. Testing never saves anything.
- If it fails, the reason is shown and the device list stays open, for example "Panposh 20MT Rourkela (ID 186) isn't enabled for Coldtivate". Pick another device, or contact support with that ID.
- If it succeeds, Connected. The device is reachable. is shown with the device's chambers.
- Select the chamber for this cooling unit and press Save, then save the cooling unit within 15 minutes of the test. Use the back arrow to test another device.
Repeat for each chamber, one cooling unit per chamber.
Step 3: verify
- Readings: open Cooling Units → Room Conditions for the cooling unit. Readings from the last 24 hours should appear right after saving. Check that the times match the room's local clock.
- Polling: after the next scheduled poll (at most 4 hours later), in the Django admin under Storage → Sensor integrations, the integration should have a recent Last successful poll at and Polling error unchecked. Its Username is the device ID and its password is empty.
- Freshness: Last updated on the Room Conditions screen should keep advancing. If no new reading arrives for more than 12 hours, the screen shows a Stale warning.
Maintenance
- Rotating the token: change
INFICOLD_API_TOKENand restart the containers. Every linked chamber uses the new token on its next poll, and polling history is kept. - Editing a cooling unit: changes that don't touch its sensor don't need the Inficold sign-in again.
- Changing the chamber: choose Inficold again and sign in. The new chamber starts a fresh polling history.
- Moving a cooling unit:
- Same company: allowed.
- Different company: rejected until the Inficold sensor is selected again (sign in and test the device again) or turned off.
- Removing a device: turn off the sensor on each cooling unit that uses one of its chambers. A chamber whose device is no longer enabled for the token fails every poll and raises a sensor error notification.
4. Developer reference
Components
| Component | Location |
|---|---|
| Polling adapter and dashboard client | base/apps/storage/services/sensors/inficold.py (InficoldIntegration, InficoldDashboardClient) |
| Discovery workflow: device and chamber discovery, discovery errors, signed tickets, connection test and save validation | base/apps/storage/services/sensors/inficold.py (its own section, using plain user IDs) |
Credential resolution for polling (generic; Inficold fills in the token through InficoldIntegration.runtime_credentials()) |
base/apps/storage/services/sensory.py |
| Adapter registry (applies an adapter's schemas only when it declares them) | base/apps/storage/services/sensors/registry.py. Inficold's InficoldCredentials and InficoldConfiguration schemas live in inficold.py. |
| Polling task | base/apps/storage/tasks/sensor_polling.py (sync_sensor_data, poll_sensor_integration) |
| Source-list and test-connection endpoints | base/apps/storage/views/sensors.py (SensorIntegrationViewSet, InficoldViewSet) |
| Cooling-unit validation | base/apps/storage/serializers/cooling_unit.py |
| Mobile sign-in, device and chamber steps | src/screens/Dashboard/Management/AddCoolingUnit/components/Sensors/ (components/SensorModal/components/InficoldForm.tsx, like Ecozen's own form; the shared components/SourceList.tsx list; sensorErrors.ts for error messages and the expired-session recovery when saving) |
Adapter contract
InficoldIntegration follows the shared adapter contract, and never touches Django models. It's built from a plain configuration:
{
"type": "inficold",
"username": "186", # the device ID
"password": "<API token>", # from INFICOLD_API_TOKEN
"source_id": "186:2", # <device_id>:<chamber>
}
authorize(): sets the bearer header.usernamemust be one positive device ID.list_sources(): returns the chambers of that one device, using the API's chamber count.get_datums_from_source(): reads temperature and humidity for the chamber. A source whose device differs fromusernameis rejected.validate_source(source_id, live=False): checks the chamber exists.live=Trueskips the cache, as saving does.test_connection(): a live connection test for the configured device. One uncached request that returns the device's chambers, used by the chamber step.
At poll time, resolve_sensor_credentials() builds this configuration from the saved integration, with the token read from settings on every call.
Dashboard client
InficoldDashboardClient(username, password).discover() returns the account's devices as (id, name) pairs. For each call it:
- Opens a fresh
requests.Sessionand loads the login page to get a session cookie. - Posts
md=UserLogin,sd=CheckUser,UsernameandPasswordwithout following redirects. - Treats a redirect back to the login page, or a login form returned instead, as wrong credentials. A 302 alone isn't trusted.
- Loads the dashboard and reads devices from two places on the real page:
- The device selector. The page builds it inside a script as a JavaScript string (
'<option value="4101">Cold Room A</option>', inserted into<select name="DeviceID">). Only scripts that build that selector are read, and a plain-HTML selector is read too. - The sidebar. Each device is a
treeviewentry whose header is the device name, followed byILDeviceID=<id>links. - Selector names win over sidebar names. Numeric options and IDs anywhere else on the page, such as map data, are ignored.
- The device selector. The page builds it inside a script as a JavaScript string (
- Keeps positive IDs only, removes duplicates, and turns names into short plain text, falling back to
Device <id>. - Closes the session. The dashboard's logout endpoint is deliberately not called: the session exists only in memory for this one request.
Credentials, cookies and page HTML are never logged or stored. Only counts and error types are logged.
API endpoints
Discover devices: POST /storage/v1/user-sensor/sources/
{
"integration_type": "inficold",
"username": "<dashboard username>",
"password": "<dashboard password>"
}
- Access: any authenticated user, like the other providers' source listing. Whether the user may configure a given cooling unit is checked when it is saved.
- Behaviour: dashboard login and device parsing only. No official API calls.
- Success (
200): the dashboard devices, and a device ticket for the connection test. - Errors: always
{"code": ..., "detail": ...}.
| Status | code |
When |
|---|---|---|
400 |
INFICOLD_DASHBOARD_AUTHENTICATION_FAILED |
Username or password missing. |
401 |
INFICOLD_DASHBOARD_AUTHENTICATION_FAILED |
The dashboard rejected the credentials. |
404 |
INFICOLD_NO_DASHBOARD_DEVICES |
The account lists no devices. |
503 |
INFICOLD_PROVIDER_UNAVAILABLE |
Dashboard unreachable, an unexpected page, or the token isn't configured. |
Test a device's connection: POST /storage/v1/inficold/test-connection/, next to Ecozen's POST /storage/v1/ecozen/test-connection/.
- Access: any authenticated user, as for listing devices. The ticket limits which devices can be tested.
- Behaviour: Inficold's connection test and chamber discovery in one operation, so nothing is saved.
- It checks that the device ticket was issued to this user and lists this device.
- It makes exactly one live official API request for the device, never served from cache (
InficoldIntegration.test_connection()). That single response shows whether the token is accepted and the device is authorized, and gives its chamber count. It also refreshes the metadata cache that polling uses.
- Success (
200):
| Status | code |
When |
|---|---|---|
400 |
INFICOLD_DISCOVERY_EXPIRED / INFICOLD_DISCOVERY_INVALID |
The ticket expired, was tampered with, belongs to another user, is a source ticket, doesn't list this device, or the device ID is malformed. No API call is made. |
409 |
INFICOLD_DEVICE_NOT_ENABLED |
Inficold doesn't give Coldtivate's token access to this device, or the token itself was rejected. The detail includes the device ID. |
503 |
INFICOLD_PROVIDER_UNAVAILABLE |
Timeout, server error, or an inconsistent response such as a unit_id mismatch. |
Not-enabled devices use 409 rather than 422, because the mobile HTTP client logs request details on 422.
Link a chamber: create or update the cooling unit (POST or PUT /storage/v1/cooling-units/) with:
{
"sensor": true,
"sensor_data": {
"type": "inficold",
"source_id": "186:2",
"source_token": "<signed source ticket>"
}
}
The saved integration has username = "186" and an empty password. The tickets and any dashboard credentials are never stored.
Read the sensor: GET /storage/v1/cooling-units/{id}/sensor-data/ returns source_id, type, username (the device ID) and the link date. It never returns a password.
Discovery tickets
Discovery uses two short-lived tickets, signed with Django signing. Each has its own salt, so one can never be used in place of the other. Both contain only the user ID and provider type, plus the signing timestamp. They aren't tied to a company: like with other providers, users can attach a device they control to any cooling unit they're allowed to configure.
| Ticket | Issued by | Lists | Accepted by |
|---|---|---|---|
Device ticket (device_token, salt storage.inficold.device-ticket.v1) |
Listing devices | The dashboard device IDs | The connection test |
Source ticket (source_token, salt storage.inficold.source-ticket.v1) |
The connection test | That device's <device_id>:<chamber> sources |
Saving the cooling unit |
Each ticket lasts 15 minutes (INFICOLD_DISCOVERY_TOKEN_MAX_AGE_SECONDS in inficold.py) from when it's issued, so the time to save starts at the connection test.
When a source ticket is required: adding Inficold to a cooling unit, changing its chamber, or saving it under a different company. Saving an unchanged chamber in the same company doesn't need one.
Checks on save:
- The signature is a valid source ticket and it's younger than 15 minutes.
- It was issued to the current user.
- The chosen chamber is one of the sources it lists. A guessed chamber, or a device ticket, is rejected without an API call.
- The user has a staff role in the cooling unit's company, which is the normal cooling-unit authorization.
- The device and chamber pass a live check against the official API.
Errors: ticket failures return sensor_data.source_token as INFICOLD_DISCOVERY_EXPIRED or INFICOLD_DISCOVERY_INVALID. The app then shows "Your Inficold session expired…" and reopens the Inficold sign-in. Device or chamber failures return sensor_data.source_id.
Polling and data
- Schedule:
sync_sensor_dataruns every 4 hours and queuespoll_sensor_integrationfor each linked chamber. Polling uses only the official API. - Poll window:
- The first sync covers the last 24 hours.
- After that, each poll requests everything since the chamber's last stored reading, minus a 15-minute overlap.
- Fields beyond the published spec: the adapter requires these, and they aren't in Inficold's spec. Confirm them with Inficold if the API changes.
| Field | Used for |
|---|---|
offset (request) |
Paging through results. |
has_more (response, required) |
Knowing when to stop paging. A response without it is treated as malformed (provider unavailable). |
returned (response) |
Sanity check on the page length. Only logged on mismatch. |
chamber_count (response) |
Listing and validating chambers, from 1 to 3. The spec only describes chambers 1 and 2. |
device_name (response) |
Cached device metadata. |
timezone (response, optional) |
Reading timestamps. Falls back to Asia/Kolkata when absent. |
- Requests:
- The API only filters by local date (
date,end_date) and pages withlimitandoffset. - The connector fetches every page, up to 1,000 rows per page, with a 15-second timeout per request and a safety limit of 500 pages.
- It then keeps only readings inside the exact time window.
- Timestamps: Inficold sends naive local times. They're read in the device timezone if the API ever reports one, else
Asia/Kolkata(DEFAULT_TIMEZONEininficold.py), and stored in UTC. - Values:
"NaN", missing and malformed values are skipped. A successful response with no rows is a successful poll. - Storage: readings are stored in
SensorReadingwith the provider timestamp, and averaged into 15-minute buckets inCoolingUnitSpecifications(see Polling and ingestion). - Identity: changing the provider type or source ID resets the polling watermark. Rotating the token doesn't.
- Failures:
| API response | Treated as | Result |
|---|---|---|
HTTP 401 |
Token rejected | Non-retryable: the chamber fails at once and is reported once. |
HTTP 403 / 404, or body status 403 / 404 |
Device not accessible | Non-retryable. In the connection test, it's reported as INFICOLD_DEVICE_NOT_ENABLED. |
Timeout, network error, 5xx, malformed JSON, unit_id mismatch, invalid chamber count or pagination |
Provider unavailable | Retried after 5, 15 and 30 minutes, then SENSOR_ERROR and Sentry. |
- Device metadata: device name and chamber count are cached per device and token fingerprint (a hash, never the token itself), with a stale fallback for transient failures.
Tests
- Unit and integration tests: the dashboard client and adapter tests live in
services/sensors/test_inficold.py; discovery, tickets, saving and polling live with the general sensor tests intest_sensor_ingestion.py. Both use mocked HTTP, run with the normal test suite, and fail any test that attempts a real request. - Shared adapter contract:
services/sensors/test_sensors.pyruns the common adapter checks for Inficold offline, using a plain username/password configuration. - Live checks: opt-in with
TEST_SENSORS_INFICOLD, plusTEST_SENSORS_INFICOLD_USERNAME(device ID),TEST_SENSORS_INFICOLD_PASSWORD(API token) andTEST_SENSORS_INFICOLD_SOURCE_ID. They read the device's full history, so don't run them against production. See Testing.
5. Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| App: "Incorrect Inficold username or password." | The dashboard rejected the sign-in. | Check the credentials by logging into the Inficold dashboard directly. |
| App: "No devices were found in this Inficold account." | The account lists no devices. | Ask Inficold to assign the devices to that account. |
| Test connection: " |
Coldtivate's token can't read that device, or the token was rejected. | In the Base API logs, "rejected the deployment API token" means the token is wrong: update INFICOLD_API_TOKEN. Otherwise ask Inficold to enable that device ID for Coldtivate. |
| App: "Too many Inficold sign-in attempts. Please wait a while and try again." | The Base API's per-user write rate limit (120 requests per minute) was reached (HTTP 429). |
Wait a minute before signing in or testing again. |
| App: "Inficold is temporarily unavailable. Please try again." | Inficold unreachable, an unexpected dashboard page, or INFICOLD_API_TOKEN isn't set. |
Check the Base API logs for the error type, outbound HTTPS to infilog.com, and the token setting. |
| App: "Your Inficold session expired…" after Test connection or when saving | More than 15 minutes passed since signing in (for the test) or since the test (for saving). | Sign in to Inficold again and re-select the chamber. |
| Saving fails with "Inficold chamber N does not exist for this device" | The device's chamber count changed since discovery. | Choose Inficold again and pick an existing chamber. |
| Moving a cooling unit to another company reopens the Inficold sign-in with "Your Inficold session expired…" | Saving under a different company needs a new source ticket. | Sign in, test the device and pick the chamber again, or turn the sensor off before moving. |
| Readings are shifted by a fixed number of hours | The devices aren't on Asia/Kolkata time. |
Ask the development team to change DEFAULT_TIMEZONE in inficold.py. New readings use it; stored readings keep their times. |
| Stale warning, or a sensor error notification | Polls are failing, or the device has stopped reporting to Inficold. | In the admin, check the integration's Polling error and Last successful poll at. Check the token is set and the device is still enabled, then confirm with Inficold that it's reporting. |
| No data, but no error | Inficold returns no readings for that period. | The device isn't sending data to Inficold. Contact Inficold. |
| Gaps in the graph after an outage | The next successful poll fills gaps automatically, as long as Inficold has the data. | Wait for the next poll. Readings Inficold received more than 15 minutes after later ones were stored can't be recovered. |
6. Known limitations
- Minimal official API: Inficold's spec only covers date-range history for one device. Paging, chamber count, device name and timezone aren't in it, so a change on Inficold's side could break polling without notice.
- Undocumented dashboard: discovery depends on the dashboard's login flow and page markup, which Inficold doesn't document and may change. If it changes, discovery fails with "temporarily unavailable" while polling of already-linked chambers keeps working.
- One token per deployment: every company uses the same Coldtivate API token.
- Ownership: a device is offered to anyone who can sign in to a dashboard account that lists it. Two companies could link the same device.
- Enablement is checked late: the device list comes from the dashboard alone, so a device that isn't enabled for Coldtivate only shows as such once it's selected.
- No older history: only the last 24 hours are imported when a chamber is linked.
- Late readings: a reading that reaches Inficold after newer readings have been stored, with a timestamp more than 15 minutes older, is never fetched.
- Silent devices: an empty successful response is a normal poll, so a silent device looks the same as a period without readings. It's only caught by the 12-hour Stale warning.
- Request volume: Inficold filters only by date, so each poll downloads the whole local day for the device. This is repeated for each of its chambers.
- Pagination: it assumes Inficold's result order is stable while pages are fetched. This hasn't been confirmed by Inficold yet.