• Register
    • Login
    • Search
    • Categories
    • Recent
    • Tags
    • Popular
    • Users
    • Groups
    • Search

    Need Some sort of Jumpstart

    Official EVOK API
    3
    11
    1417
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • ntd
      ntd last edited by

      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.

      1 Reply Last reply Reply Quote 0
      • M
        martin_triska administrators last edited by

        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.

        K 1 Reply Last reply Reply Quote 1
        • K
          knebb last edited by

          @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

          ntd 1 Reply Last reply Reply Quote 0
          • K
            knebb @martin_triska last edited by

            @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

            1 Reply Last reply Reply Quote 0
            • ntd
              ntd @knebb last edited by

              @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;
              
              K 1 Reply Last reply Reply Quote 0
              • K
                knebb @ntd last edited by

                @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

                ntd 1 Reply Last reply Reply Quote 0
                • K
                  knebb last edited by

                  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);
                  }
                  
                  1 Reply Last reply Reply Quote 0
                  • ntd
                    ntd @knebb last edited by

                    @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))
                    
                    K 1 Reply Last reply Reply Quote 1
                    • K
                      knebb @ntd last edited by knebb

                      @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

                      ntd 1 Reply Last reply Reply Quote 0
                      • ntd
                        ntd @knebb last edited by ntd

                        @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 a nightmare for language bindings).

                        1 Reply Last reply Reply Quote 0
                        • First post
                          Last post