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 againstlibmodbus
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. -
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
-
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 asmain()
and drop all the locking stuff (i.e. a good 50% of code initeration
).#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 beTRUE
(to enable it) orFALSE
(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))
-
Sorry to bother you. I think I got it finally!
Using modbus_read_bits will readnb
bits and write (according to the state) 0 or 1 intodst[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
or1
into each of theuint8_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 0Similar 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
andTRUE
.Directly using bits would be really slow and error prone (and a nightmare for language bindings).