Commit 3e5765a009 for asterisk.org
commit 3e5765a00921ec5eb6144127071e587280f62a71
Author: Naveen Albert <asterisk@phreaknet.org>
Date: Mon Sep 21 19:54:42 2026 -0400
res_manager_devicestate: Add AMI actions to set device/mailbox state.
Add AMI actions that can be used to set device states and mailbox
states. This allows these states to be manually set in the same
way that res_pjsip_publish_asterisk can be used to manually
synchronize these states. This overcomes some existing limitations:
* func_devstate only allows setting Custom device states
* res_mwi_external_ami has a conflict with app_voicemail
Resolves: #2173
UserNote: The new DeviceStateChange and MailboxStateChange manager
actions allow setting device and mailbox states via AMI (the latter
of which, unlike the existing MWIUpdate action, doesn't require
res_mwi_external or conflict with app_voicemail).
diff --git a/res/res_manager_devicestate.c b/res/res_manager_devicestate.c
index 6702c29a54..5d5fbf5da7 100644
--- a/res/res_manager_devicestate.c
+++ b/res/res_manager_devicestate.c
@@ -65,6 +65,82 @@
</managerEvent>
</responses>
</manager>
+ <manager name="DeviceStateChange" language="en_US">
+ <since>
+ <version>24.1.0</version>
+ <version>23.7.0</version>
+ <version>22.13.0</version>
+ <version>20.23.0</version>
+ </since>
+ <synopsis>
+ Set a device state
+ </synopsis>
+ <syntax>
+ <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
+ <parameter name="Device" required="true">
+ <para>Device name. Does not need to be Custom; however, set device states for non-Custom devices with caution.</para>
+ </parameter>
+ <parameter name="State" required="true">
+ <para>The new device state value.</para>
+ <para>Should be one of the following:</para>
+ <para>The possible values are:</para>
+ <para>UNKNOWN | NOT_INUSE | INUSE | BUSY | INVALID | UNAVAILABLE | RINGING | RINGINUSE | ONHOLD</para>
+ </parameter>
+ <parameter name="Cachable" required="false">
+ <para>Whether or not this device state is cachable. Default is true, which is needed to persist the update.</para>
+ </parameter>
+ <parameter name="EntityID" required="true">
+ <para>The Entity ID of the remote Asterisk system that originated this device state update.</para>
+ <para>If the Entity ID provided matches the local Asterisk system's Entity ID, the update will be rejected.</para>
+ </parameter>
+ </syntax>
+ <description>
+ <para>Sets a device state value.</para>
+ <para>This can be used to manually synchronize the device state of remote devices.
+ It should NOT be used to set the device state of local devices.</para>
+ </description>
+ <see-also>
+ <ref type="manager">MailboxStateChange</ref>
+ </see-also>
+ </manager>
+ <manager name="MailboxStateChange" language="en_US">
+ <since>
+ <version>24.1.0</version>
+ <version>23.7.0</version>
+ <version>22.13.0</version>
+ <version>20.23.0</version>
+ </since>
+ <synopsis>
+ Set mailbox state
+ </synopsis>
+ <syntax>
+ <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
+ <parameter name="Mailbox" required="true">
+ <para>The name of the mailbox (with optional context).</para>
+ </parameter>
+ <parameter name="NewMessages" required="true">
+ <para>The number of new messages.</para>
+ </parameter>
+ <parameter name="OldMessages" required="true">
+ <para>The number of old messages.</para>
+ </parameter>
+ <parameter name="EntityID" required="true">
+ <para>The Entity ID of the remote Asterisk system that originated this mailbox state update.</para>
+ <para>If the Entity ID provided matches the local Asterisk system's Entity ID, the update will be rejected.</para>
+ </parameter>
+ </syntax>
+ <description>
+ <para>Sets a mailbox's state.</para>
+ <para>This can be used to manually synchronize the mailbox state of remote mailboxes.
+ It should NOT be used to set the mailbox state of local mailboxes.</para>
+ <para>Note this differs from the <literal>MWIUpdate</literal> action, which is more heavyweight
+ and depends on <literal>res_mwi_external</literal>.</para>
+ </description>
+ <see-also>
+ <ref type="manager">DeviceStateChange</ref>
+ <ref type="manager">MWIUpdate</ref>
+ </see-also>
+ </manager>
***/
@@ -73,6 +149,8 @@
#include "asterisk/manager.h"
#include "asterisk/stasis.h"
#include "asterisk/devicestate.h"
+#include "asterisk/mwi.h"
+#include "asterisk/conversions.h"
static struct stasis_forward *topic_forwarder;
@@ -118,10 +196,107 @@ static int action_devicestatelist(struct mansession *s, const struct message *m)
return 0;
}
+static int action_devicestatechange(struct mansession *s, const struct message *m)
+{
+ struct ast_eid eid;
+ enum ast_device_state state_val;
+ const char *device = astman_get_header(m, "Device");
+ const char *state = astman_get_header(m, "State");
+ const char *cachable = astman_get_header(m, "Cachable");
+ const char *entity_id = astman_get_header(m, "EntityID");
+
+ if (ast_strlen_zero(device) || ast_strlen_zero(state)) {
+ astman_send_error(s, m, "Missing device or device state");
+ return 0;
+ }
+ if (ast_strlen_zero(entity_id) || ast_str_to_eid(&eid, entity_id)) {
+ astman_send_error(s, m, "Missing or invalid entity ID");
+ return 0;
+ }
+ if (!strchr(device, '/') && !strchr(device, ':')) {
+ astman_send_error(s, m, "Invalid device name");
+ return 0;
+ }
+
+ state_val = ast_devstate_val(state);
+ /* ast_devstate_val returns AST_DEVICE_UNKNOWN if the device state value is not valid,
+ * but AST_DEVICE_UNKNOWN is also a legitimate value, so allow it if that's what the input actually was. */
+ if (state_val == AST_DEVICE_UNKNOWN && strcasecmp(state, "UNKNOWN")) {
+ astman_send_error(s, m, "Invalid device state value");
+ return 0;
+ }
+
+ if (!ast_eid_cmp(&ast_eid_default, &eid)) {
+ astman_send_error(s, m, "Entity ID is ourself (must be from a different Asterisk system)");
+ return 0;
+ }
+
+ if (ast_publish_device_state_full(device, state_val, ast_false(cachable) ? AST_DEVSTATE_NOT_CACHABLE : AST_DEVSTATE_CACHABLE, &eid)) {
+ astman_send_error(s, m, "Failed to publish device state update");
+ return 0;
+ }
+
+ astman_send_ack(s, m, "Updated or set device state");
+ return 0;
+}
+
+static int action_mailboxstatechange(struct mansession *s, const struct message *m)
+{
+ struct ast_eid eid;
+ int new_msgs, old_msgs;
+ char *context, *mailbox;
+ const char *mbox = astman_get_header(m, "Mailbox");
+ const char *newmsgs = astman_get_header(m, "NewMessages");
+ const char *oldmsgs = astman_get_header(m, "OldMessages");
+ const char *entity_id = astman_get_header(m, "EntityID");
+
+ if (ast_strlen_zero(mbox) || ast_strlen_zero(newmsgs) || ast_strlen_zero(oldmsgs)) {
+ astman_send_error(s, m, "Missing required parameters");
+ return 0;
+ }
+ if (ast_strlen_zero(entity_id) || ast_str_to_eid(&eid, entity_id)) {
+ astman_send_error(s, m, "Missing or invalid entity ID");
+ return 0;
+ }
+
+ if (ast_str_to_int(newmsgs, &new_msgs) || ast_str_to_int(oldmsgs, &old_msgs)) {
+ astman_send_error(s, m, "Invalid mailbox counts");
+ return 0;
+ }
+ if (new_msgs < 0 || old_msgs < 0) {
+ astman_send_error(s, m, "Invalid mailbox counts (must be non-negative)");
+ return 0;
+ }
+
+ if (!ast_eid_cmp(&ast_eid_default, &eid)) {
+ astman_send_error(s, m, "Entity ID is ourself (must be from a different Asterisk system)");
+ return 0;
+ }
+
+ context = ast_strdupa(mbox);
+ mailbox = strsep(&context, "@");
+
+ /* Mailbox is mandatory, context is optional */
+ if (ast_strlen_zero(mailbox)) {
+ astman_send_error(s, m, "A mailbox is required");
+ return 0;
+ }
+
+ if (ast_publish_mwi_state_full(mailbox, context, new_msgs, old_msgs, NULL, &eid)) {
+ astman_send_error(s, m, "Failed to publish mailbox state update");
+ return 0;
+ }
+
+ astman_send_ack(s, m, "Updated mailbox state");
+ return 0;
+}
+
static int unload_module(void)
{
topic_forwarder = stasis_forward_cancel(topic_forwarder);
ast_manager_unregister("DeviceStateList");
+ ast_manager_unregister("DeviceStateChange");
+ ast_manager_unregister("MailboxStateChange");
return 0;
}
@@ -130,6 +305,11 @@ static int load_module(void)
{
struct stasis_topic *manager_topic;
+ if (ast_eid_is_empty(&ast_eid_default)) {
+ ast_log(LOG_ERROR, "Entity ID is not set.\n");
+ return AST_MODULE_LOAD_DECLINE;
+ }
+
manager_topic = ast_manager_get_topic();
if (!manager_topic) {
return AST_MODULE_LOAD_DECLINE;
@@ -145,6 +325,18 @@ static int load_module(void)
return AST_MODULE_LOAD_DECLINE;
}
+ if (ast_manager_register_xml("DeviceStateChange", EVENT_FLAG_CALL | EVENT_FLAG_REPORTING, action_devicestatechange)) {
+ topic_forwarder = stasis_forward_cancel(topic_forwarder);
+ ast_manager_unregister("DeviceStateList");
+ return AST_MODULE_LOAD_DECLINE;
+ }
+ if (ast_manager_register_xml("MailboxStateChange", EVENT_FLAG_CALL | EVENT_FLAG_REPORTING, action_mailboxstatechange)) {
+ topic_forwarder = stasis_forward_cancel(topic_forwarder);
+ ast_manager_unregister("DeviceStateList");
+ ast_manager_unregister("DeviceStateChange");
+ return AST_MODULE_LOAD_DECLINE;
+ }
+
return AST_MODULE_LOAD_SUCCESS;
}