From 3753cab46c8f1bfcb03a350a8a28cb0d0b42ffc9 Mon Sep 17 00:00:00 2001
From: Naomi Pentrel <5212232+npentrel@users.noreply.github.com>
Date: Mon, 6 Oct 2025 10:29:11 +0200
Subject: [PATCH 1/5] Update scheduled-jobs.md
---
docs/manage/software/scheduled-jobs.md | 12 +++++++-----
1 file changed, 7 insertions(+), 5 deletions(-)
diff --git a/docs/manage/software/scheduled-jobs.md b/docs/manage/software/scheduled-jobs.md
index 6d2013990d..83809ba1a3 100644
--- a/docs/manage/software/scheduled-jobs.md
+++ b/docs/manage/software/scheduled-jobs.md
@@ -15,9 +15,11 @@ date: "2025-06-17"
cost: "0"
---
-Viam's machine job scheduler allows you to configure automated jobs that run on your machines at specified intervals. This enables you to automate routine tasks such as data collection, sensor readings, maintenance operations, and system checks.
+Viam's machine job scheduler allows you to configure automated jobs that run on your machines at specified intervals.
+This enables you to automate routine tasks such as data collection, sensor readings, maintenance operations, and system checks.
-The job scheduler is built into `viam-server` and executes configured jobs according to their specified schedules. Each job targets a specific resource on your machine and calls a designated method at the scheduled intervals.
+The job scheduler is built into `viam-server` and executes configured jobs according to their specified schedules.
+Each job targets a specific resource on your machine and calls a designated component or service API method at the scheduled intervals.
## Configure a job
@@ -31,7 +33,7 @@ The job scheduler is built into `viam-server` and executes configured jobs accor
1. For the **Schedule**, select **Interval** or **Cron** and specify the interval the job should be run in.
-1. For **Job**, select a **Resource**, and a **method**.
+1. For **Job**, select a **Resource**, and a component or service API **method**.
For the `DoCommand` method also specify the command parameters.
{{% /tab %}}
@@ -143,7 +145,7 @@ Jobs are configured as part of your machine's configuration. Each job requires t
| `name` | string | **Required** | Unique identifier for the job within the machine. |
| `schedule` | string | **Required** | Schedule specification using unix-cron format or Golang duration. Accepts
- Unix-cron expressions for time-based scheduling:
- `"0 */6 * * *"`: Every 6 hours
- `"0 0 * * 0"`: Every Sunday at midnight
- `"*/15 * * * *"`: Every 15 minutes
- `"0 9 * * 1-5"`: Every weekday at 9 AM
- Golang duration strings for interval-based scheduling:
- `"5m"`: Every 5 minutes
- `"1h"`: Every hour
- `"30s"`: Every 30 seconds
- `"24h"`: Every 24 hours
Job schedules are evaluated in the machine's local timezone. |
| `resource` | string | **Required** | Name of the target resource (component or service). |
-| `method` | string | **Required** | gRPC method to call on the target resource. |
+| `method` | string | **Required** | Component or service API method to call on the target resource. |
| `command` | object | Optional | Command parameters for `DoCommand` operations. |
## Monitoring and troubleshooting
@@ -163,7 +165,7 @@ Monitor job execution through `viam-server` logs. Look for `rdk.job_manager`:
- If a unix-cron job is scheduled to start but a previous invocation is still running, the job will be skipped. The job will next run once the previous invocation has finished running and the next scheduled time is reached.
- If a Golang duration job is scheduled to run but a previous invocation is still running, the next invocation will not run until the previous invocation finishes, at which point it will run immediately.
- Jobs can only support using arguments for `DoCommand` method.
- Other methods are only supported if they have no required arguments.
+ All other componetn and service API methods are only supported if they have no required arguments.
To avoid this limitation, a generic service can be written as a module to encapsulate API calls in a DoCommand API.
- Jobs run locally on each machine and are not coordinated across multiple machines.
- Job execution depends on `viam-server` running.
From 3f367f82e7f66ebb3cf51142cc7a4d945855cb60 Mon Sep 17 00:00:00 2001
From: Naomi Pentrel <5212232+npentrel@users.noreply.github.com>
Date: Mon, 6 Oct 2025 10:37:37 +0200
Subject: [PATCH 2/5] Update scheduled-jobs.md
---
docs/manage/software/scheduled-jobs.md | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/docs/manage/software/scheduled-jobs.md b/docs/manage/software/scheduled-jobs.md
index 83809ba1a3..26c93e707f 100644
--- a/docs/manage/software/scheduled-jobs.md
+++ b/docs/manage/software/scheduled-jobs.md
@@ -164,9 +164,9 @@ Monitor job execution through `viam-server` logs. Look for `rdk.job_manager`:
- Jobs only run when `viam-server` is running.
- If a unix-cron job is scheduled to start but a previous invocation is still running, the job will be skipped. The job will next run once the previous invocation has finished running and the next scheduled time is reached.
- If a Golang duration job is scheduled to run but a previous invocation is still running, the next invocation will not run until the previous invocation finishes, at which point it will run immediately.
-- Jobs can only support using arguments for `DoCommand` method.
- All other componetn and service API methods are only supported if they have no required arguments.
- To avoid this limitation, a generic service can be written as a module to encapsulate API calls in a DoCommand API.
+- `DoCommand` is currently the only supported component and service API method that you can invoke with arguments.
+ Aside from `DoCommand`, Jobs currently only support component and service API methods that do not require arguments.
+ To avoid this limitation, you create a module and encapsulate API calls in a DoCommand API call.
- Jobs run locally on each machine and are not coordinated across multiple machines.
- Job execution depends on `viam-server` running.
- Failed jobs do not retry.
From cf880aec5c275f69798d19268bdffa6085a88f84 Mon Sep 17 00:00:00 2001
From: Naomi Pentrel <5212232+npentrel@users.noreply.github.com>
Date: Mon, 6 Oct 2025 10:50:12 +0200
Subject: [PATCH 3/5] on_loop to _on_loop
---
docs/operate/hello-world/building.md | 8 ++++----
docs/operate/modules/control-logic.md | 20 ++++++++++----------
2 files changed, 14 insertions(+), 14 deletions(-)
diff --git a/docs/operate/hello-world/building.md b/docs/operate/hello-world/building.md
index 45c8650efa..e4e242b4d1 100644
--- a/docs/operate/hello-world/building.md
+++ b/docs/operate/hello-world/building.md
@@ -339,7 +339,7 @@ For a step-by-step guide, see [Run control logic](/operate/modules/control-logic
The control logic for the project might look like this:
```python {class="line-numbers linkable-line-numbers" data-line=""}
-async def sand_at(motion, world_state, arm, x_min, x_max, y_min, y_max):
+async def _sand_at(motion, world_state, arm, x_min, x_max, y_min, y_max):
# simple sanding logic that moves the arm from x_min, y_min to x_max, y_max
start_pose = translate_x_y_to_coord(x_min, y_min)
end_pose = translate_x_y_to_coord(x_max, y_max)
@@ -350,7 +350,7 @@ async def sand_at(motion, world_state, arm, x_min, x_max, y_min, y_max):
return True
-async def on_loop(self):
+async def _on_loop(self):
self.logger.info("Executing control logic")
# Check vision service for detections of the color of the pencil markings
# on the wood
@@ -358,7 +358,7 @@ async def on_loop(self):
self.camera_name)
for d in detections:
- await sand_at(
+ await _sand_at(
self.motion_service,
self.world_state,
self.arm_name,
@@ -379,7 +379,7 @@ async def do_command(
result = {key: False for key in command.keys()}
for name, args in command.items():
if name == "action" and args == "run_control_logic":
- await self.on_loop()
+ await self._on_loop()
result[name] = True
return result
```
diff --git a/docs/operate/modules/control-logic.md b/docs/operate/modules/control-logic.md
index 988f7aba4c..869ad1a778 100644
--- a/docs/operate/modules/control-logic.md
+++ b/docs/operate/modules/control-logic.md
@@ -109,12 +109,12 @@ To add the control logic, use the `DoCommand()` method.
The method accepts arbitrary JSON objects as commands.
The following code checks the command object and for the `start` command it sets the `running` parameter to `True` and for the `stop` command to `False`.
-A third command, `on_loop`, results in the `on_loop()` method being called, but only if `running` is `True`.
+A third command, `on_loop`, results in the `_on_loop()` method being called, but only if `running` is `True`.
-The `on_loop()` method increments the counter.
+The `_on_loop()` method increments the counter.
```python
- async def on_loop(self):
+ async def _on_loop(self):
try:
self.logger.info("Executing control logic")
self.counter += 1
@@ -140,7 +140,7 @@ The `on_loop()` method increments the counter.
result[name] = True
if name == "action" and args == "on_loop":
if self.running:
- await self.on_loop()
+ await self._on_loop()
result[name] = True
result["counter"] = self.counter
return result
@@ -199,7 +199,7 @@ class ControlLogic(Generic, EasyResource):
self.running = False
return super().reconfigure(config, dependencies)
- async def on_loop(self):
+ async def _on_loop(self):
try:
self.logger.info("Executing control logic")
self.counter += 1
@@ -225,7 +225,7 @@ class ControlLogic(Generic, EasyResource):
result[name] = True
if name == "action" and args == "on_loop":
if self.running:
- await self.on_loop()
+ await self._on_loop()
result[name] = True
result["counter"] = self.counter
return result
@@ -334,7 +334,7 @@ Update your logic in the `do_command` method to use the board:
result[name] = True
if name == "action" and args == "on_loop":
if self.running:
- await self.on_loop()
+ await self._on_loop()
result[name] = True
result["counter"] = self.counter
return result
@@ -412,7 +412,7 @@ class ControlLogic(Generic, EasyResource):
self.running = False
return super().reconfigure(config, dependencies)
- async def on_loop(self):
+ async def _on_loop(self):
try:
self.logger.info("Executing control logic")
self.counter += 1
@@ -442,7 +442,7 @@ class ControlLogic(Generic, EasyResource):
result[name] = True
if name == "action" and args == "on_loop":
if self.running:
- await self.on_loop()
+ await self._on_loop()
result[name] = True
result["counter"] = self.counter
return result
@@ -499,7 +499,7 @@ On the **CONTROL** or the **CONFIGURE** tab, use the `DoCommand` panel:
}
```
- To run the control logic loop `on_loop`, copy and paste the following command input:
+ To run the control logic loop method `_on_loop`, copy and paste the following command input:
```json {class="line-numbers linkable-line-numbers"}
{
From b8431127dc08f28dcd3d3786971c6a211d620d75 Mon Sep 17 00:00:00 2001
From: Naomi Pentrel <5212232+npentrel@users.noreply.github.com>
Date: Mon, 6 Oct 2025 10:53:50 +0200
Subject: [PATCH 4/5] change to run_control_logic
---
docs/operate/modules/control-logic.md | 16 ++++++++--------
1 file changed, 8 insertions(+), 8 deletions(-)
diff --git a/docs/operate/modules/control-logic.md b/docs/operate/modules/control-logic.md
index 869ad1a778..ad8075e178 100644
--- a/docs/operate/modules/control-logic.md
+++ b/docs/operate/modules/control-logic.md
@@ -109,7 +109,7 @@ To add the control logic, use the `DoCommand()` method.
The method accepts arbitrary JSON objects as commands.
The following code checks the command object and for the `start` command it sets the `running` parameter to `True` and for the `stop` command to `False`.
-A third command, `on_loop`, results in the `_on_loop()` method being called, but only if `running` is `True`.
+A third command, `run_control_logic`, results in the `_on_loop()` method being called, but only if `running` is `True`.
The `_on_loop()` method increments the counter.
@@ -138,7 +138,7 @@ The `_on_loop()` method increments the counter.
if name == "action" and args == "stop":
self.running = False
result[name] = True
- if name == "action" and args == "on_loop":
+ if name == "action" and args == "run_control_logic":
if self.running:
await self._on_loop()
result[name] = True
@@ -223,7 +223,7 @@ class ControlLogic(Generic, EasyResource):
if name == "action" and args == "stop":
self.running = False
result[name] = True
- if name == "action" and args == "on_loop":
+ if name == "action" and args == "run_control_logic":
if self.running:
await self._on_loop()
result[name] = True
@@ -332,7 +332,7 @@ Update your logic in the `do_command` method to use the board:
pin = await self.board.gpio_pin_by_name(name="13")
await pin.set(high=False)
result[name] = True
- if name == "action" and args == "on_loop":
+ if name == "action" and args == "run_control_logic":
if self.running:
await self._on_loop()
result[name] = True
@@ -440,7 +440,7 @@ class ControlLogic(Generic, EasyResource):
pin = await self.board.gpio_pin_by_name(name="13")
await pin.set(high=False)
result[name] = True
- if name == "action" and args == "on_loop":
+ if name == "action" and args == "run_control_logic":
if self.running:
await self._on_loop()
result[name] = True
@@ -503,7 +503,7 @@ On the **CONTROL** or the **CONFIGURE** tab, use the `DoCommand` panel:
```json {class="line-numbers linkable-line-numbers"}
{
- "action": "on_loop"
+ "action": "run_control_logic"
}
```
@@ -529,7 +529,7 @@ You can start and stop your control logic with the `DoCommand()` method from the
await control_logic.do_command({"action": "start"})
# Run your control loop
-await control_logic.do_command({"action": "on_loop"})
+await control_logic.do_command({"action": "run_control_logic"})
# Stop your control logic
await control_logic.do_command({"action": "stop"})
@@ -566,7 +566,7 @@ Configure another job:
- **Cron Schedule**: `0 * * * * *` (every minute)
- **Resource**: `generic-1`
- **Method**: `DoCommand`
-- **Command**: `{ "action": "on_loop" }`
+- **Command**: `{ "action": "run_control_logic" }`
{{% /tablestep %}}
{{% tablestep %}}
From 362c632a3003bf6bc5d31bbb02d43088c13c4af9 Mon Sep 17 00:00:00 2001
From: Naomi Pentrel <5212232+npentrel@users.noreply.github.com>
Date: Mon, 6 Oct 2025 10:54:46 +0200
Subject: [PATCH 5/5] Apply suggestions from code review
---
docs/manage/software/scheduled-jobs.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/docs/manage/software/scheduled-jobs.md b/docs/manage/software/scheduled-jobs.md
index 26c93e707f..07be6b34e4 100644
--- a/docs/manage/software/scheduled-jobs.md
+++ b/docs/manage/software/scheduled-jobs.md
@@ -164,9 +164,9 @@ Monitor job execution through `viam-server` logs. Look for `rdk.job_manager`:
- Jobs only run when `viam-server` is running.
- If a unix-cron job is scheduled to start but a previous invocation is still running, the job will be skipped. The job will next run once the previous invocation has finished running and the next scheduled time is reached.
- If a Golang duration job is scheduled to run but a previous invocation is still running, the next invocation will not run until the previous invocation finishes, at which point it will run immediately.
-- `DoCommand` is currently the only supported component and service API method that you can invoke with arguments.
+- `DoCommand` is currently the only supported component and service API method which you can invoke with arguments.
Aside from `DoCommand`, Jobs currently only support component and service API methods that do not require arguments.
- To avoid this limitation, you create a module and encapsulate API calls in a DoCommand API call.
+ To avoid this limitation, create a module and encapsulate API calls in a DoCommand API call.
- Jobs run locally on each machine and are not coordinated across multiple machines.
- Job execution depends on `viam-server` running.
- Failed jobs do not retry.