gc_plugin_abi 0.5.0

Gridcore Plugin API
Documentation
use super::GCBorrowedDatapointValue;
use crate::{GCDatapoint, GCDatapointID, GCDatapointValue};
use gc_abi::device_config::{GCEthernetInterface, GCSerialInterface};
use gc_abi::error::GCPluginABIError;
use gc_abi::{GCAbiConversion, GCLogLevel, raw};
use std::ffi::CStr;

/// Zero cost abstraction over the GCPluginInterface
#[repr(transparent)]
pub struct GCPluginInterface(gc_abi::GCPluginInterface);

impl GCPluginInterface {
    /// Publishes a datapoint value
    #[inline(always)]
    pub fn publish_datapoint(&self, datapoint_value: &GCDatapointValue) -> bool {
        self.0.publish_datapoint(datapoint_value)
    }

    /// Stores a datapoint value in the timeseries database
    #[inline(always)]
    pub fn store_datapoint(&self, datapoint_value: &GCDatapointValue) {
        self.0.store_datapoint(datapoint_value)
    }

    /// Get the datapoints that the plugin is allowed to publish
    #[inline(always)]
    pub fn get_own_datapoints(&self) -> &[GCDatapoint] {
        self.0.get_config().get_own_datapoints()
    }

    /// Retrieve the last value of a datapoint from the realtime database
    #[inline(always)]
    pub fn get_last_datapoint_value(&self, datapoint_id: GCDatapointID) -> Option<GCBorrowedDatapointValue> {
        // Safety:
        // The GCBorrowedDatapointValue is ensures the pointer is deallocated when it goes out of scope
        unsafe {
            let ptr = self.0.get_last_datapoint_value(datapoint_id);
            if ptr.is_null() {
                None
            } else {
                Some(GCBorrowedDatapointValue::from(ptr as gc_abi::raw::GCDatapointValue))
            }
        }
    }

    /// Get the datapoints that the plugin is subscribed to
    #[inline(always)]
    pub fn get_subscribed_datapoints(&self) -> &[GCDatapoint] {
        self.0.get_config().get_subscribed_datapoints()
    }

    /// Log a message
    /// Note: for logging messages, the macros at [crate::logger] should be used instead.
    #[inline(always)]
    pub fn log(&self, level: raw::eGCLogLevel, message: &CStr) {
        self.0.log(level, message)
    }

    /// Log a message audit
    /// Note: for logging messages, the macro at [crate::logger] should be used instead.
    #[inline(always)]
    pub fn log_audit(&self, message: &CStr) {
        self.0.log_audit(message)
    }

    /// Get the log level of the plugin
    #[inline]
    pub fn get_log_level(&self) -> Result<GCLogLevel, GCPluginABIError> {
        self.0.get_log_level()
    }

    /// Get raw C log level of the plugin
    #[inline]
    pub fn get_raw_log_level(&self) -> raw::eGCLogLevel {
        self.0.get_raw_log_level()
    }

    /// Parses the plugin specific configuration, this is an expensive operation and should be called only once
    pub fn get_own_configuration<T>(&self) -> Result<T, GCPluginABIError>
    where
        T: serde::de::DeserializeOwned,
    {
        // Safety:
        // The pointer is guaranteed to be valid by the gateway core
        let config = unsafe { *self.0.ref_c().config.pluginJsonConfig };

        let data = config.data;
        let mut result: raw::eResultCallback = raw::eResultCallback_GC_JSON_ERROR;

        // Safety:
        // The callback is guaranteed to be valid by the gateway core
        let str_data_obj =
            unsafe { config.getObjectSerialized.ok_or(GCPluginABIError::ErrorCallbackNotAvailable)?(data, &mut result as *mut raw::eResultCallback) };

        if result == raw::eResultCallback_GC_JSON_ERROR {
            return Err(GCPluginABIError::ErrorSerializingJsonConfig);
        }

        // Safety:
        // The string is guaranteed to be valid unless the function returns an error, which is checked above
        let str_data = unsafe { CStr::from_ptr(str_data_obj).to_str()? };
        let json_result = serde_json::from_str(str_data)?;

        // Safety:
        // The string is guaranteed to be valid unless the function returns an error, which is checked above
        unsafe { config.freeString.ok_or(GCPluginABIError::ErrorCallbackNotAvailable)?(str_data_obj) };
        Ok(json_result)
    }

    /// Retrieves an Ethernet interface configuration by logical name.
    ///
    /// Returns `None` if:
    /// - The core does not support device configuration
    /// - The requested interface name does not exist in the device configuration
    ///
    /// # Example
    /// ```ignore
    /// if let Some(eth) = plugin_interface.get_ethernet_interface("primary_eth") {
    ///     let interface = eth.interface; // e.g., "eth0"
    ///     // Use interface for network operations
    /// }
    /// ```
    #[inline]
    pub fn get_ethernet_interface(&self, name: &str) -> Option<GCEthernetInterface> {
        use std::ffi::CString;
        let c_name = CString::new(name).ok()?;
        self.0.get_ethernet_interface(&c_name)
    }

    /// Retrieves a serial interface configuration by logical name.
    ///
    /// Returns `None` if:
    /// - The core does not support device configuration
    /// - The requested interface name does not exist in the device configuration
    ///
    /// # Example
    /// ```ignore
    /// if let Some(serial) = plugin_interface.get_serial_interface("moxa_port_1") {
    ///     let path = serial.interface; // e.g., "/dev/ttyM0"
    ///     let baud_rate = serial.baud_rate; // e.g., 115200
    ///     // Open and configure serial port
    /// }
    /// ```
    #[inline]
    pub fn get_serial_interface(&self, name: &str) -> Option<GCSerialInterface> {
        use std::ffi::CString;
        let c_name = CString::new(name).ok()?;
        self.0.get_serial_interface(&c_name)
    }
}

impl From<raw::GCPluginInterface> for &GCPluginInterface {
    fn from(interface: raw::GCPluginInterface) -> Self {
        // Safety:
        // The current object is a zero cost abstraction of raw::GCPluginInterface,
        // thus both types are guaranteed to have the same memory layout.
        // TODO: A test should be made to ensure this condition is always true
        unsafe {
            let a = interface as *const GCPluginInterface;

            (a.as_ref().unwrap()) as _
        }
    }
}

unsafe impl Send for GCPluginInterface {}
unsafe impl Sync for GCPluginInterface {}