ELAI S.r.l.

AI must work without Internet: what should stay on the device?

A reproducible case explains which functions to keep local, how much storage to reserve, and how to recover history after twelve hours offline.

AI must work without Internet: what should stay on the device?

When the network fails, what work must continue?

A remote greenhouse collects environmental measurements and sends them to a service that flags unusual conditions. If Internet access disappears for twelve hours, the problem is more than displaying an error: decide whether to keep measuring, retain history, and provide a local indication. The same issue affects equipment with intermittent connectivity. This analysis helps readers choose between a remote service, local device, and hybrid architecture: which dependencies to remove from essential work, how much storage to reserve, and how to recover data without confusing past and present.

The answer is not “move all AI onto the board.” Separate immediate functions, archival work, and deferrable analysis. An executed simulation shows that twelve hours of data may need little space yet still be partly lost if the budget is wrong; network recovery also does not instantly clear backlog. We tested no sensors, firmware, models, flash storage, or real protocols. The scenario is hypothetical, calculations are reproducible, and device performance remains to be measured. This is neither an automatic greenhouse-control design nor an agronomic trial.

Separate a useful decision from an available connection

The minimum path acquires a measurement, validates format and quality, applies the chosen local function, and clearly reports its state. An explicit threshold may suffice for a simple alert; a model needs additional requirements and evidence of benefit. Local AI also needs preprocessing, parameters, supporting data, and runtime: downloading weights does not remove a hidden remote call. A concrete check disconnects the network in a test setting and records which dependencies block the function, rather than inferring success from an architecture diagram.

A local function must not pretend everything is normal when a sensor fails or required updates are missing. Distinguish valid measurement, insufficient data, unavailable model, and absent connection so the operator understands the limit. Our hypothetical local output is an indication, not a pump command: actuators need separate constraints and verification. Deferrable analysis, such as comparing history across greenhouses, may remain on a server. Choose the boundary by the work that must continue, not by a preference for cloud or embedded computing.

How much storage is needed for twelve hours offline?

Define the data contract before choosing a board. Assume one record every ten seconds and 64 serialized bytes per record, including identifier, time, value, and status. The size is hypothetical: it is not a Python object' s size, network packet size, or database page size. Added integrity information or metadata must be included in actual serialization. Ten seconds is not a crop-sampling recommendation. It makes the relationship between rate and space explicit.

The formula answers “how many records arrive during the outage?” lambda is records per second, D outage seconds, and b bytes per record. B is records to retain and S their payload storage. The interval includes the start but excludes the end: samples at 0, 10, …, 43190 seconds. Distinguish decimal and binary units: one KiB is 1024 bytes.

lambda = 1/10 = 0.1 records/s B = lambda * D = 0.1 * 43200 = 4320 records S = B*b = 4320*64 = 276480 bytes = 270 KiB

A 256 KiB area holds only 4096 records of 64 bytes. It is smaller than the required 270 KiB, though the numbers look close. Keeping newest data by evicting oldest means 224 records are already missing at the end of twelve hours. This protects the present but loses history; it is not automatically better than rejecting new data. If the goal is reconstructing an earlier event, those records may matter most. Overflow policy must follow the use of history.

270 KiB is backlog payload alone, not the platform' s full requirement. Separate model weights, activations and peak RAM, protocol buffers, record index, and persistent storage. Nominally free memory may also contain reserved areas or require block erasure. Record arithmetic therefore does not prove a specific board can run the application. First reserve an explicit budget, then verify it against the actual format, firmware, and storage medium.

The network returns: why history is not yet synchronized

Assume the server can steadily accept two useful records per second. This is hypothetical service capacity, not measured radio performance or link bits per second. It must include the actual bottleneck through to the useful outcome; retries and waits can reduce it. Local measurement still produces 0.1 records per second. Backlog drains at departure rate minus arrival rate, not departure rate alone. Think of emptying a container while a tap keeps filling it: the analogy explains the balance, not faults or packets.

mu = 2 records/s net_drain = mu - lambda = 1.9 records/s t_recovery ≈ B/(mu-lambda) = 4320/1.9 = 2273.6842 s

