In plain terms
Fingerprint attendance terminals come in two kinds that look identical on a wall. The expensive kind can talk to the internet by itself. The cheaper kind - the one most businesses already own - cannot; it only talks to a computer on the same local network and waits to be collected from.
This is a small program that runs on a laptop or a tiny Raspberry Pi in the customer’s office. It talks to the old device the only way the device understands, and then reports the results to the cloud service as though the device had been internet-capable all along.
Why it matters commercially: without it, the sales conversation includes “and you will need to replace the terminal at every branch first”. With it, the customer starts on Monday with the hardware they already have. It is the smallest project on this site and one of the most commercially consequential.
The problem
ZKTeco terminals come in two families that are completely different to integrate with.
The expensive ones support ADMS: the device itself opens a connection to a cloud server and pushes attendance events as they happen. The cheaper and older ones - sold as Ethernet or PC Connection models - do not. They speak a private protocol on the local network and wait for a PC to come and collect. They cannot reach the internet, and no configuration screen will change that.
That is a commercial problem before it is a technical one. The gym platform this agent was built for is a cloud product, and its customers are gyms that already own the cheap devices. “Buy new hardware for every branch before you can use our attendance module” is not a sentence that closes a deal - it turns a subscription decision into a capital expenditure decision, with an approval process attached.
The design decision that pays off for years
The obvious implementation is a second server endpoint - one for modern devices, one for the agent, each with its own data format.
I did the opposite. The agent imitates the protocol, not the device. It reports events in exactly the format the server already accepts:
[ ZKTeco device ] --TCP/4370, ZK protocol--> [ LAN Agent ] --ADMS HTTP--> [ FitPulse ]
(Ethernet only) (laptop / Pi) (unchanged)
The server does not know, and does not need to know, whether a record came from a terminal that pushed it or an agent that fetched it. One way in, one set of validation rules, one place where attendance can be wrong.
The payoff shows up in maintenance rather than in the first release. Every future fix to attendance handling applies to both families of hardware automatically, and there is no slow drift where new devices get a feature old ones never do. The compatibility problem lives entirely at the edge, in the component that is cheapest to update - which means supporting older hardware costs almost nothing to keep supporting.
Never lose a punch
The link from a gym’s office laptop to a cloud service is not reliable, so this has the same problem an offline till has, in a different shape: events happen in the physical world before the server hears about them.
Two rules carry the design.
Progress is tracked locally, and the device is never wiped. These terminals offer a “read the logs, then clear them” operation, and clearing is the intuitive way to avoid re-reading. It is also the way to permanently lose a day of attendance the moment an upload fails after the clear succeeded. The agent instead remembers how far it has read, in its own records, and re-reads from there. The device’s memory is left as a safety net.
Each event’s identity is derived, not invented. A punch is identified by the device, the person and the time it happened. Re-sending a batch after a timeout produces the same identities, so the server absorbs it as a no-op rather than doubling someone’s attendance.
That last point matters more than it looks: attendance flows into payroll, and a duplicated punch is a payroll error somebody eventually has to argue about with a member of staff. Together the two rules mean a dropped connection delays attendance without losing or duplicating it.
The device clock lies
Every terminal keeps its own clock, and those clocks drift, get reset by power cuts, and occasionally get set wrong by a well-meaning member of staff.
The agent syncs the device’s time when it connects, but it never rewrites history: every record carries both the device’s timestamp and the time the server received it. Payroll rules choose deliberately between them, and when the two disagree badly the discrepancy is visible rather than silently resolved.
Quietly “correcting” a timestamp is the kind of helpfulness that becomes a dispute about someone’s wages six weeks later, with no record of what the device actually said. Keeping both numbers means the business can answer the question instead of guessing.
Deploying to a machine you do not own
This is the part that made it an operations project rather than an integration script. The agent runs on hardware in someone else’s building, on a network you cannot see, maintained by people whose job is running a gym.
That constraint drove every packaging decision:
- One-command install.
pip install zkteco-lan-agentand a config file, not a repository clone and a page of build steps. - Runs on what is already there. An office laptop that stays on, or a Raspberry Pi for sites that would rather not depend on a laptop. Both are cheap enough that a spare can sit in a drawer.
- Supervised, not started by hand. Installed as a background service so it survives reboots and power cuts - because it will get both.
- Self-healing on the two failures that actually happen. The device goes unreachable when someone restarts the network switch; the uplink goes away when the internet does. Both recover on their own, rather than generating an alert asking a gym manager to intervene.
- Diagnosable from a distance. The agent reports its own health and progress upstream, so “is the Dhanmondi branch syncing?” is answered from a dashboard rather than a phone call to a receptionist.
Each device is identified by serial number and mapped to a branch on the server, so one agent can serve every terminal on a site, and moving a device between branches is a settings change rather than a site visit.
Security posture
A program running on a customer’s own network, holding a credential to a shared cloud platform, deserves a moment of paranoia.
The credential is scoped to one customer and to attendance only - it cannot read member records, cannot change anything else, and cannot reach another customer’s data even if the machine it runs on is compromised. Traffic goes outbound only, over an encrypted connection, so installing it needs no firewall changes and opens no new way into the customer’s network. That is also what makes it an easy “yes” for an IT-conscious client.
The result
- A hardware purchase disappeared from the sales conversation - existing terminals work on day one.
- Attendance survives outages rather than vanishing with them, which keeps payroll defensible.
- A re-send can never inflate someone’s hours, removing an entire category of payroll dispute.
- The server never changed, so supporting old hardware adds no ongoing maintenance burden.
- Non-technical sites run it unattended, and head office can see remotely whether a branch is healthy.
What it demonstrates
The unglamorous integration work that decides whether a product is buyable. A bridge that leaves the server untouched, guarantees on an unreliable link, an explicit position on untrusted clocks, and packaging that lets non-technical people run a background service on hardware nobody is watching.