{"id":41830376,"url":"https://github.com/cafjs/caf_iot","last_synced_at":"2026-01-25T08:36:34.397Z","repository":{"id":6584006,"uuid":"7826354","full_name":"cafjs/caf_iot","owner":"cafjs","description":"IoT platform that runs on the device and pairs with a Cloud Assistant.","archived":false,"fork":false,"pushed_at":"2023-04-18T22:34:09.000Z","size":288,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2025-10-13T10:51:47.989Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/cafjs.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGES.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE-2.0.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2013-01-25T19:00:04.000Z","updated_at":"2023-01-16T11:25:58.000Z","dependencies_parsed_at":"2023-01-13T14:02:40.546Z","dependency_job_id":null,"html_url":"https://github.com/cafjs/caf_iot","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/cafjs/caf_iot","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cafjs%2Fcaf_iot","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cafjs%2Fcaf_iot/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cafjs%2Fcaf_iot/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cafjs%2Fcaf_iot/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cafjs","download_url":"https://codeload.github.com/cafjs/caf_iot/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cafjs%2Fcaf_iot/sbom","scorecard":{"id":261767,"data":{"date":"2025-08-11","repo":{"name":"github.com/cafjs/caf_iot","commit":"aeb822964432346216e8c58d18b3b234cce1d3b2"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3.4,"checks":[{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Code-Review","score":0,"reason":"Found 0/30 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"SAST","score":0,"reason":"no SAST tool detected","details":["Warn: no pull requests merged into dev branch"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Warn: no topLevel permission defined: .github/workflows/push.yml:1","Info: no jobLevel write permissions found"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Pinned-Dependencies","score":0,"reason":"dependency not pinned by hash detected -- score normalized to 0","details":["Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/push.yml:18: update your workflow using https://app.stepsecurity.io/secureworkflow/cafjs/caf_iot/push.yml/master?enable=pin","Info:   0 out of   1 GitHub-owned GitHubAction dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":9,"reason":"license file detected","details":["Info: project has a license file: LICENSE-2.0.txt:0","Warn: project license file does not contain an FSF or OSI license."],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}}]},"last_synced_at":"2025-08-17T10:58:37.732Z","repository_id":6584006,"created_at":"2025-08-17T10:58:37.732Z","updated_at":"2025-08-17T10:58:37.732Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28749599,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-25T08:31:04.260Z","status":"ssl_error","status_checked_at":"2026-01-25T08:30:28.859Z","response_time":113,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-01-25T08:36:33.868Z","updated_at":"2026-01-25T08:36:34.379Z","avatar_url":"https://github.com/cafjs.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Caf.js\n\nCo-design cloud assistants with your web app and IoT devices.\n\nSee https://www.cafjs.com\n\n## Platform for IoT Devices\n\n[![Build Status](https://github.com/cafjs/caf_iot/actions/workflows/push.yml/badge.svg)](https://github.com/cafjs/caf_iot/actions/workflows/push.yml)\n\nIoT platform that runs on the device and pairs with a CA.\n\nThe IoT device programming model is very similar to programming a CA (see {@link external:caf_ca}). In fact, most of the code is reused.\n\nThings similar to the cloud implementation. Method execution is serialized by a queue, no global state, similar plugins, and transactional changes to local state can be rolled backed on abort.\n\nHowever, we do not checkpoint state, and there is only one \"CA\", i.e., the device. We are not that concerned about long term consistency either, since we reset the device every reboot; therefore, some plugins may not delay external actions, improving responsiveness.\n\nWe have also simplified framework methods and config files. For instance, the file `iot.json` describes both the framework, i.e., `framework.json`, and  the device, i.e., `ca.json`, component hierarchies. Also, the naming of methods is more \"Arduino-friendly\".\n\n### Hello World (see `examples/helloworld`)\n\n```\nexports.methods = {\n    async __iot_setup__() {\n        this.state.counter = this.toCloud.get('counter') || 0;\n        return [];\n    },\n    async __iot_loop__() {\n        var msg = this.fromCloud.get('msg') || 'Counter:';\n        this.$.log \u0026\u0026 this.$.log.debug(msg + this.state.counter);\n        this.state.counter = this.state.counter + 1;\n        this.toCloud.set('counter', this.state.counter);\n        return [];\n    }\n};\n```\n\nDefines two methods called by the IoT framework:\n\n* `__iot_setup__`: initializes the state of the device everytime it resets.\n* `__iot_loop__`: similar to a `__ca_pulse__` CA method, it executes periodically. See {@link module:caf_iot/plug_iot_handler} for details.\n\nDevice data is described with:\n\n* `this.state`: similar to a CA's `this.state` but not checkpointed.\n* `this.scratch`: similar to a CA's `this.scratch`.\n* `this.toCloud`: A `SharedMap` (see {@link external:caf_sharing}) written by the device and read by its CA. When the device resets, it also downloads the latest contents of this map that reached the cloud.\n* `this.fromCloud`: A `SharedMap` written by the CA and read by the device. This is the main mechanism to configure the device or trigger actions.\n\nIn the previous example `this.toCloud` has two purposes:\n\n* checkpoint the last value of `counter`, so that it is remembered after a reset.\n* communicate this value to the CA, making it visible to external clients (see `examples/helloworld/client.js`).\n\nThe purpose of `this.fromCloud` is to allow a client to indirectly modify the behavior of the device by communicating with its CA.\n\nThe CA impersonates the device, enabling seamless interaction when the device is offline, or behind a firewall.\n\n### Hello Cron (see `examples/hellocron`)\n\nSimilar to CA plugins, device plugins are exposed to application code with proxies in `this.$`.\n\nAn interesting plugin is `cron` (see {@link module:caf_iot/proxy_iot_cron}), which allows calls to arbitrary methods at regular intervals.\n\n```\nexports.methods = {\n    async __iot_setup__() {\n        this.state.counter = this.toCloud.get('counter') || 0;\n        this.$.cron.addCron('helloCron', 'greetings', ['Hello:'], 2000);\n        this.$.cron.addCron('byeCron', 'greetings', ['Bye:'], 3000);\n        return [];\n    },\n    async greetings(greet) {\n        const now = (new Date()).getTime();\n        this.$.log \u0026\u0026 this.$.log.debug(greet + now);\n        return [];\n    },\n    ...\n};\n```\n\n#### How are errors and exceptions handled?\n\nThe default behavior is rather crude, just log and do a full reset.\n\nIt is recommended to override that behavior by adding a method `__iot_error__`.\n\nThis method could avoid the reset by **not** propagating the error in the callback, see `__iot_error__` in {@link module:caf_iot/plug_iot_handler}  and `examples/hellocron/iot/iot_methods.js`.\n\n### Hello Bundles (see `examples/hellobundle`)\n\nThe CA can invoke device methods by using timed bundles of commands.\n\n`Caf.js` synchronizes device clocks with the cloud, coordinating **soft** real-time actions across the globe with UTC time. Given a few seconds to propagate commands,  millions of devices could blink within a hundred milliseconds of each other.\n\nWhy bundles and not just separate commands?\n\nSafety. Think of controlling a drone. One command to dive as fast as it can. Second command to gracefully recover. Lost network connection between them. Oops...\n\nIf we bundle commands, both are cached in the drone before anything happens. If execution is based on UTC time, not on arrival time, we can pipeline bundles, ensuring smooth movement. And the CA can keep generating these bundles based on a higher goal. See {@link module:caf_iot/plug_iot_bundles} for details.\n\nExtra time is added to bundles for network propagation but, when a bundle arrives to the device late, it gets ignored. The CA can detect that by monitoring responses in `this.state.acks`; this is an array of max size `this.state.maxAcks`, and elements of type:\n\n    {result: boolean, index: number}\n\nwhere:\n\n* `result`: `False` if the bundle was late, `True` otherwise.\n* `index`: An identifier for the bundle. It matches the one previously returned by {@link module:caf_iot/ca/proxy_iot#sendBundle}.\n\nLet's look at an example.\n\nThe device code defines three simple commands for our \"drone\": `up`, `down`, or take a recovery action if we lose connectivity.\n\n```\nexports.methods = {\n...\n    async down(speed) {\n        const now = (new Date()).getTime();\n        this.$.log \u0026\u0026 this.$.log.debug('Down:' +  now + ' speed: ' + speed);\n        return [];\n    },\n    async up(speed) {\n        const now = (new Date()).getTime();\n        this.$.log \u0026\u0026 this.$.log.debug('Up:  ' +  now + ' speed: ' + speed);\n        return [];\n    },\n    async recover(msg) {\n        const now = (new Date()).getTime();\n        this.$.log \u0026\u0026 this.$.log.debug('RECOVERING:' +  now + ' msg: ' + msg);\n        return [];\n    },\n};\n```\n\nThe CA code is a bit more interesting:\n\n```\nvar MARGIN=100;\nexports.methods = {\n    async __ca_init__() {\n        this.state.maxAcks = 1;\n        return [];\n    },\n    async __ca_pulse__() {\n        if ((this.state.acks \u0026\u0026 (this.state.acks.length \u003e 0) \u0026\u0026\n             (!this.state.acks[0].result))) {\n            this.$.log \u0026\u0026 this.$.log.debug('Last bundle was late');\n        }\n        var bundle = this.$.iot.newBundle(MARGIN);\n        bundle.down(0, [1]).up(300, [1]).recover(5000, ['go home']);\n        this.$.iot.sendBundle(bundle);\n        // `notify` improves responsiveness.\n        this.$.session.notify(['new bundle'], 'iot');\n        return [];\n    },\n  ...\n};\n```\n\nIf you are wondering how `bundle` gets methods `down`, `up`, and `recover`, the framework instrospects the device code, and generates them at run time. *I love JavaScript*.\n\n`__ca_pulse__` gets called every `interval` msec. If `interval` is less than `5000`, we have two cases:\n\n* Normal case: the next bundle starts before the recovery action of the previous bundle. Only one bundle can be active, and the device skips recovery.\n\n* Network partition for more than five seconds: no new bundles, and the device triggers recovery.\n\nWhy `notify` after `sendBundle`? To save energy and bandwidth the device typically syncs with the cloud every few seconds, and we are providing a margin of only 100 msec to execute the bundle. If the device is currently connected, `notify` uses the websocket to force the device to sync.\n\n\n\n### Hello Cloud (see `examples/hellocloud`)\n\nThe device can also call CA methods.\n\nThe `cloud` plugin provides a standard client `Session` (see {@link external:caf_cli}). It can also receive session notifications (see {@link external:caf_session}), and process them as conventional method calls.\n\nNotifications improve responsiveness, because otherwise the device waits until the next `__iot_loop__` to synchronize with the cloud.\n\nFor example:\n\n```\nexports.methods = {\n    async __iot_setup__() {\n        this.$.cloud.registerHandler((msg) =\u003e {\n            var args = this.$.cloud.getMethodArgs(msg);\n            this.$.queue.process('greetings', args);\n        });\n        return [];\n    },\n    async greetings(msg) {\n        const now = (new Date()).getTime();\n        this.$.log \u0026\u0026 this.$.log.debug(msg + now);\n        try {\n            const value = await this.$.cloud.cli.getCounter().getPromise();\n            this.$.log \u0026\u0026 this.$.log.debug('Got ' + value);\n            return [];\n        } catch (err) {\n            return [err];\n        }\n    },\n    ...\n};\n```\n\nEvery time the CA notifies the device, a method call for `greetings` gets queued,  and eventually, that method will call back the CA, reading its counter.\n\nThe default session identifier for a device is `iot`, but it can be changed with the property `session` (see {@link module:caf_iot/plug_iot_cloud}).\n\n### Much more...\n\nWe have a few RPi plugins in `caf/extra` that do real IoT stuff. Controlling `gpio` pins, managing external RTC/power boards, distance sensors...\n\nThe long term strategy is **not** to duplicate the great work of other JavaScript IoT libraries supporting zillions of sensors/actuators/devices. Instead, wrap these libraries with local plugins, and focus on cloud/client integration. This is consistent with our web client strategy, i.e., integrating with other libraries such as React/Redux.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcafjs%2Fcaf_iot","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcafjs%2Fcaf_iot","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcafjs%2Fcaf_iot/lists"}