The continuous calculation gives about 37.9 minutes, not the 36 minutes obtained by dividing by two. It requires mu greater than lambda: if equal, old backlog never shrinks; if output is slower, it keeps growing. The formula assumes constant rates, immediately available service, and no further outage. Bursty traffic or reconnection pauses require a richer time model. Practically, reserve capacity to recover history as well as sustain normal flow: a connection barely adequate in normal operation may never recover from failure.

The simulation: three capacities, the same events

To check the formula, use discrete events: arrivals and departures at defined times. One tick is half a second. A numbered record is created initially and every twenty ticks. The network removes nothing through tick 86400, twelve hours; first completion is half a second later. Then one send completes per tick until the queue first empties. When arrival and departure coincide, add the new record first. A full buffer evicts the oldest. There is no randomness, so no seed is needed.

With 8192 slots, the queue reaches 4321 records, not 4320: a sample arrives at network recovery before the first send. It first empties 2274 seconds after recovery, 37 minutes 54 seconds, consistent with the continuous approximation. At that point 4548 records have been generated and uploaded: 4320 from the outage plus 228 new samples. Uploading more than the original backlog is not duplication: acquisition continued during recovery. The model ends there, not at the end of the day.

SlotsSpace (KiB)LostGeneratedUploadedRecovery (s)
102464.0329743741077538.5
4096256.0225453643112155.5
8192512.00454845482274.0

With 4096 slots, 225 records are lost: 224 during the outage plus one evicted at reconnection before the first completion. Recovery takes 2155.5 seconds, less than lossless recovery. With 1024 slots it finishes sooner still, in 538.5 seconds, but 3297 records are missing. Read this carefully: “faster synchronization” can conceal deleted data. The table also gives generated and uploaded counts: durations differ because each run ends at first empty queue; these are not equal time windows for comparing total production.

Simulated queue for three capacities: a flat region can hide evictions. No storage, network, or hardware platform measured.
Simulated queue for three capacities: a flat region can hide evictions. No storage, network, or hardware platform measured.

In the plot, notice the flat portions of smaller queues: acquisition has not stopped; each new arrival evicts old data. After twelve hours the curves descend; only the largest capacity preserves every record under this protocol. Axes show simulated time and record count, not measured RAM or network performance. Serialized capacity is not Python process memory: deque contains objects and extra metadata, which we do not use as a board measurement.

Stored data and received messages are different guarantees

OASIS MQTT 5.0, a standard dated 7 March 2019, distinguishes delivery levels. QoS 1 can produce duplicates; a protocol acknowledgement does not imply the final application has completed its work. These are protocol properties, not results from our own MQTT test.

For archive design, propose a stable application identity per record, such as device, boot epoch, and a consistently persisted counter. The server can store that identity under a uniqueness constraint, making retries avoid a second row. Test the design when a reply is lost after writing. A counter reset to zero may identify different samples as identical. Local deletion policy must also specify which acknowledgement allows space reclamation; “the packet was sent” is not an archive acknowledgement.

SQLite' s atomic-commit documentation explains that behavior after power loss also depends on storage and flush assumptions. Rollback and WAL modes use different mechanisms. We tested neither SQLite nor power interruption.

Our queue exists only inside the simulation process. It proves nothing about surviving reset, flash integrity, or atomic writes. Real design must distinguish acquired, persisted, sent, and acknowledged records; restart must reconstruct state without deleting unacknowledged data. Databases and logs are alternatives to assess against resources, not interchangeable slogans. A microcontroller and embedded Linux system may need different strategies. The decisive verification interrupts power and communications at controlled points, using nonoperational data and a device-appropriate protocol.

History must not control the present

Reconnection brings old measurements to the server. Preserve acquisition time separately from receipt time, alongside quality and local-function version. Otherwise twelve hours uploaded in minutes can look like a new event. Keep historical recovery separate from current decision-making. Do not automatically replay old commands as new requests: alerts and actions can expire. This is not a safe actuator-control design; it separates two data uses with different requirements.

What to verify before choosing the platform

