Need Some sort of Jumpstart



  • Hi all,

    I am controlling my heating up to now based on a Raspi together with a relay board. Now I want to use my Neuron M103 for this purpose.

    Up to now I coded in good, old C-Code. Running perfectly, integrated into systemd, doing syslogging and so on. Regarding Python- I know it is a interpreting programing language and did some first basic steps. But I doubt I can do the same I did in C in Python (from my knowledge, not from language abilities).

    However, I would like to use my Neuron to control the relays. I have ssen the Evok API but I have no clue how to really use. Are there somewhere some nice examples how others used it?

    Is there a possibility to use C anyhow?

    Thanks!

    /KNEBB



  • Hi @knebb
    I developed a fistful of programs in plain C on Neuron. I just linked against libmodbus and called the loopback ModBUS server at 127.0.0.1, port 502 (hence no need to enable the EVOK service).
    If you are not familiar I can send you some code to help you getting started.


  • administrators

    Hello @knebb, as @ntd wrote there is no need to use the Evok (which is, basically a bridge between Modbus server and several web-service interfaces). The simplest option for direct access from a C/C++ application should be the sysfs interface. Of course, a Modbus client connected to the internally running Modbus server can be used also.



  • @martin_triska @ntd
    Great thanks for the hints. Using libmodbus seems to be the way I will go. Seems to be easy enough to use and is available in C. I already found the Neuron Docs regarding the coils and registers.

    If you could send me your example code sniplets? How to set the relays, read DI or set DO?

    Thanks a lot in advance! first step is done.

    /KNEBB



  • @martin_triska

    I checked for the sysfs, too.

    I did not see any references to the relays? How can I set relays (Neuron M103 has some) through sysfs?

    Thanks again!

    /KNEBB



  • @knebb The relevant function is fieldbus_thread, that basically runs a polling loop every 20 ms. If you do not need threads, you can consider that function as main() and drop all the locking stuff (i.e. a good 50% of code in iteration).

    #include "lappaseven.h"
    #include "fieldbus.h"
    #include <errno.h>
    #include <string.h>
    
    #define MODBUS_ID       0
    
    #define AI1_ADDRESS     3
    #define DI1_ADDRESS     0
    #define DI2_ADDRESS     100
    #define DI3_ADDRESS     200
    #define DO1_ADDRESS     1
    #define DO2_ADDRESS     101
    #define DO3_ADDRESS     201
    #define AO1_ADDRESS     2
    #define VREFINT_ADDRESS 5
    #define VREF_ADDRESS    1009
    
    
    static IO image = { 0 };
    
    
    void
    fieldbus_free(modbus_t *modbus)
    {
        if (modbus != NULL) {
            modbus_close(modbus);
            modbus_free(modbus);
        }
    }
    
    modbus_t *
    fieldbus_new(void)
    {
        modbus_t *modbus;
        gchar *host;
        int port;
    
        host   = g_settings_get_string(gs.settings, "modbus-host");
        port   = g_settings_get_uint(gs.settings, "modbus-porta");
        modbus = modbus_new_tcp(host, port);
    
        if (modbus == NULL ||
            modbus_connect(modbus) == -1 ||
            modbus_set_slave(modbus, MODBUS_ID) == -1)
        {
            g_warning("Unable to initialize communication on %s:%d: %s",
                      host, port, modbus_strerror(errno));
            fieldbus_free(modbus);
            modbus = NULL;
        }
    
        g_free(host);
        return modbus;
    }
    
    gboolean
    fieldbus_get_ai(modbus_t *modbus)
    {
        uint16_t vrefint, vref, ai;
    
        if (modbus_read_registers(modbus, VREFINT_ADDRESS, 1, &vrefint) == -1 ||
            modbus_read_registers(modbus, VREF_ADDRESS, 1, &vref) == -1 ||
            modbus_read_registers(modbus, AI1_ADDRESS, 1, &ai) == -1)
        {
            g_warning("fieldbus_get_ai error: %s", modbus_strerror(errno));
            return FALSE;
        }
    
        /* Linearization to volts as described by Neuron manual, page 14.
         * AI1vdev and AI1voffset are set to supposedly 0. */
        image.AI1 = 3.3 * vref / vrefint * 3 * ai / 4096;
        return TRUE;
    }
    
    gboolean
    fieldbus_get_di(modbus_t *modbus)
    {
        uint16_t word1, word2, word3;
        int n;
    
        if (modbus_read_registers(modbus, DI1_ADDRESS, 1, &word1) == -1 ||
            modbus_read_registers(modbus, DI2_ADDRESS, 1, &word2) == -1 ||
            modbus_read_registers(modbus, DI3_ADDRESS, 1, &word3) == -1)
        {
            g_warning("fieldbus_get_di error: %s", modbus_strerror(errno));
            return FALSE;
        }
    
        for (n = 0; n < G_N_ELEMENTS(image.DI1); ++n) {
            image.DI1[n] = (word1 & (1 << n)) > 0;
        }
        for (n = 0; n < G_N_ELEMENTS(image.DI2); ++n) {
            image.DI2[n] = (word2 & (1 << n)) > 0;
        }
        for (n = 0; n < G_N_ELEMENTS(image.DI3); ++n) {
            image.DI3[n] = (word3 & (1 << n)) > 0;
        }
    
        return TRUE;
    }
    
    gboolean
    fieldbus_set_do(modbus_t *modbus)
    {
        uint16_t word1, word2, word3;
        int n;
    
        word1 = 0;
        word2 = 0;
        word3 = 0;
    
        for (n = 0; n < G_N_ELEMENTS(image.DO1); ++n) {
            word1 |= image.DO1[n] << n;
        }
        for (n = 0; n < G_N_ELEMENTS(image.DO2); ++n) {
            word2 |= image.DO2[n] << n;
        }
        for (n = 0; n < G_N_ELEMENTS(image.DO3); ++n) {
            word3 |= image.DO3[n] << n;
        }
    
        if (modbus_write_register(modbus, DO1_ADDRESS, word1) == -1 ||
            modbus_write_register(modbus, DO2_ADDRESS, word2) == -1 ||
            modbus_write_register(modbus, DO3_ADDRESS, word3) == -1)
        {
            g_warning("fieldbus_set_do error: %s", modbus_strerror(errno));
            return FALSE;
        }
    
        return TRUE;
    }
    
    gboolean
    fieldbus_set_ao(modbus_t *modbus)
    {
        /* Analog output must be between 0 .. 5 V, hence the division */
        if (modbus_write_register(modbus, AO1_ADDRESS, image.AO1 / 2) == -1) {
            g_warning("fieldbus_set_ao error: %s", modbus_strerror(errno));
            return FALSE;
        }
    
        return TRUE;
    }
    
    #if THREADING
    static gboolean
    iteration(modbus_t *modbus)
    {
        static gboolean workaround = FALSE;
        gboolean AI, DI, AO, DO, quit;
    
        AI = fieldbus_get_ai(modbus);
        DI = fieldbus_get_di(modbus);
    
        g_mutex_lock(&gs.fieldbus_lock);
        AO = gs.hal.AO1 != gs.share.AO1;
        DO = memcmp(gs.hal.DO1, gs.share.DO1, sizeof(gs.hal.DO1)) != 0 ||
             memcmp(gs.hal.DO2, gs.share.DO2, sizeof(gs.hal.DO2)) != 0 ||
             memcmp(gs.hal.DO3, gs.share.DO3, sizeof(gs.hal.DO3)) != 0;
        if (DI) {
            memcpy(gs.share.DI1, image.DI1, sizeof(image.DI1));
            memcpy(gs.share.DI2, image.DI2, sizeof(image.DI2));
            memcpy(gs.share.DI3, image.DI3, sizeof(image.DI3));
        }
        if (AI) {
            gs.share.AI1 = image.AI1;
        }
        if (DO) {
            memcpy(image.DO1, gs.share.DO1, sizeof(image.DO1));
            memcpy(image.DO2, gs.share.DO2, sizeof(image.DO2));
            memcpy(image.DO3, gs.share.DO3, sizeof(image.DO3));
        }
        if (AO) {
            image.AO1 = gs.share.AO1;
        }
        quit = gs.quit;
        g_mutex_unlock(&gs.fieldbus_lock);
    
        if (DI) {
            memcpy(gs.hal.DI1, image.DI1, sizeof(image.DI1));
            memcpy(gs.hal.DI2, image.DI2, sizeof(image.DI2));
            memcpy(gs.hal.DI3, image.DI3, sizeof(image.DI3));
        }
        if (DO && fieldbus_set_do(modbus)) {
            memcpy(gs.hal.DO1, image.DO1, sizeof(image.DO1));
            memcpy(gs.hal.DO2, image.DO2, sizeof(image.DO2));
            memcpy(gs.hal.DO3, image.DO3, sizeof(image.DO3));
        }
    
        /* XXX Workaround for a unknown fieldbus error that seems to reset the
         *     analog output: if this happens, force a AO1 refresh cycle */
        if (! DI || ! AI) {
            workaround = TRUE;
        }
        AO = AO || workaround;
    
        if (AO && fieldbus_set_ao(modbus)) {
            g_debug("AO1 set to %d", image.AO1);
            gs.hal.AO1 = image.AO1;
            workaround = FALSE;
        }
    
        return ! quit;
    }
    
    gpointer
    fieldbus_thread(gpointer user_data)
    {
        modbus_t *modbus;
        gint64 period, scan, now, span, limit;
    
        if (gs.dry_run) {
            return NULL;
        }
    
        modbus = fieldbus_new();
        if (modbus == NULL) {
            return NULL;
        }
    
        period = g_settings_get_uint(gs.settings, "periodo");
        if (period == 0) {
            g_warning("Period set to 0: fallback to 20000");
            period = 20000;
        }
    
        scan  = g_get_monotonic_time();
        limit = period;
    
        do {
            g_main_context_invoke_full(NULL, G_PRIORITY_HIGH,
                                       cycle_iteration, NULL, NULL);
            now  = g_get_monotonic_time();
            span = now - scan;
            if (span > limit) {
                limit = span;
                g_warning("Period exceeded: %.3f ms > %.3f ms" ,
                          limit / 1000., period / 1000.);
            }
            scan += period;
            if (now < scan) {
                g_usleep(scan - now);
            }
        } while (iteration(modbus));
    
        fieldbus_free(modbus);
        return NULL;
    }
    #endif
    

    The IO struct is just an image of the I/O status:

    typedef struct {
        uint8_t         DI1[4];
        uint8_t         DI2[16];
        uint8_t         DI3[16];
        uint8_t         DO1[4];
        uint8_t         DO2[14];
        uint8_t         DO3[14];
        double          AI1;
        uint16_t        AO1;
    } IO;
    


  • @ntd Thanks a lot! I think I got it so far. The adresses of the devices you got from Neuron documentation I assume?

    Only one question left so far. If I want to write the current state of the relay I would use (according to doc):

    //int modbus_write_bit(modbus_t *ctx, int addr, int status);
    errlvl=modbus_write_bit(modbus_handle, 100, status);
    

    I am unsure about the "status". This means I will enable or disable the bit and therefore switching the relay, right?

    Ok, how about reading the DI 2.1?
    Would I use the following?

    //int modbus_read_bits(modbus_t *ctx, int addr, int nb, uint8_t *dest);
    errlvl=modbus_read_bits(modbus_handle, 108, nb, *dest);
    

    Here I have no clue for what I would need the nb... does it mean it will read nb bits? To be stored in nb*dest? I would always use the value 1, wouldn't I? If using 2 it would return the value of the second DI 2.2 in the second bit, correct?

    Thanks a lot in advance!

    /KNEBB



  • Hi,

    for reference purposes even though some questions are still unanswered.
    I was able to set my relays as expected with the following code:

    #include <modbus/modbus.h>
    #include <errno.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>
    
    void main()
    {
    
    modbus_t *modbusglob;
    int errlvl;
    
    void fieldbus_free(modbus_t *modbus)
    {
            if (modbus != NULL) {
            modbus_close(modbus);
            modbus_free(modbus);
        }
      return;
    }
    
    modbus_t *fieldbus_new(void)
    {
        int err;
        modbus_t *modbus;
        char host[15];
        int port;
    
    // Need to use IP as it does not resolve DNS Names to IPs
        strcpy(host,"127.0.0.1");
        port   = 502;
        modbus = modbus_new_tcp(host, port);
    
        if (modbus == NULL )
        {
             printf("modbus is NULL\n");
            fieldbus_free(modbus);
            exit (55);
        }
    
         err = modbus_connect(modbus);
         if (err  == -1)
         {
            printf("modbus_connect, %s",modbus_strerror(errno));
            fieldbus_free(modbus);
            exit (55);
         };
     
         err=modbus_set_slave(modbus, 0);
         if(  err == -1)
        {
            printf("modbus_set_slave, %s",modbus_strerror(errno));
            fieldbus_free(modbus);
            modbus = NULL;
            exit (55);
        }
    
        return modbus;
    }
    
    modbusglob=fieldbus_new();
    printf("ON\n");
    // Using fixed address of 100 here for first relay (M103), relay 2.2 ist 101 and so on.
    // Using TRUE as synonym for HIGH or ON
    errlvl=modbus_write_bit(modbusglob, 100, TRUE);
    // Just for demo purposes- keep the relay on for 10 seconds
    sleep(10);
    printf("OFF\n");
    errlvl=modbus_write_bit(modbusglob, 100, FALSE);
    fieldbus_free(modbusglob);
    }
    


  • @knebb said in Need Some sort of Jumpstart:

    I am unsure about the "status".

    According to the documentation, status is the new value of the output and can be TRUE (to enable it) or FALSE (to disable it).

    Here I have no clue for what I would need the nb... does it mean it will read nb bits? To be stored in nb*dest? I would always use the value 1, wouldn't I? If using 2 it would return the value of the second DI 2.2 in the second bit, correct?

    Yes to all. This mimics the behavior of the underlying protocol: ModBUS has a Read coils function but does not have Read single coil. You can easily provide your own macro for that, if you really want:

    #define read_bit(bus,addr,dst) modbus_read_bits((bus),(addr),1,(dst))
    


  • @ntd

    Sorry to bother you. I think I got it finally!
    Using modbus_read_bits will read nb bits and write (according to the state) 0 or 1 into dst[0]...dst[nb].
    I had thought when reading 4 bits it will write the first state into the first bit of dst, second into the second and so on.

    But no, it writes the state as 0 or 1 into each of the uint8_t .

    I was successfull in reading RO2.8 and DI2.1 together with

    int nb,i;
    uint8_t dst[2];
    nb=2;
    modbus_read_bits(modbus,107,nb,dst);
    for (i=0;i<nb;i++)
    {
      if (dst[i] != 0) 
      {
        printf("Bit %d is %u\n",i,dst[i]);
      } else {
        printf("Bit %d is %u\n",i,dst[i])
      }
    }
    

    And in case RO2.8 is on an DI2.1 is off I will get:
    Bit 0 is 1
    Bit 1 is 0

    Similar this would apply to modbus_read_register();

    Holy sh**, I think I really got it now!

    Thanks for your patience!

    /KNEBB



  • @knebb said in Need Some sort of Jumpstart:

    it writes the state as 0 or 1 into each of the uint8_t .

    Or, using semantic constants, as FALSE and TRUE.

    Directly using bits would be really slow and error prone (and I nightmare for language bindings).