{"id":37574108,"url":"https://github.com/codefoster/waterrower","last_synced_at":"2026-01-16T09:28:03.375Z","repository":{"id":8775542,"uuid":"59718028","full_name":"codefoster/waterrower","owner":"codefoster","description":"A library for communicating with your WaterRower","archived":false,"fork":false,"pushed_at":"2022-07-06T18:42:29.000Z","size":1072,"stargazers_count":73,"open_issues_count":10,"forks_count":18,"subscribers_count":19,"default_branch":"master","last_synced_at":"2025-08-09T18:54:47.843Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/codefoster.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2016-05-26T03:50:16.000Z","updated_at":"2024-11-13T10:12:29.000Z","dependencies_parsed_at":"2022-08-07T04:16:46.226Z","dependency_job_id":null,"html_url":"https://github.com/codefoster/waterrower","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/codefoster/waterrower","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codefoster%2Fwaterrower","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codefoster%2Fwaterrower/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codefoster%2Fwaterrower/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codefoster%2Fwaterrower/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/codefoster","download_url":"https://codeload.github.com/codefoster/waterrower/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codefoster%2Fwaterrower/sbom","scorecard":{"id":296389,"data":{"date":"2025-08-11","repo":{"name":"github.com/codefoster/waterrower","commit":"8baa992e3f609a293de1eae32d0c066e3d4631d7"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":1.7,"checks":[{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"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":"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":"Dangerous-Workflow","score":-1,"reason":"no workflows found","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":"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":"Code-Review","score":0,"reason":"Found 1/23 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":"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":"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":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"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":"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":"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":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE.md:0","Info: FSF or OSI recognized license: Apache License 2.0: LICENSE.md:0"],"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"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 8 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Vulnerabilities","score":0,"reason":"22 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: GHSA-93q8-gq69-wqmw","Warn: Project is vulnerable to: GHSA-pp7h-53gx-mx7r","Warn: Project is vulnerable to: GHSA-v6h2-p8h4-qcjw","Warn: Project is vulnerable to: GHSA-gxpj-cx7g-858c","Warn: Project is vulnerable to: GHSA-4q6p-r6v2-jvc5","Warn: Project is vulnerable to: GHSA-qqgx-2p2h-9c37","Warn: Project is vulnerable to: GHSA-6c8f-qphg-qjgp","Warn: Project is vulnerable to: GHSA-p6mc-m468-83gw","Warn: Project is vulnerable to: GHSA-29mw-wpgm-hmr9","Warn: Project is vulnerable to: GHSA-35jh-r3h4-6jhm","Warn: Project is vulnerable to: GHSA-f8q6-p94x-37v3","Warn: Project is vulnerable to: GHSA-vh95-rmgr-6w4m","Warn: Project is vulnerable to: GHSA-xvch-5gv4-984h","Warn: Project is vulnerable to: GHSA-8hfj-j24r-96c4","Warn: Project is vulnerable to: GHSA-wc69-rhjr-hc9g","Warn: Project is vulnerable to: GHSA-g6ww-v8xp-vmwg","Warn: Project is vulnerable to: GHSA-c2qf-rxjj-qqgw","Warn: Project is vulnerable to: GHSA-jv35-xqg7-f92r","Warn: Project is vulnerable to: GHSA-4jqc-8m5r-9rpr","Warn: Project is vulnerable to: GHSA-wpg7-2c88-r8xv","Warn: Project is vulnerable to: GHSA-pq67-2wwv-3xjx","Warn: Project is vulnerable to: GHSA-8cj5-5rvv-wf4v"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-17T19:36:45.054Z","repository_id":8775542,"created_at":"2025-08-17T19:36:45.054Z","updated_at":"2025-08-17T19:36:45.054Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28478049,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-16T06:30:42.265Z","status":"ssl_error","status_checked_at":"2026-01-16T06:30:16.248Z","response_time":107,"last_error":"SSL_read: 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-16T09:28:03.264Z","updated_at":"2026-01-16T09:28:03.369Z","avatar_url":"https://github.com/codefoster.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\nTalk to your WaterRower!\n\nThis project is being actively developed. It is stable and working as it is, but there's still more that can be added for more communication with the WaterRower device. Please jump in with contributions. Pull requests are welcome.\n\n`waterrower` gives you two ways to subscribe to data changes - the EventEmitter pattern and Rx.js. The former is very familiar and works fine. The latter allows you to do some interesting things since data changes are an Rx stream. If you haven't worked with Rx.js yet, don't worry. As you can see in the example below, it's very easy.\n\nThis project was initially created to support the [Waterbug](http://github.com/codefoster/waterbug) project.\n\nKeep in mind that version 0.2.0 introduced some significant changes from 0.1.0 which is what's published in npm as of now. It's much better. Instead of certain rowing session values (i.e. distance) being available by calling specific functions, any session values (that are defined in the datapoints.ts file) can be requested. I'll explain more in context below.\n\n## Installation\n\nIn your terminal...\n```\nnpm install waterrower\n\n```\nIn your project code...\n```\nimport { WaterRower } from 'waterrower';\nlet waterrower = new WaterRower();\n```\n\n## Example Usage\n\nHere's the simplest case...\n```\nimport { WaterRower } from 'waterrower';\nlet waterrower = new WaterRower();\nwaterrower.on('data', d =\u003e {\n    // access the value that just changed using d\n    // or access any of the other datapoints using waterrower.readDataPoint('\u003cdatapointName\u003e');\n});\n```\nIn this simple example we've left the port off and let the waterrower module discover it for us.\n\nIf you want to do setup stuff to the WaterRower such as reset the console and then start a distance workout, you need to do that _after_ the unit is initialized. Use the following event...\n```\nwaterrower.on('initialized', () =\u003e {\n    waterrower.reset();\n});\n```\n\nIf you would prefer, you can directly access the observable properties available on the module. `waterrower.reads$` observes all serial messages that come from the WaterRower. `waterrower.datapoints$` is a filter and map of `reads$` and includes only the datapoints (memory location values).\n\nHere's how you would do that...\n```\nimport { WaterRower } from 'waterrower';\nimport { Observable } from 'rxjs/Rx';\nlet waterrower = new WaterRower();\n\n// respond to the waterrower sending data\nwaterrower.datapoints$.subscribe(d =\u003e {\n    // access the value that just changed using d\n    // or access any of the other datapoints using waterrower.readDataPoint('\u003cdatapointName\u003e');\n});\n```\n\nIf you want to access all of the PING messages that come from the WaterRower for some reason, you could use...\n```\nwaterrower.reads$\n    .filter(r =\u003e r.type === 'ping')\n    .subscribe(r =\u003e {\n        // ping\n    });\n```\n\nNote that you can find all of the available \"types\" in the `types.ts` file. \n\n## API Reference\n\n\u003e The members in this API Reference are depicted using TypeScript to give you an idea of the parameter types and return types. Take, for example...\n\u003e \n\u003e ```\n\u003e f(p1?: string | string[]): number \n\u003e ```\n\u003e \n\u003e ...where function `f` receives an optional (depicted by the `?`) parameter `p1` with a type of either `string` or `string[]` and has a return value of type `number`  \n\n### `WaterRower()` (constructor)\nTakes an `options` parameter that must at minimum have a portName. Options are specified in `WaterRowerOptions` (documented below).\n\n### `reads$`\nThis is the Rx stream that fires whenever the WaterRower sends any serial message. You can check the pdf documentation distributed by WaterRower to see all valid messages. Read messages have a type and are thus easy to filter. They also have a value property with the data portion of the message.\n     \n### `datapoints$`\nThis is simply a filter of the reads$ stream containing only the datapoint messages.\n\nSee example `datapoints$.subscribe` above.\n\n### `on(): void`\n`on()` is the typical means of subscribing to node EventEmitter events. Valid events for waterrower are...\n\n`on('initiazed', d =\u003e {...})` fires when the port connection to the WaterRower has been established, an initialization message has been sent, and the unit has responded with its \"hardware type\" message (`_WR_`). \n\n`on('data', d =\u003e {...})` fires whenever a datapoint value changes. When the rower goes 1 more meter and his total distance changes from 237 to 238, for instance, this event will fire. \n\n`on('close', d =\u003e {...})` fires when the WaterRower module is stopped. \n\n### `reset(): void`\nSend a signal to the WaterRower to reset. You'll hear your WaterRower beep and the numbers will flash ready for activity to begin.\n\n### `requestDataPoints(points?: string | string[]): any`\nAsks the WaterRower to send the value for a the datapoint with the given name. The returned value happens in a completely separate serial message, so it is not returned by this function. Rather, after issuing this request, you would use `readDataPoint` to get the new value. Note that you should only need to do this if the `options.refreshRate` is set to `0` and thus the module is not configured to poll the waterrower on a regular interval. When the module is configured (as it is by default) with an options.refreshRate value \u003e 0, you should only ever need to `readDataPoint` whenever you are not subscribed.\n\n### `readDataPoints(points?: string | string[]): any`\nGets the current value of a single datapoint, an array of datapoints, or all datapoints depending on what you pass in. This does not request the latest value from the waterrower, but will be current if the module is refreshing (`options.refreshRate \u003e 0`). If it is not, then the `requestDataPoints` method should be called prior to this and a short time waited before reading.\n\nTo read a single data, simply pass in the datapoint name as a string...\n\n```\nwaterrower.readDataPoints('distance');\n```\n\nTo read multiple datapoints, pass in an array of datapoint names as an array of strings...\n\n```\nwaterrower.readDataPoints(['distance','total_kcal']);\n```\n\nTo read all datapoints, just leave the value blank...\n\n```\nwaterrower.readDataPoints();\n```\n\n### `defineDistanceWorkout(distance: number, units: Units)`\nInitiates a distance workout on the WaterRower. Accepts `distance` and `units` parameters (units defaults to Meters).\n\n### `defineDurationWorkout(seconds: number)`\nInitiates a duration workout on the WaterRower. Accepts the number of seconds for the new workout. \n\n### `displaySetDistance(units: Units)`\nChange the distance display units. See Units for possible values.\n\n### `displaySetIntensity(option: IntensityDisplayOptions)`\nChange the intensity display. See IntensityDisplayOptions for possible values.\n\n### `displaySetAverageIntensity(option: AverageIntensityDisplayOptions)`\nChange the average intensity display. See AverageIntensityDisplayOptions for possible values.\n\n### Recording and Playing Back Sessions\nwaterrower offers the ability to record and playback rowing sessions.\n\nThese recordings are a file containing all serial messages to cross the wire. For example...\n\n```\n{\"time\":1468559128188,\"type\":\"hardwaretype\",\"data\":\"_WR_\\r\"}\n{\"time\":1468559128386,\"type\":\"datapoint\",\"data\":\"IDD0550007\\r\"}\n{\"time\":1468559128397,\"type\":\"datapoint\",\"data\":\"IDD0570007\\r\"}\n{\"time\":1468559128402,\"type\":\"datapoint\",\"data\":\"IDD0810007\\r\"}\n```\n\nThe project contains a `data` folder with one 30 minute rowing session that you can play back. This essentially allows you to simulate a rowing session for development purposes.\n\nTo start the simulation data, use...\n\n```\nimport { WaterRower } from 'waterrower';\nlet waterrower = new WaterRower();\nwaterrower.startSimulation();\nwaterrower.on('data', d =\u003e {\n    console.log(d);\n});\n```\n`startRecording(name?: string)` starts recording all serial messages coming _from_ the rower to a file called `name` in the data directory. If name is not provided, then a date/time stamp will be used for the file name.\n\n`stopRecording(): void` stops the current recording.\n\n`getRecordings(): string[]` returns a list of all recorded sessions from the data directory.\n\n`playRecording(name?: string): void` starts playing back a recorded session by `name`. If no name is provided then it will defaults to `simulationdata` - the name of the 30-minute rowing session that comes with waterrower.\n\n`startSimulation(): void` is a shortcut method that is identical to calling `playRecording()`.\n\n### WaterRowerOptions Interface\nThese are the options that you can pass to the WaterRower constructor. All of the options are optional.\n\n```\nlet waterrower = new WaterRower({\n  portName:'/dev/ttyACM0', //or perhaps 'COM6'\n  baudRate:19200,\n  refreshRate:200,\n  dataDirectory:'data',\n  datapoints:['distance','total_kcal']\n})\n```\n\nIf `portName` is omitted then the waterrower module will automatically attempt to discover the port that the WaterRower is on.\n\n`baudRate` and `refreshRate` have defaults and are optional.\n\n`dataDirectory` is a string representing the path (relative to waterrower's root directory) of the directory where session recordings should be saved.\n\n`datapoints` is either a string representing a single datapoint or an array of strings representing all of the datapoints that you want waterrower to request every refresh. Keeping this list as trim as possible will reduce the number of messages that are generated while rowing, so it's recommended you only include the data you're interested in. \n\n### IntensityDisplayOptions Enum\nThis enum defines the possible values you can send to the `displaySetIntensity` method.\n```\nthis.displaySetIntensity(IntensityDisplayOptions.MetersPerSecond);\n```\n\n### AverageIntensityDisplayOptions Enum\nThis enum defines the possible values you can send to the `displaySetAverageIntensity` method.\n\nPossible values are: `AverageMetersPerSecond`,`AverageMPH`,`_500m`, and `_2km`\n\n```\nthis.displaySetIntensity(AverageIntensityDisplayOptions.AverageMetersPerSecond);\n```\n\n### Units Enum\nThis enum defines the possible values you can send to the `displaySetAverageIntensity` method.\n\nPossible values are: `Meters`, `Miles`, `Kilometers`, and `Strokes`\n\nTo use the Units, you have to import the Units interface from the waterrower module.\n```\nimport { WaterRower, Units } from 'waterrower';\n...\nthis.defineDistanceWorkout(500, Units.Meters);\n```\n\n### DataPoint Interface\nThe DataPoint interface constitutes a type for the objects in the `datapoints.ts` file and the type returned in the `datapoints$` stream.\n\n```\nexport interface DataPoint {\n    name?: string,\n    address: string,\n    length: string,\n    value: any\n}\n```\n\n## Known Issues\n\nSee the [issues](http://github.com/codefoster/waterrower/issues) on GitHub for a complete list of issues, and feel free to submit some yourself either for bugs or feature requests.\n\n\n## License\n\nApache 2.0\n\n## Contributors\nBig thanks to [redoPop](https://github.com/redoPop) for the recent contributions to the waterrower project!\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcodefoster%2Fwaterrower","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcodefoster%2Fwaterrower","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcodefoster%2Fwaterrower/lists"}