The alternatives can now be compared concretely. Cloud only: central analysis is easier to update, but depends on the network, and a remote queue cannot retain data never sent. Local only: the function may be autonomous, but updates, resources, and maintenance remain. Hybrid: a minimum function uses local data and archives records; the server adds deferrable analysis. A non-AI rule may provide the minimum function if sufficient. The simulation compares queues only, not diagnostic quality or economic value of these architectures.

Test the chosen device with declared format and versions: measure peak RAM separately from weights, available storage and archive overhead; mean latency, percentiles, and deadline for the local function during synchronization; power in watts and energy per inference or useful decision in joules, separating sensing and radio. An outage longer than budget tests overflow policy. Repeat after updates and reset to test state continuity. These are proposed experiments without results, not implied measurements of the photographed board.

Read the code and answer the original question

simulate uses deque as an ordered container, generates increasing identifiers, and evicts the oldest when full. A removal is an assumed service completion, not an MQTT packet or database acknowledgement. Assertions enforce count conservation and disjoint lost/uploaded sets, then check 225 losses and 2274-second lossless recovery. No duplicates is a property of this simulation, not a network guarantee. Tick count determines computational work; Python memory and execution duration are not embedded benchmarks.

The operational conclusion is to keep what is needed for a valid local indication on the device and disclose its limits; budget data and state; provide recovery capacity above normal inflow. Here 256 KiB of payload cannot cover twelve hours, and a smaller queue empties sooner because data was lost. Capacity of 8192 records avoids loss in the model but proves neither power-failure resilience nor product suitability. The architecture decision is now reasoned; board and model choice still requires missing tests. This analysis does not announce EL-AI embedded products or installations.

Sources and reproducible code

OASIS — MQTT 5.0, 7 March 2019; sections 4.3–4.6.

SQLite — Atomic Commit; storage assumptions and sections 8–9.

import json,sys
from collections import deque
from pathlib import Path
# One tick is 0.5 seconds. New sample precedes completion at coincident events.
OUTAGE_TICKS=86400
RECORD_BYTES=64
def simulate(capacity):
 q=deque();lost=[];uploaded=[];maxq=0;trace=[];generated=0;end=None
 for tick in range(120001):
  if tick%20==0:
   if len(q)>=capacity:lost.append(q.popleft())
   q.append(generated);generated+=1;maxq=max(maxq,len(q))
  if tick>OUTAGE_TICKS and q:uploaded.append(q.popleft())
  if tick%120==0:trace.append([tick/2,len(q)])
  if tick>OUTAGE_TICKS and not q:end=tick/2;break
 trace.append([end,0])
 assert generated==len(lost)+len(uploaded)+len(q)
 assert not(set(lost)&set(uploaded)) and len(uploaded)==len(set(uploaded))
 return dict(capacityRecords=capacity,capacityBytes=capacity*RECORD_BYTES,lost=len(lost),uploaded=len(uploaded),generated=generated,maxQueue=maxq,drainSeconds=end-OUTAGE_TICKS/2,trace=trace)
runs=[simulate(c) for c in [1024,4096,8192]]
assert runs[2]['lost']==0 and runs[2]['drainSeconds']==2274 and runs[2]['maxQueue']==4321
assert runs[1]['lost']==225
assert abs(4320/(2-.1)-2273.684210526316)<1e-9
assert 4096*64==262144 and 4320*64==276480
result=dict(python=sys.version.split()[0],seed=None,scope='Discrete deterministic buffer simulation only; no sensor, model, MQTT client, flash or device tested',samplePeriodSeconds=10,assumedRecordBytes=64,outageSeconds=43200,assumedServiceRecordsPerSecond=2,fluidApproxDrainSeconds=4320/(2-.1),runs=runs)
Path(__file__).with_name('results.json').write_text(json.dumps(result,indent=2),encoding='utf8')
print(json.dumps([{k:v for k,v in x.items() if k!='trace'} for x in runs]))

Code, data, and instructions · JSON. Educational calculations executed with Python 3.14.0; figures with Matplotlib 3.11.2. AI-assisted analysis, without claiming peer review or human review. Original illustrative ImageGen cover: it does not document EL-AI people, premises, or installations. Sources accessed 27 October 2026.