Commit 6dd5e15e authored by Ivan Tyagov's avatar Ivan Tyagov

Feature/opcua

See merge request !9
parents a5ef183a 810bf151
# ignore by file extension
*.pyc *.pyc
*.pyo *.pyo
*.o
*.a
*.so
*.gch
*.vscode
*.log
# ignore specific files
coupler/opc-ua-server/open62541.c
coupler/opc-ua-server/open62541.h
__LOCATED_VAR(BOOL,__QX0_0_0_0,Q,X,0,0,0,0)
__LOCATED_VAR(BOOL,__QX0_0_1_1,Q,X,0,0,1,1)
__LOCATED_VAR(BOOL,__QX0_0_2_2,Q,X,0,0,2,2)
__LOCATED_VAR(BOOL,__QX0_0_3_3,Q,X,0,0,3,3)
/* File generated by Beremiz (PlugGenerate_C method of Modbus plugin) */
/*
* Copyright (c) 2016 Mario de Sousa (msousa@fe.up.pt)
*
* This file is part of the Modbus library for Beremiz and matiec.
*
* This Modbus library is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
* General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this Modbus library. If not, see <http://www.gnu.org/licenses/>.
*
* This code is made available on the understanding that it will not be
* used in safety-critical situations without a full and competent review.
*/
#include <stdio.h>
#include <string.h> /* required for memcpy() */
#include <errno.h>
#include <time.h>
#include <signal.h>
#include <unistd.h> /* required for pause() */
#include "mb_slave_and_master.h"
#include "MB_0.h"
#define MAX_MODBUS_ERROR_CODE 11
static const char *modbus_error_messages[MAX_MODBUS_ERROR_CODE+1] = {
/* 0 */ "", /* un-used -> no error! */
/* 1 */ "illegal/unsuported function",
/* 2 */ "illegal data address",
/* 3 */ "illegal data value",
/* 4 */ "slave device failure",
/* 5 */ "acknowledge -> slave intends to reply later",
/* 6 */ "slave device busy",
/* 7 */ "negative acknowledge",
/* 8 */ "memory parity error",
/* 9 */ "", /* undefined by Modbus */
/* 10*/ "gateway path unavalilable",
/* 11*/ "gateway target device failed to respond"
};
/* Execute a modbus client transaction/request */
static int __execute_mb_request(int request_id){
switch (client_requests[request_id].mb_function){
case 1: /* read coils */
return read_output_bits(client_requests[request_id].slave_id,
client_requests[request_id].address,
client_requests[request_id].count,
client_requests[request_id].coms_buffer,
(int) client_requests[request_id].count,
client_nodes[client_requests[request_id].client_node_id].mb_nd,
client_requests[request_id].retries,
&(client_requests[request_id].mb_error_code),
&(client_requests[request_id].resp_timeout),
&(client_requests[request_id].coms_buf_mutex));
case 2: /* read discrete inputs */
return read_input_bits( client_requests[request_id].slave_id,
client_requests[request_id].address,
client_requests[request_id].count,
client_requests[request_id].coms_buffer,
(int) client_requests[request_id].count,
client_nodes[client_requests[request_id].client_node_id].mb_nd,
client_requests[request_id].retries,
&(client_requests[request_id].mb_error_code),
&(client_requests[request_id].resp_timeout),
&(client_requests[request_id].coms_buf_mutex));
case 3: /* read holding registers */
return read_output_words(client_requests[request_id].slave_id,
client_requests[request_id].address,
client_requests[request_id].count,
client_requests[request_id].coms_buffer,
(int) client_requests[request_id].count,
client_nodes[client_requests[request_id].client_node_id].mb_nd,
client_requests[request_id].retries,
&(client_requests[request_id].mb_error_code),
&(client_requests[request_id].resp_timeout),
&(client_requests[request_id].coms_buf_mutex));
case 4: /* read input registers */
return read_input_words(client_requests[request_id].slave_id,
client_requests[request_id].address,
client_requests[request_id].count,
client_requests[request_id].coms_buffer,
(int) client_requests[request_id].count,
client_nodes[client_requests[request_id].client_node_id].mb_nd,
client_requests[request_id].retries,
&(client_requests[request_id].mb_error_code),
&(client_requests[request_id].resp_timeout),
&(client_requests[request_id].coms_buf_mutex));
case 5: /* write single coil */
return write_output_bit(client_requests[request_id].slave_id,
client_requests[request_id].address,
client_requests[request_id].coms_buffer[0],
client_nodes[client_requests[request_id].client_node_id].mb_nd,
client_requests[request_id].retries,
&(client_requests[request_id].mb_error_code),
&(client_requests[request_id].resp_timeout),
&(client_requests[request_id].coms_buf_mutex));
case 6: /* write single register */
return write_output_word(client_requests[request_id].slave_id,
client_requests[request_id].address,
client_requests[request_id].coms_buffer[0],
client_nodes[client_requests[request_id].client_node_id].mb_nd,
client_requests[request_id].retries,
&(client_requests[request_id].mb_error_code),
&(client_requests[request_id].resp_timeout),
&(client_requests[request_id].coms_buf_mutex));
case 7: break; /* function not yet supported */
case 8: break; /* function not yet supported */
case 9: break; /* function not yet supported */
case 10: break; /* function not yet supported */
case 11: break; /* function not yet supported */
case 12: break; /* function not yet supported */
case 13: break; /* function not yet supported */
case 14: break; /* function not yet supported */
case 15: /* write multiple coils */
return write_output_bits(client_requests[request_id].slave_id,
client_requests[request_id].address,
client_requests[request_id].count,
client_requests[request_id].coms_buffer,
client_nodes[client_requests[request_id].client_node_id].mb_nd,
client_requests[request_id].retries,
&(client_requests[request_id].mb_error_code),
&(client_requests[request_id].resp_timeout),
&(client_requests[request_id].coms_buf_mutex));
case 16: /* write multiple registers */
return write_output_words(client_requests[request_id].slave_id,
client_requests[request_id].address,
client_requests[request_id].count,
client_requests[request_id].coms_buffer,
client_nodes[client_requests[request_id].client_node_id].mb_nd,
client_requests[request_id].retries,
&(client_requests[request_id].mb_error_code),
&(client_requests[request_id].resp_timeout),
&(client_requests[request_id].coms_buf_mutex));
default: break; /* should never occur, if file generation is correct */
}
fprintf(stderr, "Modbus plugin: Modbus function %d not supported\n", request_id); /* should never occur, if file generation is correct */
return -1;
}
/* pack bits from unpacked_data to packed_data */
static inline int __pack_bits(u16 *unpacked_data, u16 start_addr, u16 bit_count, u8 *packed_data) {
u8 bit;
u16 byte, coils_processed;
if ((0 == bit_count) || (65535-start_addr < bit_count-1))
return -ERR_ILLEGAL_DATA_ADDRESS; /* ERR_ILLEGAL_DATA_ADDRESS defined in mb_util.h */
for( byte = 0, coils_processed = 0; coils_processed < bit_count; byte++) {
packed_data[byte] = 0;
for( bit = 0x01; (bit & 0xFF) && (coils_processed < bit_count); bit <<= 1, coils_processed++ ) {
if(unpacked_data[start_addr + coils_processed])
packed_data[byte] |= bit; /* set bit */
else packed_data[byte] &= ~bit; /* reset bit */
}
}
return 0;
}
/* unpack bits from packed_data to unpacked_data */
static inline int __unpack_bits(u16 *unpacked_data, u16 start_addr, u16 bit_count, u8 *packed_data) {
u8 temp, bit;
u16 byte, coils_processed;
if ((0 == bit_count) || (65535-start_addr < bit_count-1))
return -ERR_ILLEGAL_DATA_ADDRESS; /* ERR_ILLEGAL_DATA_ADDRESS defined in mb_util.h */
for(byte = 0, coils_processed = 0; coils_processed < bit_count; byte++) {
temp = packed_data[byte] ;
for(bit = 0x01; (bit & 0xff) && (coils_processed < bit_count); bit <<= 1, coils_processed++) {
unpacked_data[start_addr + coils_processed] = (temp & bit)?1:0;
}
}
return 0;
}
static int __read_inbits (void *mem_map, u16 start_addr, u16 bit_count, u8 *data_bytes) {
int res = __pack_bits(((server_mem_t *)mem_map)->ro_bits, start_addr, bit_count, data_bytes);
if (res >= 0) {
/* update the flag and counter of Modbus requests we have processed. */
((server_mem_t *)mem_map)->flag_read_req_counter++;
((server_mem_t *)mem_map)->flag_read_req_flag = 1;
}
return res;
}
static int __read_outbits (void *mem_map, u16 start_addr, u16 bit_count, u8 *data_bytes) {
int res = __pack_bits(((server_mem_t *)mem_map)->rw_bits, start_addr, bit_count, data_bytes);
if (res >= 0) {
/* update the flag and counter of Modbus requests we have processed. */
((server_mem_t *)mem_map)->flag_read_req_counter++;
((server_mem_t *)mem_map)->flag_read_req_flag = 1;
}
return res;
}
static int __write_outbits (void *mem_map, u16 start_addr, u16 bit_count, u8 *data_bytes) {
int res = __unpack_bits(((server_mem_t *)mem_map)->rw_bits, start_addr, bit_count, data_bytes);
if (res >= 0) {
/* update the flag and counter of Modbus requests we have processed. */
((server_mem_t *)mem_map)->flag_write_req_counter++;
((server_mem_t *)mem_map)->flag_write_req_flag = 1;
}
return res;
}
static int __read_inwords (void *mem_map, u16 start_addr, u16 word_count, u16 *data_words) {
if ((start_addr + word_count) > MEM_AREA_SIZE)
return -ERR_ILLEGAL_DATA_ADDRESS; /* ERR_ILLEGAL_DATA_ADDRESS defined in mb_util.h */
/* update the flag and counter of Modbus requests we have processed. */
((server_mem_t *)mem_map)->flag_read_req_counter++;
((server_mem_t *)mem_map)->flag_read_req_flag = 1;
/* use memcpy() because loop with pointers (u16 *) caused alignment problems */
memcpy(/* dest */ (void *)data_words,
/* src */ (void *)&(((server_mem_t *)mem_map)->ro_words[start_addr]),
/* size */ word_count * 2);
return 0;
}
static int __read_outwords (void *mem_map, u16 start_addr, u16 word_count, u16 *data_words) {
if ((start_addr + word_count) > MEM_AREA_SIZE)
return -ERR_ILLEGAL_DATA_ADDRESS; /* ERR_ILLEGAL_DATA_ADDRESS defined in mb_util.h */
/* update the flag and counter of Modbus requests we have processed. */
((server_mem_t *)mem_map)->flag_read_req_counter++;
((server_mem_t *)mem_map)->flag_read_req_flag = 1;
/* use memcpy() because loop with pointers (u16 *) caused alignment problems */
memcpy(/* dest */ (void *)data_words,
/* src */ (void *)&(((server_mem_t *)mem_map)->rw_words[start_addr]),
/* size */ word_count * 2);
return 0;
}
static int __write_outwords(void *mem_map, u16 start_addr, u16 word_count, u16 *data_words) {
if ((start_addr + word_count) > MEM_AREA_SIZE)
return -ERR_ILLEGAL_DATA_ADDRESS; /* ERR_ILLEGAL_DATA_ADDRESS defined in mb_util.h */
/* update the flag and counter of Modbus requests we have processed. */
((server_mem_t *)mem_map)->flag_write_req_counter++;
((server_mem_t *)mem_map)->flag_write_req_flag = 1;
/* WARNING: The data returned in the data_words[] array is not guaranteed to be 16 bit aligned.
* It is not therefore safe to cast it to an u16 data type.
* The following code cannot be used. memcpy() is used instead.
*/
/*
for (count = 0; count < word_count ; count++)
((server_mem_t *)mem_map)->rw_words[count + start_addr] = data_words[count];
*/
memcpy(/* dest */ (void *)&(((server_mem_t *)mem_map)->rw_words[start_addr]),
/* src */ (void *)data_words,
/* size */ word_count * 2);
return 0;
}
#include <pthread.h>
static void *__mb_server_thread(void *_server_node) {
server_node_t *server_node = _server_node;
mb_slave_callback_t callbacks = {
&__read_inbits,
&__read_outbits,
&__write_outbits,
&__read_inwords,
&__read_outwords,
&__write_outwords,
(void *)&(server_node->mem_area)
};
// Enable thread cancelation. Enabled is default, but set it anyway to be safe.
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
// mb_slave_run() should never return!
mb_slave_run(server_node->mb_nd /* nd */, callbacks, server_node->slave_id);
fprintf(stderr, "Modbus plugin: Modbus server for node %s died unexpectedly!\n", server_node->location); /* should never occur */
return NULL;
}
#define timespec_add(ts, sec, nsec) { \
ts.tv_sec += sec; \
ts.tv_nsec += nsec; \
if (ts.tv_nsec >= 1000000000) { \
ts.tv_sec ++; \
ts.tv_nsec -= 1000000000; \
} \
}
static void *__mb_client_thread(void *_index) {
int client_node_id = (char *)_index - (char *)NULL; // Use pointer arithmetic (more portable than cast)
// Enable thread cancelation. Enabled is default, but set it anyway to be safe.
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
/* loop the communication with the client
*
* When the client thread has difficulty communicating with remote client and/or server (network issues, for example),
* then the communications get delayed and we will fall behind in the period.
*
* This is OK. Note that if the condition variable were to be signaled multiple times while the client thread is inside the same
* Modbus transaction, then all those signals would be ignored.
* However, and since we keep the mutex locked during the communication cycle, it is not possible to signal the condition variable
* during that time (it is only possible while the thread is blocked during the call to pthread_cond_wait().
*
* This means that when network issues eventually get resolved, we will NOT have a bunch of delayed activations to handle
* in quick succession (which would goble up CPU time).
*
* Notice that the above property is valid whether the communication cycle is run with the mutex locked, or unlocked.
* Since it makes it easier to implement the correct semantics for the other activation methods if the communication cycle
* is run with the mutex locked, then that is what we do.
*
* Note that during all the communication cycle we will keep locked the mutex
* (i.e. the mutex used together with the condition variable that will activate a new communication cycle)
*
* Note that we never get to explicitly unlock this mutex. It will only be unlocked by the pthread_cond_wait()
* call at the end of the cycle.
*/
pthread_mutex_lock(&(client_nodes[client_node_id].mutex));
while (1) {
/*
struct timespec cur_time;
clock_gettime(CLOCK_MONOTONIC, &cur_time);
fprintf(stderr, "Modbus client thread (%d) - new cycle (%ld:%ld)!\n", client_node_id, cur_time.tv_sec, cur_time.tv_nsec);
*/
int req;
for (req=0; req < NUMBER_OF_CLIENT_REQTS; req ++){
/* just do the requests belonging to the client */
if (client_requests[req].client_node_id != client_node_id)
continue;
/* only do the request if:
* - this request was explictly asked to be executed by the client program
* OR
* - the client thread was activated periodically
* (in which case we execute all the requests belonging to the client node)
*/
if ((client_requests[req].flag_exec_req == 0) && (client_nodes[client_requests[req].client_node_id].periodic_act == 0))
continue;
/*
fprintf(stderr, "Modbus client thread (%d): RUNNING Modbus request %d (periodic = %d flag_exec_req = %d)\n",
client_node_id, req, client_nodes[client_requests[req].client_node_id].periodic_act, client_requests[req].flag_exec_req );
*/
int res_tmp = __execute_mb_request(req);
client_requests[req].tn_error_code = 0; // assume success
switch (res_tmp) {
case PORT_FAILURE: {
if (res_tmp != client_nodes[client_node_id].prev_error)
fprintf(stderr, "Modbus plugin: Error connecting Modbus client %s to remote server.\n", client_nodes[client_node_id].location);
client_nodes[client_node_id].prev_error = res_tmp;
client_requests[req].tn_error_code = 1; // error accessing IP network, or serial interface
break;
}
case INVALID_FRAME: {
if ((res_tmp != client_requests[req].prev_error) && (0 == client_nodes[client_node_id].prev_error))
fprintf(stderr, "Modbus plugin: Modbus client request configured at location %s was unsuccesful. Server/slave returned an invalid/corrupted frame.\n", client_requests[req].location);
client_requests[req].prev_error = res_tmp;
client_requests[req].tn_error_code = 2; // reply received from server was an invalid frame
break;
}
case TIMEOUT: {
if ((res_tmp != client_requests[req].prev_error) && (0 == client_nodes[client_node_id].prev_error))
fprintf(stderr, "Modbus plugin: Modbus client request configured at location %s timed out waiting for reply from server.\n", client_requests[req].location);
client_requests[req].prev_error = res_tmp;
client_requests[req].tn_error_code = 3; // server did not reply before timeout expired
break;
}
case MODBUS_ERROR: {
if (client_requests[req].prev_error != client_requests[req].mb_error_code) {
fprintf(stderr, "Modbus plugin: Modbus client request configured at location %s was unsuccesful. Server/slave returned error code 0x%2x", client_requests[req].location, client_requests[req].mb_error_code);
if (client_requests[req].mb_error_code <= MAX_MODBUS_ERROR_CODE ) {
fprintf(stderr, "(%s)", modbus_error_messages[client_requests[req].mb_error_code]);
fprintf(stderr, ".\n");
}
}
client_requests[req].prev_error = client_requests[req].mb_error_code;
client_requests[req].tn_error_code = 4; // server returned a valid Modbus error frame
break;
}
default: {
if ((res_tmp >= 0) && (client_nodes[client_node_id].prev_error != 0)) {
fprintf(stderr, "Modbus plugin: Modbus client %s has reconnected to server/slave.\n", client_nodes[client_node_id].location);
}
if ((res_tmp >= 0) && (client_requests[req] .prev_error != 0)) {
fprintf(stderr, "Modbus plugin: Modbus client request configured at location %s has succesfully resumed comunication.\n", client_requests[req].location);
}
client_nodes[client_node_id].prev_error = 0;
client_requests[req] .prev_error = 0;
break;
}
}
/* Set the flag_tn_error_code and flag_mb_error_code that are mapped onto
* located BYTE variables, so the user program
* knows how the communication is going.
*/
client_requests[req].flag_mb_error_code = client_requests[req].mb_error_code;
client_requests[req].flag_tn_error_code = client_requests[req].tn_error_code;
/* We have just finished excuting a client transcation request.
* If the current cycle was activated by user request we reset the flag used to ask to run it
*/
if (0 != client_requests[req].flag_exec_req) {
client_requests[req].flag_exec_req = 0;
client_requests[req].flag_exec_started = 0;
}
//fprintf(stderr, "Modbus plugin: RUNNING<---> of Modbus request %d (periodic = %d flag_exec_req = %d)\n",
// req, client_nodes[client_requests[req].client_node_id].periodic_act, client_requests[req].flag_exec_req );
}
// Wait for signal (from timer or explicit request from user program) before starting the next cycle
{
// No need to lock the mutex. Is is already locked just before the while(1) loop.
// Read the comment there to understand why.
// pthread_mutex_lock(&(client_nodes[client_node_id].mutex));
/* the client thread has just finished a cycle, so all the flags used to signal an activation
* and specify the activation source (periodic, user request, ...)
* get reset here, before waiting for a new activation.
*/
client_nodes[client_node_id].periodic_act = 0;
client_nodes[client_node_id].execute_req = 0;
while (client_nodes[client_node_id].execute_req == 0)
pthread_cond_wait(&(client_nodes[client_node_id].condv),
&(client_nodes[client_node_id].mutex));
// We run the communication cycle with the mutex locked.
// Read the comment just above the while(1) to understand why.
// pthread_mutex_unlock(&(client_nodes[client_node_id].mutex));
}
}
// humour the compiler.
return NULL;
}
/* Thread that simply implements a periodic 'timer',
* i.e. periodically sends signal to the thread running __mb_client_thread()
*
* Note that we do not use a posix timer (timer_create() ) because there doesn't seem to be a way
* of having the timer notify the thread that is portable across Xenomai and POSIX.
* - SIGEV_THREAD : not supported by Xenomai
* - SIGEV_THREAD_ID : Linux specific (i.e. non POSIX)
* Even so, I did not get it to work under Linux (issues with the header files)
* - SIGEV_SIGNAL : Will not work, as signal is sent to random thread in process!
*/
static void *__mb_client_timer_thread(void *_index) {
int client_node_id = (char *)_index - (char *)NULL; // Use pointer arithmetic (more portable than cast)
struct timespec next_cycle;
int period_sec = client_nodes[client_node_id].comm_period / 1000; /* comm_period is in ms */
int period_nsec = (client_nodes[client_node_id].comm_period %1000)*1000000; /* comm_period is in ms */
// Enable thread cancelation. Enabled is default, but set it anyway to be safe.
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
if (client_nodes[client_node_id].comm_period <= 0) {
// No periodic activation required => nothing to do!
while (1) pause(); // wait to be canceled when program terminates (shutdown() is called)
return NULL; // not really necessary, just makes it easier to understand the code.
}
// get the current time
clock_gettime(CLOCK_MONOTONIC, &next_cycle);
while(1) {
// Determine absolute time instant for starting the next cycle
struct timespec prev_cycle, now;
prev_cycle = next_cycle;
timespec_add(next_cycle, period_sec, period_nsec);
/* NOTE:
* It is probably un-necessary to check for overflow of timer!
* Even in 32 bit systems this will take at least 68 years since the computer booted
* (remember, we are using CLOCK_MONOTONIC, which should start counting from 0
* every time the system boots). On 64 bit systems, it will take over
* 10^11 years to overflow.
*/
clock_gettime(CLOCK_MONOTONIC, &now);
if (next_cycle.tv_sec < prev_cycle.tv_sec) {
/* Timer overflow. See NOTE B above */
next_cycle = now;
timespec_add(next_cycle, period_sec, period_nsec);
}
while (0 != clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &next_cycle, NULL));
/* signal the client node's condition variable on which the client node's thread should be waiting... */
/* Since the communication cycle is run with the mutex locked, we use trylock() instead of lock() */
if (pthread_mutex_trylock (&(client_nodes[client_node_id].mutex)) == 0) {
client_nodes[client_node_id].execute_req = 1; // tell the thread to execute
client_nodes[client_node_id].periodic_act = 1; // tell the thread the activation was done by periodic timer
pthread_cond_signal (&(client_nodes[client_node_id].condv));
pthread_mutex_unlock(&(client_nodes[client_node_id].mutex));
} else {
/* We never get to signal the thread for activation. But that is OK.
* If it still in the communication cycle (during which the mutex is kept locked)
* then that means that the communication cycle is falling behing in the periodic
* communication cycle, and we therefore need to skip a period.
*/
}
}
return NULL; // humour the compiler -> will never be executed!
}
int __cleanup_0 ();
int __init_0 (int argc, char **argv){
int index;
for (index=0; index < NUMBER_OF_CLIENT_NODES;index++) {
client_nodes[index].mb_nd = -1;
/* see comment in mb_runtime.h to understad why we need to initialize these entries */
switch (client_nodes[index].node_address.naf) {
case naf_tcp:
client_nodes[index].node_address.addr.tcp.host = client_nodes[index].str1;
client_nodes[index].node_address.addr.tcp.service = client_nodes[index].str2;
break;
case naf_rtu:
client_nodes[index].node_address.addr.rtu.device = client_nodes[index].str1;
break;
}
}
for (index=0; index < NUMBER_OF_SERVER_NODES;index++) {
// mb_nd with negative numbers indicate how far it has been initialised (or not)
// -2 --> no modbus node created; no thread created
// -1 --> modbus node created!; no thread created
// >=0 --> modbus node created!; thread created!
server_nodes[index].mb_nd = -2;
server_nodes[index].mem_area.flag_write_req_flag = 0;
server_nodes[index].mem_area.flag_write_req_counter = 0;
server_nodes[index].mem_area.flag_read_req_counter = 0;
server_nodes[index].mem_area.flag_read_req_flag = 0;
/* see comment in mb_runtime.h to understad why we need to initialize these entries */
switch (server_nodes[index].node_address.naf) {
case naf_tcp:
server_nodes[index].node_address.addr.tcp.host = server_nodes[index].str1;
server_nodes[index].node_address.addr.tcp.service = server_nodes[index].str2;
break;
case naf_rtu:
server_nodes[index].node_address.addr.rtu.device = server_nodes[index].str1;
break;
}
}
/* modbus library init */
/* Note that TOTAL_xxxNODE_COUNT are the nodes required by _ALL_ the instances of the modbus
* extension currently in the user's project. This file (MB_xx.c) is handling only one instance,
* but must initialize the library for all instances. Only the first call to mb_slave_and_master_init()
* will result in memory being allocated. All subsequent calls (by other MB_xx,c files) will be ignored
* by the mb_slave_and_master_init() funtion, as long as they are called with the same arguments.
*/
if (mb_slave_and_master_init(TOTAL_TCPNODE_COUNT, TOTAL_RTUNODE_COUNT, TOTAL_ASCNODE_COUNT) <0) {
fprintf(stderr, "Modbus plugin: Error starting modbus library\n");
// return imediately. Do NOT goto error_exit, as we did not get to
// start the modbus library!
return -1;
}
/* init each client request */
/* Must be done _before_ launching the client threads!! */
for (index=0; index < NUMBER_OF_CLIENT_REQTS; index ++){
/* make sure flags connected to user program MB transaction start request are all reset */
client_requests[index].flag_exec_req = 0;
client_requests[index].flag_exec_started = 0;
/* init the mutex for each client request */
/* Must be done _before_ launching the client threads!! */
if (pthread_mutex_init(&(client_requests[index].coms_buf_mutex), NULL)) {
fprintf(stderr, "Modbus plugin: Error initializing request for modbus client node %s\n", client_nodes[client_requests[index].client_node_id].location);
goto error_exit;
}
}
/* init each client connection to remote modbus server, and launch thread */
/* NOTE: All client_nodes[].init_state are initialised to 0 in the code
* generated by the modbus plugin
*/
for (index=0; index < NUMBER_OF_CLIENT_NODES;index++){
/* establish client connection */
client_nodes[index].mb_nd = mb_master_connect (client_nodes[index].node_address);
if (client_nodes[index].mb_nd < 0){
fprintf(stderr, "Modbus plugin: Error creating modbus client node %s\n", client_nodes[index].location);
goto error_exit;
}
client_nodes[index].init_state = 1; // we have created the node
/* initialize the mutex variable that will be used by the thread handling the client node */
bzero(&(client_nodes[index].mutex), sizeof(pthread_mutex_t));
if (pthread_mutex_init(&(client_nodes[index].mutex), NULL) < 0) {
fprintf(stderr, "Modbus plugin: Error creating mutex for modbus client node %s\n", client_nodes[index].location);
goto error_exit;
}
client_nodes[index].init_state = 2; // we have created the mutex
/* initialize the condition variable that will be used by the thread handling the client node */
bzero(&(client_nodes[index].condv), sizeof(pthread_cond_t));
if (pthread_cond_init(&(client_nodes[index].condv), NULL) < 0) {
fprintf(stderr, "Modbus plugin: Error creating condition variable for modbus client node %s\n", client_nodes[index].location);
goto error_exit;
}
client_nodes[index].execute_req = 0; //variable associated with condition variable
client_nodes[index].init_state = 3; // we have created the condition variable
/* launch a thread to handle this client node timer */
{
int res = 0;
pthread_attr_t attr;
res |= pthread_attr_init(&attr);
res |= pthread_create(&(client_nodes[index].timer_thread_id), &attr, &__mb_client_timer_thread, (void *)((char *)NULL + index));
if (res != 0) {
fprintf(stderr, "Modbus plugin: Error starting timer thread for modbus client node %s\n", client_nodes[index].location);
goto error_exit;
}
}
client_nodes[index].init_state = 4; // we have created the timer
/* launch a thread to handle this client node */
{
int res = 0;
pthread_attr_t attr;
res |= pthread_attr_init(&attr);
res |= pthread_create(&(client_nodes[index].thread_id), &attr, &__mb_client_thread, (void *)((char *)NULL + index));
if (res != 0) {
fprintf(stderr, "Modbus plugin: Error starting thread for modbus client node %s\n", client_nodes[index].location);
goto error_exit;
}
}
client_nodes[index].init_state = 5; // we have created the thread
}
/* init each local server */
/* NOTE: All server_nodes[].init_state are initialised to 0 in the code
* generated by the modbus plugin
*/
for (index=0; index < NUMBER_OF_SERVER_NODES;index++){
/* create the modbus server */
server_nodes[index].mb_nd = mb_slave_new (server_nodes[index].node_address);
if (server_nodes[index].mb_nd < 0){
fprintf(stderr, "Modbus plugin: Error creating modbus server node %s\n", server_nodes[index].location);
goto error_exit;
}
server_nodes[index].init_state = 1; // we have created the node
/* launch a thread to handle this server node */
{
int res = 0;
pthread_attr_t attr;
res |= pthread_attr_init(&attr);
res |= pthread_create(&(server_nodes[index].thread_id), &attr, &__mb_server_thread, (void *)&(server_nodes[index]));
if (res != 0) {
fprintf(stderr, "Modbus plugin: Error starting modbus server thread for node %s\n", server_nodes[index].location);
goto error_exit;
}
}
server_nodes[index].init_state = 2; // we have created the node and thread
}
return 0;
error_exit:
__cleanup_0 ();
return -1;
}
void __publish_0 (){
int index;
for (index=0; index < NUMBER_OF_CLIENT_REQTS; index ++){
/* synchronize the PLC and MB buffers only for the output requests */
if (client_requests[index].req_type == req_output){
// lock the mutex brefore copying the data
if(pthread_mutex_trylock(&(client_requests[index].coms_buf_mutex)) == 0){
// Check if user configured this MB request to be activated whenever the data to be written changes
if (client_requests[index].write_on_change) {
// Let's check if the data did change...
// compare the data in plcv_buffer to coms_buffer
int res;
res = memcmp((void *)client_requests[index].coms_buffer /* buf 1 */,
(void *)client_requests[index].plcv_buffer /* buf 2*/,
REQ_BUF_SIZE * sizeof(u16) /* size in bytes */);
// if data changed, activate execution request
if (0 != res)
client_requests[index].flag_exec_req = 1;
}
// copy from plcv_buffer to coms_buffer
memcpy((void *)client_requests[index].coms_buffer /* destination */,
(void *)client_requests[index].plcv_buffer /* source */,
REQ_BUF_SIZE * sizeof(u16) /* size in bytes */);
pthread_mutex_unlock(&(client_requests[index].coms_buf_mutex));
}
}
/* if the user program set the execution request flag, then activate the thread
* that handles this Modbus client transaction so it gets a chance to be executed
* (but don't activate the thread if it has already been activated!)
*
* NOTE that we do this, for both the IN and OUT mapped location, under this
* __publish_() function. The scan cycle of the PLC works as follows:
* - call __retrieve_()
* - execute user programs
* - call __publish_()
* - insert <delay> until time to start next periodic/cyclic scan cycle
*
* In an attempt to be able to run the MB transactions during the <delay>
* interval in which not much is going on, we handle the user program
* requests to execute a specific MB transaction in this __publish_()
* function.
*/
if ((client_requests[index].flag_exec_req != 0) && (0 == client_requests[index].flag_exec_started)) {
int client_node_id = client_requests[index].client_node_id;
/* We TRY to signal the client thread.
* We do this because this function can be called at the end of the PLC scan cycle
* and we don't want it to block at that time.
*/
if (pthread_mutex_trylock(&(client_nodes[client_node_id].mutex)) == 0) {
client_nodes[client_node_id].execute_req = 1; // tell the thread to execute
pthread_cond_signal (&(client_nodes[client_node_id].condv));
pthread_mutex_unlock(&(client_nodes[client_node_id].mutex));
/* - upon success, set flag_exec_started
* - both flags (flag_exec_req and flag_exec_started) will be reset
* once the transaction has completed.
*/
client_requests[index].flag_exec_started = 1;
} else {
/* The mutex is locked => the client thread is currently executing MB transactions.
* We will try to activate it in the next PLC cycle...
* For now, do nothing.
*/
}
}
}
}
void __retrieve_0 (){
int index;
for (index=0; index < NUMBER_OF_CLIENT_REQTS; index ++){
/*just do the input requests */
if (client_requests[index].req_type == req_input){
if(pthread_mutex_trylock(&(client_requests[index].coms_buf_mutex)) == 0){
// copy from coms_buffer to plcv_buffer
memcpy((void *)client_requests[index].plcv_buffer /* destination */,
(void *)client_requests[index].coms_buffer /* source */,
REQ_BUF_SIZE * sizeof(u16) /* size in bytes */);
pthread_mutex_unlock(&(client_requests[index].coms_buf_mutex));
}
}
}
}
int __cleanup_0 (){
int index, close;
int res = 0;
/* kill thread and close connections of each modbus client node */
for (index=0; index < NUMBER_OF_CLIENT_NODES; index++) {
close = 0;
if (client_nodes[index].init_state >= 5) {
// thread was launched, so we try to cancel it!
close = pthread_cancel(client_nodes[index].thread_id);
close |= pthread_join (client_nodes[index].thread_id, NULL);
if (close < 0)
fprintf(stderr, "Modbus plugin: Error closing thread for modbus client node %s\n", client_nodes[index].location);
}
res |= close;
close = 0;
if (client_nodes[index].init_state >= 4) {
// timer thread was launched, so we try to cancel it!
close = pthread_cancel(client_nodes[index].timer_thread_id);
close |= pthread_join (client_nodes[index].timer_thread_id, NULL);
if (close < 0)
fprintf(stderr, "Modbus plugin: Error closing timer thread for modbus client node %s\n", client_nodes[index].location);
}
res |= close;
close = 0;
if (client_nodes[index].init_state >= 3) {
// condition variable was created, so we try to destroy it!
close = pthread_cond_destroy(&(client_nodes[index].condv));
if (close < 0)
fprintf(stderr, "Modbus plugin: Error destroying condition variable for modbus client node %s\n", client_nodes[index].location);
}
res |= close;
close = 0;
if (client_nodes[index].init_state >= 2) {
// mutex was created, so we try to destroy it!
close = pthread_mutex_destroy(&(client_nodes[index].mutex));
if (close < 0)
fprintf(stderr, "Modbus plugin: Error destroying mutex for modbus client node %s\n", client_nodes[index].location);
}
res |= close;
close = 0;
if (client_nodes[index].init_state >= 1) {
// modbus client node was created, so we try to close it!
close = mb_master_close (client_nodes[index].mb_nd);
if (close < 0){
fprintf(stderr, "Modbus plugin: Error closing modbus client node %s\n", client_nodes[index].location);
// We try to shut down as much as possible, so we do not return noW!
}
client_nodes[index].mb_nd = -1;
}
res |= close;
client_nodes[index].init_state = 0;
}
//fprintf(stderr, "Modbus plugin: __cleanup_%s() 5 close=%d res=%d\n", client_nodes[index].location, close, res);
/* kill thread and close connections of each modbus server node */
for (index=0; index < NUMBER_OF_SERVER_NODES; index++) {
close = 0;
if (server_nodes[index].init_state >= 2) {
// thread was launched, so we try to cancel it!
close = pthread_cancel(server_nodes[index].thread_id);
close |= pthread_join (server_nodes[index].thread_id, NULL);
if (close < 0)
fprintf(stderr, "Modbus plugin: Error closing thread for modbus server %s\n", server_nodes[index].location);
}
res |= close;
close = 0;
if (server_nodes[index].init_state >= 1) {
// modbus server node was created, so we try to close it!
close = mb_slave_close (server_nodes[index].mb_nd);
if (close < 0) {
fprintf(stderr, "Modbus plugin: Error closing node for modbus server %s (%d)\n", server_nodes[index].location, server_nodes[index].mb_nd);
// We try to shut down as much as possible, so we do not return noW!
}
server_nodes[index].mb_nd = -1;
}
res |= close;
server_nodes[index].init_state = 0;
}
/* destroy the mutex of each client request */
for (index=0; index < NUMBER_OF_CLIENT_REQTS; index ++) {
if (pthread_mutex_destroy(&(client_requests[index].coms_buf_mutex))) {
fprintf(stderr, "Modbus plugin: Error destroying request for modbus client node %s\n", client_nodes[client_requests[index].client_node_id].location);
// We try to shut down as much as possible, so we do not return noW!
res |= -1;
}
}
/* modbus library close */
//fprintf(stderr, "Shutting down modbus library...\n");
if (mb_slave_and_master_done()<0) {
fprintf(stderr, "Modbus plugin: Error shutting down modbus library\n");
res |= -1;
}
return res;
}
/**********************************************/
/** Functions for Beremiz web interface. **/
/**********************************************/
/*
* Beremiz has a program to run on the PLC (Beremiz_service.py)
* to handle downloading of compiled programs, start/stop of PLC, etc.
* (see runtime/PLCObject.py for start/stop, loading, ...)
*
* This service also includes a web server to access PLC state (start/stop)
* and to change some basic confiuration parameters.
* (see runtime/NevowServer.py for the web server)
*
* The web server allows for extensions, where additional configuration
* parameters may be changed on the running/downloaded PLC.
* Modbus plugin also comes with an extension to the web server, through
* which the basic Modbus plugin configuration parameters may be changed
*
* These parameters are changed _after_ the code (.so file) is loaded into
* memmory. These changes may be applied before (or after) the code starts
* running (i.e. before or after __init_() ets called)!
*
* The following functions are never called from other C code. They are
* called instead from the python code in runtime/Modbus_config.py, that
* implements the web server extension for configuring Modbus parameters.
*/
/* The number of Cient nodes (i.e. the number of entries in the client_nodes array)
* The number of Server nodes (i.e. the numb. of entries in the server_nodes array)
*
* These variables are also used by the Modbus web config code to determine
* whether the current loaded PLC includes the Modbus plugin
* (so it should make the Modbus parameter web interface visible to the user).
*/
const int __modbus_plugin_client_node_count = NUMBER_OF_CLIENT_NODES;
const int __modbus_plugin_server_node_count = NUMBER_OF_SERVER_NODES;
const int __modbus_plugin_param_string_size = MODBUS_PARAM_STRING_SIZE;
/* NOTE: We could have the python code in runtime/Modbus_config.py
* directly access the server_node_t and client_node_t structures,
* however this would create a tight coupling between these two
* disjoint pieces of code.
* Any change to the server_node_t or client_node_t structures would
* require the python code to be changed accordingly. I have therefore
* opted to create get/set functions, one for each parameter.
*
* We also convert the enumerated constants naf_ascii, etc...
* (from node_addr_family_t in modbus/mb_addr.h)
* into strings so as to decouple the python code that will be calling
* these functions from the Modbus library code definitions.
*/
const char *addr_type_str[] = {
[naf_ascii] = "ascii",
[naf_rtu ] = "rtu",
[naf_tcp ] = "tcp"
};
#define __safe_strcnpy(str_dest, str_orig, max_size) { \
strncpy(str_dest, str_orig, max_size); \
str_dest[max_size - 1] = '\0'; \
}
/* NOTE: The host, port and device parameters are strings that may be changed
* (by calling the following functions) after loading the compiled code
* (.so file) into memory, but before the code starts running
* (i.e. before __init_() gets called).
* This means that the host, port and device parameters may be changed
* _before_ they get mapped onto the str1 and str2 variables by __init_(),
* which is why the following functions must access the str1 and str2
* parameters directly.
*/
const char * __modbus_get_ClientNode_config_name(int nodeid) {return client_nodes[nodeid].config_name; }
const char * __modbus_get_ClientNode_host (int nodeid) {return client_nodes[nodeid].str1; }
const char * __modbus_get_ClientNode_port (int nodeid) {return client_nodes[nodeid].str2; }
const char * __modbus_get_ClientNode_device (int nodeid) {return client_nodes[nodeid].str1; }
int __modbus_get_ClientNode_baud (int nodeid) {return client_nodes[nodeid].node_address.addr.rtu.baud; }
int __modbus_get_ClientNode_parity (int nodeid) {return client_nodes[nodeid].node_address.addr.rtu.parity; }
int __modbus_get_ClientNode_stop_bits (int nodeid) {return client_nodes[nodeid].node_address.addr.rtu.stop_bits;}
u64 __modbus_get_ClientNode_comm_period(int nodeid) {return client_nodes[nodeid].comm_period; }
const char * __modbus_get_ClientNode_addr_type (int nodeid) {return addr_type_str[client_nodes[nodeid].node_address.naf];}
const char * __modbus_get_ServerNode_config_name(int nodeid) {return server_nodes[nodeid].config_name; }
const char * __modbus_get_ServerNode_host (int nodeid) {char*x=server_nodes[nodeid].str1; return (x[0]=='\0'?"#ANY#":x); }
const char * __modbus_get_ServerNode_port (int nodeid) {return server_nodes[nodeid].str2; }
const char * __modbus_get_ServerNode_device (int nodeid) {return server_nodes[nodeid].str1; }
int __modbus_get_ServerNode_baud (int nodeid) {return server_nodes[nodeid].node_address.addr.rtu.baud; }
int __modbus_get_ServerNode_parity (int nodeid) {return server_nodes[nodeid].node_address.addr.rtu.parity; }
int __modbus_get_ServerNode_stop_bits (int nodeid) {return server_nodes[nodeid].node_address.addr.rtu.stop_bits;}
u8 __modbus_get_ServerNode_slave_id (int nodeid) {return server_nodes[nodeid].slave_id; }
const char * __modbus_get_ServerNode_addr_type (int nodeid) {return addr_type_str[server_nodes[nodeid].node_address.naf];}
void __modbus_set_ClientNode_host (int nodeid, const char * value) {__safe_strcnpy(client_nodes[nodeid].str1, value, MODBUS_PARAM_STRING_SIZE);}
void __modbus_set_ClientNode_port (int nodeid, const char * value) {__safe_strcnpy(client_nodes[nodeid].str2, value, MODBUS_PARAM_STRING_SIZE);}
void __modbus_set_ClientNode_device (int nodeid, const char * value) {__safe_strcnpy(client_nodes[nodeid].str1, value, MODBUS_PARAM_STRING_SIZE);}
void __modbus_set_ClientNode_baud (int nodeid, int value) {client_nodes[nodeid].node_address.addr.rtu.baud = value;}
void __modbus_set_ClientNode_parity (int nodeid, int value) {client_nodes[nodeid].node_address.addr.rtu.parity = value;}
void __modbus_set_ClientNode_stop_bits (int nodeid, int value) {client_nodes[nodeid].node_address.addr.rtu.stop_bits = value;}
void __modbus_set_ClientNode_comm_period(int nodeid, u64 value) {client_nodes[nodeid].comm_period = value;}
void __modbus_set_ServerNode_host (int nodeid, const char * value) {if (strcmp(value,"#ANY#")==0) value = "";
__safe_strcnpy(server_nodes[nodeid].str1, value, MODBUS_PARAM_STRING_SIZE);}
void __modbus_set_ServerNode_port (int nodeid, const char * value) {__safe_strcnpy(server_nodes[nodeid].str2, value, MODBUS_PARAM_STRING_SIZE);}
void __modbus_set_ServerNode_device (int nodeid, const char * value) {__safe_strcnpy(server_nodes[nodeid].str1, value, MODBUS_PARAM_STRING_SIZE);}
void __modbus_set_ServerNode_baud (int nodeid, int value) {server_nodes[nodeid].node_address.addr.rtu.baud = value;}
void __modbus_set_ServerNode_parity (int nodeid, int value) {server_nodes[nodeid].node_address.addr.rtu.parity = value;}
void __modbus_set_ServerNode_stop_bits (int nodeid, int value) {server_nodes[nodeid].node_address.addr.rtu.stop_bits = value;}
void __modbus_set_ServerNode_slave_id (int nodeid, u8 value) {server_nodes[nodeid].slave_id = value;}
/* File generated by Beremiz (PlugGenerate_C method of modbus Plugin instance) */
/*
* Copyright (c) 2016 Mario de Sousa (msousa@fe.up.pt)
*
* This file is part of the Modbus library for Beremiz and matiec.
*
* This Modbus library is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
* General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this Modbus library. If not, see <http://www.gnu.org/licenses/>.
*
* This code is made available on the understanding that it will not be
* used in safety-critical situations without a full and competent review.
*/
#include "mb_addr.h"
#include "mb_tcp_private.h"
#include "mb_master_private.h"
#define DEF_REQ_SEND_RETRIES 0
#define MODBUS_PARAM_STRING_SIZE 64
// Used by the Modbus server node
#define MEM_AREA_SIZE 65536
typedef struct{
u16 ro_bits [MEM_AREA_SIZE];
u16 rw_bits [MEM_AREA_SIZE];
u16 ro_words[MEM_AREA_SIZE];
u16 rw_words[MEM_AREA_SIZE];
/* Two flags to count the number of Modbus requests (read and write) we have
* successfully received from any remote Modbus master.
* Two boolean flags that are set whenever we successfully process a
* Modbus request sent from a remote client.
* These flags will be mapped onto located variables
* so the user's IEC 61131-3 code can check whether we are being
* polled by a Modbus master.
* The counters will roll over to 0 upon reaching maximum value.
* The user will probably periodically reset the boolean flags to false,
* and use this as a communication timeout
* (when it remains false in two consecutive periods)
*
* u8 for BOOL variable/flag
* u32 for UDINT variable/counter
*/
u8 flag_write_req_flag;
u8 flag_read_req_flag;
u32 flag_write_req_counter;
u32 flag_read_req_counter;
} server_mem_t;
/*
* Beremiz has a program to run on the PLC (Beremiz_service.py)
* to handle downloading of compiled programs, start/stop of PLC, etc.
* (see runtime/PLCObject.py for start/stop, loading, ...)
*
* This service also includes a web server to access PLC state (start/stop)
* and to change some basic confiuration parameters.
* (see runtime/NevowServer.py for the web server)
*
* The web server allows for extensions, where additional configuration
* parameters may be changed on the running/downloaded PLC.
* Modbus plugin also comes with an extension to the web server, through
* which the basic Modbus plugin configuration parameters may be changed
*
* This means that most values in the server_node_t and client_node_t
* may be changed after the co,piled code (.so file) is loaded into
* memory, and before the code starts executing.
* Since the we will also want to change the host and port (TCP) and the
* serial device (RTU) at this time, it is best if we allocate memory for
* these strings that may be overwritten by the web server (i.e., do not use
* const strings) in the server_node_t and client_node_t structures.
*
* The following structure members
* - node_addr_t.addr.tcp.host
* - node_addr_t.addr.tcp.service (i.e. the port)
* - node_addr_t.addr.rtu.device
* are all char *, and do not allocate memory for the strings.
*
* We therefore include two generic char arrays, str1 and str2,
* that will store the above strings, and the C code will initiliaze
* the node_addre_t.addr string pointers to these strings.
* i.e., either addr.rtu.device will point to str1,
* or
* addr.tcp.host and addr.tcp.service
* will point to str1 and str2 respectively
*/
typedef struct{
const char *location;
const char *config_name;
char str1[MODBUS_PARAM_STRING_SIZE];
char str2[MODBUS_PARAM_STRING_SIZE];
u8 slave_id;
node_addr_t node_address;
int mb_nd; // modbus library node used for this server
int init_state; // store how far along the server's initialization has progressed
/* entries from this point forward are not statically initialized when the variable is declared */
/* they will be initialized by the code itself in the init() function */
pthread_t thread_id; // thread handling this server
server_mem_t mem_area;
} server_node_t;
// Used by the Modbus client node
typedef struct{
const char *location;
const char *config_name;
char str1[MODBUS_PARAM_STRING_SIZE];
char str2[MODBUS_PARAM_STRING_SIZE];
node_addr_t node_address;
int mb_nd; // modbus library node used for this client
int init_state; // store how far along the client's initialization has progressed
u64 comm_period;// period to use when periodically sending requests to remote server
int prev_error; // error code of the last printed error message (0 when no error)
pthread_t thread_id; // thread handling all communication for this client node
pthread_t timer_thread_id; // thread handling periodical timer for this client node
pthread_mutex_t mutex; // mutex to be used with the following condition variable
pthread_cond_t condv; // used to signal the client thread when to start new modbus transactions
int execute_req; /* used, in association with condition variable,
* to signal when to send the modbus request to the server
* Note that we cannot simply rely on the condition variable to signal
* when to activate the client thread, as the call to
* pthread_cond_wait() may return without having been signaled!
* From the manual:
* Spurious wakeups from the
* pthread_cond_timedwait() or pthread_cond_wait() functions may occur.
* Since the return from pthread_cond_timedwait() or pthread_cond_wait()
* does not imply anything about the value of this predicate, the predi-
* cate should be re-evaluated upon such return.
*/
int periodic_act; /* (boolen) flag will be set when the client node's thread was activated
* (by signaling the above condition variable) by the periodic timer.
* Note that this same thread may also be activated (condition variable is signaled)
* by other sources, such as when the user program requests that a specific
* client MB transation be executed (flag_exec_req in client_request_t)
*/
} client_node_t;
// Used by the Modbus client plugin
typedef enum {
req_input,
req_output,
no_request /* just for tests to quickly disable a request */
} iotype_t;
#define REQ_BUF_SIZE 2000
typedef struct{
const char *location;
int client_node_id;
u8 slave_id;
iotype_t req_type;
u8 mb_function;
u16 address;
u16 count;
int retries;
u8 mb_error_code; // modbus error code (if any) of last executed request
u8 tn_error_code; // transaction error code (if any) of last executed request
int prev_error; // error code of the last printed error message (0 when no error)
struct timespec resp_timeout;
u8 write_on_change; // boolean flag. If true => execute MB request when data to send changes
// buffer used to store located PLC variables
u16 plcv_buffer[REQ_BUF_SIZE];
// buffer used to store data coming from / going to server
u16 coms_buffer[REQ_BUF_SIZE];
pthread_mutex_t coms_buf_mutex; // mutex to access coms_buffer[]
/* boolean flag that will be mapped onto a (BOOL) located variable
* (u8 because IEC 61131-3 BOOL are mapped onto u8 in C code! )
* -> allow PLC program to request when to start the MB transaction
* -> will be reset once the MB transaction has completed
*/
u8 flag_exec_req;
/* flag that works in conjunction with flag_exec_req
* (does not really need to be u8 as it is not mapped onto a located variable. )
* -> used by internal logic to indicate that the client thread
* that will be executing the MB transaction
* requested by flag exec_req has already been activated.
* -> will be reset once the MB transaction has completed
*/
u8 flag_exec_started;
/* flag that will be mapped onto a (BYTE) located variable
* (u8 because the flag is a BYTE! )
* -> will store the result of the last executed MB transaction
* 1 -> error accessing IP network, or serial interface
* 2 -> reply received from server was an invalid frame
* 3 -> server did not reply before timeout expired
* 4 -> server returned a valid Modbus error frame
* -> will be reset (set to 0) once this MB transaction has completed sucesfully
*
* In other words, this variable is a copy of tn_error_code, reset after each request attempt completes.
* We map this copy (instead of tn_error_code) onto a located variable in case the user program decides
* to overwrite its value and mess up the plugin logic.
*/
u8 flag_tn_error_code;
/* flag that will be mapped onto a (BYTE) located variable
* (u8 because the flag is a BYTE! )
* -> if flag_tn_error_code is 4, this flag will store the MB error code returned by the MB server in a MB error frame
* -> will be reset (set to 0) once this MB transaction has completed succesfully
*
* In other words, this variable is a copy of mb_error_code, reset after each request attempt completes.
* We map this copy (instead of mb_error_code) onto a located variable in case the user program decides
* to overwrite its value and mess up the plugin logic.
*/
u8 flag_mb_error_code;
} client_request_t;
/* The total number of nodes, needed to support _all_ instances of the modbus plugin */
#define TOTAL_TCPNODE_COUNT 11
#define TOTAL_RTUNODE_COUNT 0
#define TOTAL_ASCNODE_COUNT 0
/* Values for instance 0 of the modbus plugin */
#define MAX_NUMBER_OF_TCPCLIENTS 10
#define NUMBER_OF_TCPSERVER_NODES 0
#define NUMBER_OF_TCPCLIENT_NODES 1
#define NUMBER_OF_TCPCLIENT_REQTS 4
#define NUMBER_OF_RTUSERVER_NODES 0
#define NUMBER_OF_RTUCLIENT_NODES 0
#define NUMBER_OF_RTUCLIENT_REQTS 0
#define NUMBER_OF_ASCIISERVER_NODES 0
#define NUMBER_OF_ASCIICLIENT_NODES 0
#define NUMBER_OF_ASCIICLIENT_REQTS 0
#define NUMBER_OF_SERVER_NODES (NUMBER_OF_TCPSERVER_NODES + \
NUMBER_OF_RTUSERVER_NODES + \
NUMBER_OF_ASCIISERVER_NODES)
#define NUMBER_OF_CLIENT_NODES (NUMBER_OF_TCPCLIENT_NODES + \
NUMBER_OF_RTUCLIENT_NODES + \
NUMBER_OF_ASCIICLIENT_NODES)
#define NUMBER_OF_CLIENT_REQTS (NUMBER_OF_TCPCLIENT_REQTS + \
NUMBER_OF_RTUCLIENT_REQTS + \
NUMBER_OF_ASCIICLIENT_REQTS)
/*initialization following all parameters given by user in application*/
static client_node_t client_nodes[NUMBER_OF_CLIENT_NODES] = {
/*node 0.0*/
{"0.0", "Modbus TCP Client 0.0", "192.168.0.78", "1502", {naf_tcp, {.tcp = {NULL, NULL, DEF_CLOSE_ON_SILENCE}}}, -1 /* mb_nd */, 0 /* init_state */, 1000 /* communication period */, 0 /* prev_error */}
};
static client_request_t client_requests[NUMBER_OF_CLIENT_REQTS] = {
/*request 0_0_0*/
{"0_0_0", 0, 0, req_output, 5, 0 , 1,
DEF_REQ_SEND_RETRIES, 0 /* mb_error_code */, 0 /* tn_error_code */, 0 /* prev_code */, {0, 30000000} /* timeout */, 0 /* write_on_change */,
{0}, {0}},
/*request 0_0_1*/
{"0_0_1", 0, 0, req_output, 5, 1 , 1,
DEF_REQ_SEND_RETRIES, 0 /* mb_error_code */, 0 /* tn_error_code */, 0 /* prev_code */, {0, 30000000} /* timeout */, 0 /* write_on_change */,
{0}, {0}},
/*request 0_0_2*/
{"0_0_2", 0, 0, req_output, 5, 2 , 1,
DEF_REQ_SEND_RETRIES, 0 /* mb_error_code */, 0 /* tn_error_code */, 0 /* prev_code */, {0, 30000000} /* timeout */, 0 /* write_on_change */,
{0}, {0}},
/*request 0_0_3*/
{"0_0_3", 0, 0, req_output, 5, 3 , 1,
DEF_REQ_SEND_RETRIES, 0 /* mb_error_code */, 0 /* tn_error_code */, 0 /* prev_code */, {0, 30000000} /* timeout */, 0 /* write_on_change */,
{0}, {0}}
};
static server_node_t server_nodes[NUMBER_OF_SERVER_NODES] = {
}
;
/*******************/
/*located variables*/
/*******************/
u16 *__QX0_0_0_0 = &client_requests[0].plcv_buffer[0];
u16 *__QX0_0_1_1 = &client_requests[1].plcv_buffer[0];
u16 *__QX0_0_2_2 = &client_requests[2].plcv_buffer[0];
u16 *__QX0_0_3_3 = &client_requests[3].plcv_buffer[0];
void LOGGER_init__(LOGGER *data__, BOOL retain) {
__INIT_VAR(data__->EN,__BOOL_LITERAL(TRUE),retain)
__INIT_VAR(data__->ENO,__BOOL_LITERAL(TRUE),retain)
__INIT_VAR(data__->TRIG,__BOOL_LITERAL(FALSE),retain)
__INIT_VAR(data__->MSG,__STRING_LITERAL(0,""),retain)
__INIT_VAR(data__->LEVEL,LOGLEVEL__INFO,retain)
__INIT_VAR(data__->TRIG0,__BOOL_LITERAL(FALSE),retain)
}
// Code part
void LOGGER_body__(LOGGER *data__) {
// Control execution
if (!__GET_VAR(data__->EN)) {
__SET_VAR(data__->,ENO,,__BOOL_LITERAL(FALSE));
return;
}
else {
__SET_VAR(data__->,ENO,,__BOOL_LITERAL(TRUE));
}
// Initialise TEMP variables
if ((__GET_VAR(data__->TRIG,) && !(__GET_VAR(data__->TRIG0,)))) {
#define GetFbVar(var,...) __GET_VAR(data__->var,__VA_ARGS__)
#define SetFbVar(var,val,...) __SET_VAR(data__->,var,__VA_ARGS__,val)
LogMessage(GetFbVar(LEVEL),(char*)GetFbVar(MSG, .body),GetFbVar(MSG, .len));
#undef GetFbVar
#undef SetFbVar
;
};
__SET_VAR(data__->,TRIG0,,__GET_VAR(data__->TRIG,));
goto __end;
__end:
return;
} // LOGGER_body__()
void PYTHON_EVAL_init__(PYTHON_EVAL *data__, BOOL retain) {
__INIT_VAR(data__->EN,__BOOL_LITERAL(TRUE),retain)
__INIT_VAR(data__->ENO,__BOOL_LITERAL(TRUE),retain)
__INIT_VAR(data__->TRIG,__BOOL_LITERAL(FALSE),retain)
__INIT_VAR(data__->CODE,__STRING_LITERAL(0,""),retain)
__INIT_VAR(data__->ACK,__BOOL_LITERAL(FALSE),retain)
__INIT_VAR(data__->RESULT,__STRING_LITERAL(0,""),retain)
__INIT_VAR(data__->STATE,0,retain)
__INIT_VAR(data__->BUFFER,__STRING_LITERAL(0,""),retain)
__INIT_VAR(data__->PREBUFFER,__STRING_LITERAL(0,""),retain)
__INIT_VAR(data__->TRIGM1,__BOOL_LITERAL(FALSE),retain)
__INIT_VAR(data__->TRIGGED,__BOOL_LITERAL(FALSE),retain)
}
// Code part
void PYTHON_EVAL_body__(PYTHON_EVAL *data__) {
// Control execution
if (!__GET_VAR(data__->EN)) {
__SET_VAR(data__->,ENO,,__BOOL_LITERAL(FALSE));
return;
}
else {
__SET_VAR(data__->,ENO,,__BOOL_LITERAL(TRUE));
}
// Initialise TEMP variables
__IL_DEFVAR_T __IL_DEFVAR;
__IL_DEFVAR_T __IL_DEFVAR_BACK;
#define GetFbVar(var,...) __GET_VAR(data__->var,__VA_ARGS__)
#define SetFbVar(var,val,...) __SET_VAR(data__->,var,__VA_ARGS__,val)
extern void __PythonEvalFB(int, PYTHON_EVAL*);__PythonEvalFB(0, data__);
#undef GetFbVar
#undef SetFbVar
;
goto __end;
__end:
return;
} // PYTHON_EVAL_body__()
void PYTHON_POLL_init__(PYTHON_POLL *data__, BOOL retain) {
__INIT_VAR(data__->EN,__BOOL_LITERAL(TRUE),retain)
__INIT_VAR(data__->ENO,__BOOL_LITERAL(TRUE),retain)
__INIT_VAR(data__->TRIG,__BOOL_LITERAL(FALSE),retain)
__INIT_VAR(data__->CODE,__STRING_LITERAL(0,""),retain)
__INIT_VAR(data__->ACK,__BOOL_LITERAL(FALSE),retain)
__INIT_VAR(data__->RESULT,__STRING_LITERAL(0,""),retain)
__INIT_VAR(data__->STATE,0,retain)
__INIT_VAR(data__->BUFFER,__STRING_LITERAL(0,""),retain)
__INIT_VAR(data__->PREBUFFER,__STRING_LITERAL(0,""),retain)
__INIT_VAR(data__->TRIGM1,__BOOL_LITERAL(FALSE),retain)
__INIT_VAR(data__->TRIGGED,__BOOL_LITERAL(FALSE),retain)
}
// Code part
void PYTHON_POLL_body__(PYTHON_POLL *data__) {
// Control execution
if (!__GET_VAR(data__->EN)) {
__SET_VAR(data__->,ENO,,__BOOL_LITERAL(FALSE));
return;
}
else {
__SET_VAR(data__->,ENO,,__BOOL_LITERAL(TRUE));
}
// Initialise TEMP variables
__IL_DEFVAR_T __IL_DEFVAR;
__IL_DEFVAR_T __IL_DEFVAR_BACK;
#define GetFbVar(var,...) __GET_VAR(data__->var,__VA_ARGS__)
#define SetFbVar(var,val,...) __SET_VAR(data__->,var,__VA_ARGS__,val)
extern void __PythonEvalFB(int, PYTHON_EVAL*);__PythonEvalFB(1,(PYTHON_EVAL*)(void*)data__);
#undef GetFbVar
#undef SetFbVar
;
goto __end;
__end:
return;
} // PYTHON_POLL_body__()
void PYTHON_GEAR_init__(PYTHON_GEAR *data__, BOOL retain) {
__INIT_VAR(data__->EN,__BOOL_LITERAL(TRUE),retain)
__INIT_VAR(data__->ENO,__BOOL_LITERAL(TRUE),retain)
__INIT_VAR(data__->N,0,retain)
__INIT_VAR(data__->TRIG,__BOOL_LITERAL(FALSE),retain)
__INIT_VAR(data__->CODE,__STRING_LITERAL(0,""),retain)
__INIT_VAR(data__->ACK,__BOOL_LITERAL(FALSE),retain)
__INIT_VAR(data__->RESULT,__STRING_LITERAL(0,""),retain)
PYTHON_EVAL_init__(&data__->PY_EVAL,retain);
__INIT_VAR(data__->COUNTER,0,retain)
__INIT_VAR(data__->_TMP_ADD10_OUT,0,retain)
__INIT_VAR(data__->_TMP_EQ13_OUT,__BOOL_LITERAL(FALSE),retain)
__INIT_VAR(data__->_TMP_SEL15_OUT,0,retain)
__INIT_VAR(data__->_TMP_AND7_OUT,__BOOL_LITERAL(FALSE),retain)
}
// Code part
void PYTHON_GEAR_body__(PYTHON_GEAR *data__) {
// Control execution
if (!__GET_VAR(data__->EN)) {
__SET_VAR(data__->,ENO,,__BOOL_LITERAL(FALSE));
return;
}
else {
__SET_VAR(data__->,ENO,,__BOOL_LITERAL(TRUE));
}
// Initialise TEMP variables
__SET_VAR(data__->,_TMP_ADD10_OUT,,ADD__UINT__UINT(
(BOOL)__BOOL_LITERAL(TRUE),
NULL,
(UINT)2,
(UINT)__GET_VAR(data__->COUNTER,),
(UINT)1));
__SET_VAR(data__->,_TMP_EQ13_OUT,,EQ__BOOL__UINT(
(BOOL)__BOOL_LITERAL(TRUE),
NULL,
(UINT)2,
(UINT)__GET_VAR(data__->N,),
(UINT)__GET_VAR(data__->_TMP_ADD10_OUT,)));
__SET_VAR(data__->,_TMP_SEL15_OUT,,SEL__UINT__BOOL__UINT__UINT(
(BOOL)__BOOL_LITERAL(TRUE),
NULL,
(BOOL)__GET_VAR(data__->_TMP_EQ13_OUT,),
(UINT)__GET_VAR(data__->_TMP_ADD10_OUT,),
(UINT)0));
__SET_VAR(data__->,COUNTER,,__GET_VAR(data__->_TMP_SEL15_OUT,));
__SET_VAR(data__->,_TMP_AND7_OUT,,AND__BOOL__BOOL(
(BOOL)__BOOL_LITERAL(TRUE),
NULL,
(UINT)2,
(BOOL)__GET_VAR(data__->_TMP_EQ13_OUT,),
(BOOL)__GET_VAR(data__->TRIG,)));
__SET_VAR(data__->PY_EVAL.,TRIG,,__GET_VAR(data__->_TMP_AND7_OUT,));
__SET_VAR(data__->PY_EVAL.,CODE,,__GET_VAR(data__->CODE,));
PYTHON_EVAL_body__(&data__->PY_EVAL);
__SET_VAR(data__->,ACK,,__GET_VAR(data__->PY_EVAL.ACK,));
__SET_VAR(data__->,RESULT,,__GET_VAR(data__->PY_EVAL.RESULT,));
goto __end;
__end:
return;
} // PYTHON_GEAR_body__()
void COUNTERST_init__(COUNTERST *data__, BOOL retain) {
__INIT_VAR(data__->EN,__BOOL_LITERAL(TRUE),retain)
__INIT_VAR(data__->ENO,__BOOL_LITERAL(TRUE),retain)
__INIT_VAR(data__->RESET,__BOOL_LITERAL(FALSE),retain)
__INIT_VAR(data__->OUT1,0,retain)
__INIT_VAR(data__->OUT0,0,retain)
__INIT_VAR(data__->CNT1,0,retain)
__INIT_VAR(data__->CNT0,0,retain)
__INIT_EXTERNAL(INT,RESETCOUNTERVALUE,data__->RESETCOUNTERVALUE,retain)
__INIT_EXTERNAL(BOOL,RELAY0VALUE,data__->RELAY0VALUE,retain)
__INIT_EXTERNAL(BOOL,RELAY1VALUE,data__->RELAY1VALUE,retain)
__INIT_EXTERNAL(BOOL,RELAY2VALUE,data__->RELAY2VALUE,retain)
__INIT_EXTERNAL(BOOL,RELAY3VALUE,data__->RELAY3VALUE,retain)
}
// Code part
void COUNTERST_body__(COUNTERST *data__) {
// Control execution
if (!__GET_VAR(data__->EN)) {
__SET_VAR(data__->,ENO,,__BOOL_LITERAL(FALSE));
return;
}
else {
__SET_VAR(data__->,ENO,,__BOOL_LITERAL(TRUE));
}
// Initialise TEMP variables
if (__GET_VAR(data__->RESET,)) {
__SET_VAR(data__->,CNT0,,__GET_EXTERNAL(data__->RESETCOUNTERVALUE,));
__SET_VAR(data__->,CNT1,,__GET_EXTERNAL(data__->RESETCOUNTERVALUE,));
__SET_VAR(data__->,RESET,,__BOOL_LITERAL(FALSE));
} else {
__SET_VAR(data__->,CNT0,,(__GET_VAR(data__->CNT0,) + 1));
__SET_VAR(data__->,CNT1,,(__GET_VAR(data__->CNT1,) + 1));
if ((__GET_VAR(data__->CNT1,) > 10)) {
__SET_EXTERNAL(data__->,RELAY0VALUE,,__BOOL_LITERAL(TRUE));
__SET_EXTERNAL(data__->,RELAY1VALUE,,__BOOL_LITERAL(TRUE));
} else {
__SET_EXTERNAL(data__->,RELAY0VALUE,,__BOOL_LITERAL(TRUE));
__SET_EXTERNAL(data__->,RELAY1VALUE,,__BOOL_LITERAL(FALSE));
};
if ((__GET_VAR(data__->CNT1,) > 15)) {
__SET_EXTERNAL(data__->,RELAY1VALUE,,__BOOL_LITERAL(FALSE));
__SET_VAR(data__->,CNT1,,0);
};
};
__SET_VAR(data__->,OUT1,,__GET_VAR(data__->CNT1,));
__SET_VAR(data__->,OUT0,,__GET_VAR(data__->CNT0,));
goto __end;
__end:
return;
} // COUNTERST_body__()
void PLC_PRG_init__(PLC_PRG *data__, BOOL retain) {
__INIT_VAR(data__->RESET,__BOOL_LITERAL(FALSE),retain)
__INIT_VAR(data__->CNT0,0,retain)
__INIT_VAR(data__->CNT1,0,retain)
COUNTERST_init__(&data__->COUNTERST0,retain);
}
// Code part
void PLC_PRG_body__(PLC_PRG *data__) {
// Initialise TEMP variables
__SET_VAR(data__->COUNTERST0.,RESET,,__GET_VAR(data__->RESET,));
COUNTERST_body__(&data__->COUNTERST0);
__SET_VAR(data__->,CNT1,,__GET_VAR(data__->COUNTERST0.OUT1,));
__SET_VAR(data__->,CNT0,,__GET_VAR(data__->COUNTERST0.OUT0,));
goto __end;
__end:
return;
} // PLC_PRG_body__()
#include "beremiz.h"
#ifndef __POUS_H
#define __POUS_H
#include "accessor.h"
#include "iec_std_lib.h"
__DECLARE_ENUMERATED_TYPE(LOGLEVEL,
LOGLEVEL__CRITICAL,
LOGLEVEL__WARNING,
LOGLEVEL__INFO,
LOGLEVEL__DEBUG
)
// FUNCTION_BLOCK LOGGER
// Data part
typedef struct {
// FB Interface - IN, OUT, IN_OUT variables
__DECLARE_VAR(BOOL,EN)
__DECLARE_VAR(BOOL,ENO)
__DECLARE_VAR(BOOL,TRIG)
__DECLARE_VAR(STRING,MSG)
__DECLARE_VAR(LOGLEVEL,LEVEL)
// FB private variables - TEMP, private and located variables
__DECLARE_VAR(BOOL,TRIG0)
} LOGGER;
void LOGGER_init__(LOGGER *data__, BOOL retain);
// Code part
void LOGGER_body__(LOGGER *data__);
// FUNCTION_BLOCK PYTHON_EVAL
// Data part
typedef struct {
// FB Interface - IN, OUT, IN_OUT variables
__DECLARE_VAR(BOOL,EN)
__DECLARE_VAR(BOOL,ENO)
__DECLARE_VAR(BOOL,TRIG)
__DECLARE_VAR(STRING,CODE)
__DECLARE_VAR(BOOL,ACK)
__DECLARE_VAR(STRING,RESULT)
// FB private variables - TEMP, private and located variables
__DECLARE_VAR(DWORD,STATE)
__DECLARE_VAR(STRING,BUFFER)
__DECLARE_VAR(STRING,PREBUFFER)
__DECLARE_VAR(BOOL,TRIGM1)
__DECLARE_VAR(BOOL,TRIGGED)
} PYTHON_EVAL;
void PYTHON_EVAL_init__(PYTHON_EVAL *data__, BOOL retain);
// Code part
void PYTHON_EVAL_body__(PYTHON_EVAL *data__);
// FUNCTION_BLOCK PYTHON_POLL
// Data part
typedef struct {
// FB Interface - IN, OUT, IN_OUT variables
__DECLARE_VAR(BOOL,EN)
__DECLARE_VAR(BOOL,ENO)
__DECLARE_VAR(BOOL,TRIG)
__DECLARE_VAR(STRING,CODE)
__DECLARE_VAR(BOOL,ACK)
__DECLARE_VAR(STRING,RESULT)
// FB private variables - TEMP, private and located variables
__DECLARE_VAR(DWORD,STATE)
__DECLARE_VAR(STRING,BUFFER)
__DECLARE_VAR(STRING,PREBUFFER)
__DECLARE_VAR(BOOL,TRIGM1)
__DECLARE_VAR(BOOL,TRIGGED)
} PYTHON_POLL;
void PYTHON_POLL_init__(PYTHON_POLL *data__, BOOL retain);
// Code part
void PYTHON_POLL_body__(PYTHON_POLL *data__);
// FUNCTION_BLOCK PYTHON_GEAR
// Data part
typedef struct {
// FB Interface - IN, OUT, IN_OUT variables
__DECLARE_VAR(BOOL,EN)
__DECLARE_VAR(BOOL,ENO)
__DECLARE_VAR(UINT,N)
__DECLARE_VAR(BOOL,TRIG)
__DECLARE_VAR(STRING,CODE)
__DECLARE_VAR(BOOL,ACK)
__DECLARE_VAR(STRING,RESULT)
// FB private variables - TEMP, private and located variables
PYTHON_EVAL PY_EVAL;
__DECLARE_VAR(UINT,COUNTER)
__DECLARE_VAR(UINT,_TMP_ADD10_OUT)
__DECLARE_VAR(BOOL,_TMP_EQ13_OUT)
__DECLARE_VAR(UINT,_TMP_SEL15_OUT)
__DECLARE_VAR(BOOL,_TMP_AND7_OUT)
} PYTHON_GEAR;
void PYTHON_GEAR_init__(PYTHON_GEAR *data__, BOOL retain);
// Code part
void PYTHON_GEAR_body__(PYTHON_GEAR *data__);
// FUNCTION_BLOCK COUNTERST
// Data part
typedef struct {
// FB Interface - IN, OUT, IN_OUT variables
__DECLARE_VAR(BOOL,EN)
__DECLARE_VAR(BOOL,ENO)
__DECLARE_VAR(BOOL,RESET)
__DECLARE_VAR(INT,OUT1)
__DECLARE_VAR(INT,OUT0)
// FB private variables - TEMP, private and located variables
__DECLARE_VAR(INT,CNT1)
__DECLARE_VAR(INT,CNT0)
__DECLARE_EXTERNAL(INT,RESETCOUNTERVALUE)
__DECLARE_EXTERNAL(BOOL,RELAY0VALUE)
__DECLARE_EXTERNAL(BOOL,RELAY1VALUE)
__DECLARE_EXTERNAL(BOOL,RELAY2VALUE)
__DECLARE_EXTERNAL(BOOL,RELAY3VALUE)
} COUNTERST;
void COUNTERST_init__(COUNTERST *data__, BOOL retain);
// Code part
void COUNTERST_body__(COUNTERST *data__);
// PROGRAM PLC_PRG
// Data part
typedef struct {
// PROGRAM Interface - IN, OUT, IN_OUT variables
__DECLARE_VAR(BOOL,RESET)
__DECLARE_VAR(INT,CNT0)
__DECLARE_VAR(INT,CNT1)
// PROGRAM private variables - TEMP, private and located variables
COUNTERST COUNTERST0;
} PLC_PRG;
void PLC_PRG_init__(PLC_PRG *data__, BOOL retain);
// Code part
void PLC_PRG_body__(PLC_PRG *data__);
#endif //__POUS_H
// Programs
0;CONFIG.RESOURCE1.INSTANCE0;PLC_PRG;
// Variables
0;VAR;CONFIG.RESETCOUNTERVALUE;CONFIG.RESETCOUNTERVALUE;INT;INT;
1;OUT;CONFIG.RELAY0VALUE;CONFIG.RELAY0VALUE;BOOL;BOOL;
2;OUT;CONFIG.RELAY1VALUE;CONFIG.RELAY1VALUE;BOOL;BOOL;
3;OUT;CONFIG.RELAY2VALUE;CONFIG.RELAY2VALUE;BOOL;BOOL;
4;OUT;CONFIG.RELAY3VALUE;CONFIG.RELAY3VALUE;BOOL;BOOL;
5;FB;CONFIG.RESOURCE1.INSTANCE0;CONFIG.RESOURCE1.INSTANCE0;PLC_PRG;;
6;VAR;CONFIG.RESOURCE1.INSTANCE0.RESET;CONFIG.RESOURCE1.INSTANCE0.RESET;BOOL;BOOL;
7;VAR;CONFIG.RESOURCE1.INSTANCE0.CNT0;CONFIG.RESOURCE1.INSTANCE0.CNT0;INT;INT;
8;VAR;CONFIG.RESOURCE1.INSTANCE0.CNT1;CONFIG.RESOURCE1.INSTANCE0.CNT1;INT;INT;
9;FB;CONFIG.RESOURCE1.INSTANCE0.COUNTERST0;CONFIG.RESOURCE1.INSTANCE0.COUNTERST0;COUNTERST;;
10;VAR;CONFIG.RESOURCE1.INSTANCE0.COUNTERST0.EN;CONFIG.RESOURCE1.INSTANCE0.COUNTERST0.EN;BOOL;BOOL;
11;VAR;CONFIG.RESOURCE1.INSTANCE0.COUNTERST0.ENO;CONFIG.RESOURCE1.INSTANCE0.COUNTERST0.ENO;BOOL;BOOL;
12;VAR;CONFIG.RESOURCE1.INSTANCE0.COUNTERST0.RESET;CONFIG.RESOURCE1.INSTANCE0.COUNTERST0.RESET;BOOL;BOOL;
13;VAR;CONFIG.RESOURCE1.INSTANCE0.COUNTERST0.OUT1;CONFIG.RESOURCE1.INSTANCE0.COUNTERST0.OUT1;INT;INT;
14;VAR;CONFIG.RESOURCE1.INSTANCE0.COUNTERST0.OUT0;CONFIG.RESOURCE1.INSTANCE0.COUNTERST0.OUT0;INT;INT;
15;VAR;CONFIG.RESOURCE1.INSTANCE0.COUNTERST0.CNT1;CONFIG.RESOURCE1.INSTANCE0.COUNTERST0.CNT1;INT;INT;
16;VAR;CONFIG.RESOURCE1.INSTANCE0.COUNTERST0.CNT0;CONFIG.RESOURCE1.INSTANCE0.COUNTERST0.CNT0;INT;INT;
17;EXT;CONFIG.RESOURCE1.INSTANCE0.COUNTERST0.RESETCOUNTERVALUE;CONFIG.RESOURCE1.INSTANCE0.COUNTERST0.RESETCOUNTERVALUE;INT;INT;
18;EXT;CONFIG.RESOURCE1.INSTANCE0.COUNTERST0.RELAY0VALUE;CONFIG.RESOURCE1.INSTANCE0.COUNTERST0.RELAY0VALUE;BOOL;BOOL;
19;EXT;CONFIG.RESOURCE1.INSTANCE0.COUNTERST0.RELAY1VALUE;CONFIG.RESOURCE1.INSTANCE0.COUNTERST0.RELAY1VALUE;BOOL;BOOL;
20;EXT;CONFIG.RESOURCE1.INSTANCE0.COUNTERST0.RELAY2VALUE;CONFIG.RESOURCE1.INSTANCE0.COUNTERST0.RELAY2VALUE;BOOL;BOOL;
21;EXT;CONFIG.RESOURCE1.INSTANCE0.COUNTERST0.RELAY3VALUE;CONFIG.RESOURCE1.INSTANCE0.COUNTERST0.RELAY3VALUE;BOOL;BOOL;
// Ticktime
20000000
#ifndef _BEREMIZ_H_
#define _BEREMIZ_H_
/* Beremiz' header file for use by extensions */
#include "iec_types.h"
#define LOG_LEVELS 4
#define LOG_CRITICAL 0
#define LOG_WARNING 1
#define LOG_INFO 2
#define LOG_DEBUG 3
extern unsigned long long common_ticktime__;
#ifdef TARGET_LOGGING_DISABLE
static inline int LogMessage(uint8_t level, char* buf, uint32_t size)
{
(void)level;
(void)buf;
(void)size;
return 0;
}
#else
int LogMessage(uint8_t level, char* buf, uint32_t size);
#endif
long AtomicCompareExchange(long* atomicvar,long compared, long exchange);
void *create_RT_to_nRT_signal(char* name);
void delete_RT_to_nRT_signal(void* handle);
int wait_RT_to_nRT_signal(void* handle);
int unblock_RT_to_nRT_signal(void* handle);
void nRT_reschedule(void);
#endif
/*******************************************/
/* FILE GENERATED BY iec2c */
/* Editing this file is not recommended... */
/*******************************************/
#include "iec_std_lib.h"
#include "accessor.h"
#include "POUS.h"
// CONFIGURATION CONFIG
__DECLARE_GLOBAL(INT,CONFIG,RESETCOUNTERVALUE)
__DECLARE_GLOBAL_LOCATION(BOOL,__QX0_0_0_0)
__DECLARE_GLOBAL_LOCATED(BOOL,CONFIG,RELAY0VALUE)
__DECLARE_GLOBAL_LOCATION(BOOL,__QX0_0_1_1)
__DECLARE_GLOBAL_LOCATED(BOOL,CONFIG,RELAY1VALUE)
__DECLARE_GLOBAL_LOCATION(BOOL,__QX0_0_2_2)
__DECLARE_GLOBAL_LOCATED(BOOL,CONFIG,RELAY2VALUE)
__DECLARE_GLOBAL_LOCATION(BOOL,__QX0_0_3_3)
__DECLARE_GLOBAL_LOCATED(BOOL,CONFIG,RELAY3VALUE)
void RESOURCE1_init__(void);
void config_init__(void) {
BOOL retain;
retain = 0;
__INIT_GLOBAL(INT,RESETCOUNTERVALUE,__INITIAL_VALUE(0),retain)
__INIT_GLOBAL_LOCATED(CONFIG,RELAY0VALUE,__QX0_0_0_0,retain)
__INIT_GLOBAL(BOOL,RELAY0VALUE,__INITIAL_VALUE(0),retain)
__INIT_GLOBAL_LOCATED(CONFIG,RELAY1VALUE,__QX0_0_1_1,retain)
__INIT_GLOBAL(BOOL,RELAY1VALUE,__INITIAL_VALUE(0),retain)
__INIT_GLOBAL_LOCATED(CONFIG,RELAY2VALUE,__QX0_0_2_2,retain)
__INIT_GLOBAL(BOOL,RELAY2VALUE,__INITIAL_VALUE(0),retain)
__INIT_GLOBAL_LOCATED(CONFIG,RELAY3VALUE,__QX0_0_3_3,retain)
__INIT_GLOBAL(BOOL,RELAY3VALUE,__INITIAL_VALUE(0),retain)
RESOURCE1_init__();
}
void RESOURCE1_run__(unsigned long tick);
void config_run__(unsigned long tick) {
RESOURCE1_run__(tick);
}
unsigned long long common_ticktime__ = 20000000ULL * 1ULL; /*ns*/
unsigned long greatest_tick_count__ = (unsigned long)0UL; /*tick*/
#include "beremiz.h"
__DECLARE_GLOBAL_PROTOTYPE(INT,RESETCOUNTERVALUE)
__DECLARE_GLOBAL_PROTOTYPE(BOOL,RELAY0VALUE)
__DECLARE_GLOBAL_PROTOTYPE(BOOL,RELAY1VALUE)
__DECLARE_GLOBAL_PROTOTYPE(BOOL,RELAY2VALUE)
__DECLARE_GLOBAL_PROTOTYPE(BOOL,RELAY3VALUE)
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# This file is part of Beremiz runtime.
#
# Copyright (C) 2020: Mario de Sousa
#
# See COPYING.Runtime file for copyrights details.
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
##############################################################################################
# This file implements an extension to the web server embedded in the Beremiz_service.py #
# runtime manager (webserver is in runtime/NevowServer.py). #
# #
# The extension implemented in this file allows for runtime configuration #
# of Modbus plugin parameters #
##############################################################################################
import json
import os
import ctypes
import string
import hashlib
from formless import annotate, webform
import runtime.NevowServer as NS
# Directory in which to store the persistent configurations
# Should be a directory that does not get wiped on reboot!
_ModbusConfFiledir = WorkingDir
# List of all Web Extension Setting nodes we are handling.
# One WebNode each for:
# - Modbus TCP client
# - Modbus TCP server
# - Modbus RTU client
# - Modbus RTU slave
# configured in the loaded PLC (i.e. the .so file loaded into memory)
# Each entry will be a dictionary. See _AddWebNode() for the details
# of the data structure in each entry.
_WebNodeList = []
class MB_StrippedString(annotate.String):
def __init__(self, *args, **kwargs):
annotate.String.__init__(self, strip = True, *args, **kwargs)
class MB_StopBits(annotate.Choice):
_choices = [0, 1, 2]
def coerce(self, val, configurable):
return int(val)
def __init__(self, *args, **kwargs):
annotate.Choice.__init__(self, choices = self._choices, *args, **kwargs)
class MB_Baud(annotate.Choice):
_choices = [110, 300, 600, 1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200]
def coerce(self, val, configurable):
return int(val)
def __init__(self, *args, **kwargs):
annotate.Choice.__init__(self, choices = self._choices, *args, **kwargs)
class MB_Parity(annotate.Choice):
# For more info on what this class really does, have a look at the code in
# file twisted/nevow/annotate.py
# grab this code from $git clone https://github.com/twisted/nevow/
#
# Warning: do _not_ name this variable choice[] without underscore, as that name is
# already used for another similar variable by the underlying class annotate.Choice
_choices = [ 0, 1, 2 ]
_label = ["none", "odd", "even"]
def choice_to_label(self, key):
#PLCObject.LogMessage("Modbus web server extension::choice_to_label() " + str(key))
return self._label[key]
def coerce(self, val, configurable):
"""Coerce a value with the help of an object, which is the object
we are configuring.
"""
# Basically, make sure the value the user introduced is valid, and transform
# into something that is valid if necessary or mark it as an error
# (by raising an exception ??).
#
# We are simply using this functions to transform the input value (a string)
# into an integer. Note that although the available options are all
# integers (0, 1 or 2), even though what is shown on the user interface
# are actually strings, i.e. the labels), these parameters are for some
# reason being parsed as strings, so we need to map them back to an
# integer.
#
#PLCObject.LogMessage("Modbus web server extension::coerce " + val )
return int(val)
def __init__(self, *args, **kwargs):
annotate.Choice.__init__(self,
choices = self._choices,
stringify = self.choice_to_label,
*args, **kwargs)
# Parameters we will need to get from the C code, but that will not be shown
# on the web interface. Common to all modbus entry types (client/server, tcp/rtu/ascii)
#
# The annotate type entry is basically useless and is completely ignored.
# We kee that entry so that this list can later be correctly merged with the
# following lists...
General_parameters = [
# param. name label ctype type annotate type
# (C code var name) (used on web interface) (C data type) (web data type)
# (annotate.String,
# annotate.Integer, ...)
("config_name" , _("") , ctypes.c_char_p, annotate.String),
("addr_type" , _("") , ctypes.c_char_p, annotate.String)
]
# Parameters we will need to get from the C code, and that _will_ be shown
# on the web interface.
TCPclient_parameters = [
# param. name label ctype type annotate type
# (C code var name) (used on web interface) (C data type) (web data type)
# (annotate.String,
# annotate.Integer, ...)
("host" , _("Remote IP Address") , ctypes.c_char_p, MB_StrippedString),
("port" , _("Remote Port Number") , ctypes.c_char_p, MB_StrippedString),
("comm_period" , _("Invocation Rate (ms)") , ctypes.c_ulonglong, annotate.Integer )
]
RTUclient_parameters = [
# param. name label ctype type annotate type
# (C code var name) (used on web interface) (C data type) (web data type)
# (annotate.String,
# annotate.Integer, ...)
("device" , _("Serial Port") , ctypes.c_char_p, MB_StrippedString),
("baud" , _("Baud Rate") , ctypes.c_int, MB_Baud ),
("parity" , _("Parity") , ctypes.c_int, MB_Parity ),
("stop_bits" , _("Stop Bits") , ctypes.c_int, MB_StopBits ),
("comm_period" , _("Invocation Rate (ms)") , ctypes.c_ulonglong, annotate.Integer)
]
TCPserver_parameters = [
# param. name label ctype type annotate type
# (C code var name) (used on web interface) (C data type) (web data type)
# (annotate.String,
# annotate.Integer, ...)
("host" , _("Local IP Address") , ctypes.c_char_p, MB_StrippedString),
("port" , _("Local Port Number") , ctypes.c_char_p, MB_StrippedString),
("slave_id" , _("Slave ID") , ctypes.c_ubyte, annotate.Integer )
]
RTUslave_parameters = [
# param. name label ctype type annotate type
# (C code var name) (used on web interface) (C data type) (web data type)
# (annotate.String,
# annotate.Integer, ...)
("device" , _("Serial Port") , ctypes.c_char_p, MB_StrippedString),
("baud" , _("Baud Rate") , ctypes.c_int, MB_Baud ),
("parity" , _("Parity") , ctypes.c_int, MB_Parity ),
("stop_bits" , _("Stop Bits") , ctypes.c_int, MB_StopBits ),
("slave_id" , _("Slave ID") , ctypes.c_ubyte, annotate.Integer)
]
# Dictionary containing List of Web viewable parameters
# Note: the dictionary key must be the same as the string returned by the
# __modbus_get_ClientNode_addr_type()
# __modbus_get_ServerNode_addr_type()
# functions implemented in C (see modbus/mb_runtime.c)
_client_WebParamListDict = {}
_client_WebParamListDict["tcp" ] = TCPclient_parameters
_client_WebParamListDict["rtu" ] = RTUclient_parameters
_client_WebParamListDict["ascii"] = [] # (Note: ascii not yet implemented in Beremiz modbus plugin)
_server_WebParamListDict = {}
_server_WebParamListDict["tcp" ] = TCPserver_parameters
_server_WebParamListDict["rtu" ] = RTUslave_parameters
_server_WebParamListDict["ascii"] = [] # (Note: ascii not yet implemented in Beremiz modbus plugin)
WebParamListDictDict = {}
WebParamListDictDict['client'] = _client_WebParamListDict
WebParamListDictDict['server'] = _server_WebParamListDict
def _SetModbusSavedConfiguration(WebNode_id, newConfig):
""" Stores a dictionary in a persistant file containing the Modbus parameter configuration """
WebNode_entry = _WebNodeList[WebNode_id]
if WebNode_entry["DefaultConfiguration"] == newConfig:
_DelModbusSavedConfiguration(WebNode_id)
WebNode_entry["ModbusSavedConfiguration"] = None
else:
# Add the addr_type and node_type to the data that will be saved to file
# This allows us to confirm the saved data contains the correct addr_type
# when loading from file
save_info = {}
save_info["addr_type"] = WebNode_entry["addr_type"]
save_info["node_type"] = WebNode_entry["node_type"]
save_info["config" ] = newConfig
filename = WebNode_entry["filename"]
with open(os.path.realpath(filename), 'w') as f:
json.dump(save_info, f, sort_keys=True, indent=4)
WebNode_entry["ModbusSavedConfiguration"] = newConfig
def _DelModbusSavedConfiguration(WebNode_id):
""" Deletes the file cotaining the persistent Modbus configuration """
filename = _WebNodeList[WebNode_id]["filename"]
if os.path.exists(filename):
os.remove(filename)
def _GetModbusSavedConfiguration(WebNode_id):
"""
Returns a dictionary containing the Modbus parameter configuration
that was last saved to file. If no file exists, or file contains
wrong addr_type (i.e. 'tcp', 'rtu' or 'ascii' -> does not match the
addr_type of the WebNode_id), then return None
"""
filename = _WebNodeList[WebNode_id]["filename"]
try:
#if os.path.isfile(filename):
save_info = json.load(open(os.path.realpath(filename)))
except Exception:
return None
if save_info["addr_type"] != _WebNodeList[WebNode_id]["addr_type"]:
return None
if save_info["node_type"] != _WebNodeList[WebNode_id]["node_type"]:
return None
if "config" not in save_info:
return None
saved_config = save_info["config"]
#if _CheckConfiguration(saved_config):
# return saved_config
#else:
# return None
return saved_config
def _GetModbusPLCConfiguration(WebNode_id):
"""
Returns a dictionary containing the current Modbus parameter configuration
stored in the C variables in the loaded PLC (.so file)
"""
current_config = {}
C_node_id = _WebNodeList[WebNode_id]["C_node_id"]
WebParamList = _WebNodeList[WebNode_id]["WebParamList"]
GetParamFuncs = _WebNodeList[WebNode_id]["GetParamFuncs"]
for par_name, x1, x2, x3 in WebParamList:
value = GetParamFuncs[par_name](C_node_id)
if value is not None:
current_config[par_name] = value
return current_config
def _SetModbusPLCConfiguration(WebNode_id, newconfig):
"""
Stores the Modbus parameter configuration into the
the C variables in the loaded PLC (.so file)
"""
C_node_id = _WebNodeList[WebNode_id]["C_node_id"]
SetParamFuncs = _WebNodeList[WebNode_id]["SetParamFuncs"]
for par_name in newconfig:
value = newconfig[par_name]
if value is not None:
SetParamFuncs[par_name](C_node_id, value)
def _GetModbusWebviewConfigurationValue(ctx, WebNode_id, argument):
"""
Callback function, called by the web interface (NevowServer.py)
to fill in the default value of each parameter of the web form
Note that the real callback function is a dynamically created function that
will simply call this function to do the work. It will also pass the WebNode_id
as a parameter.
"""
try:
return _WebNodeList[WebNode_id]["WebviewConfiguration"][argument.name]
except Exception:
return ""
def OnModbusButtonSave(**kwargs):
"""
Function called when user clicks 'Save' button in web interface
The function will configure the Modbus plugin in the PLC with the values
specified in the web interface. However, values must be validated first!
Note that this function does not get called directly. The real callback
function is the dynamic __OnButtonSave() function, which will add the
"WebNode_id" argument, and call this function to do the work.
"""
#PLCObject.LogMessage("Modbus web server extension::OnModbusButtonSave() Called")
newConfig = {}
WebNode_id = kwargs.get("WebNode_id", None)
WebParamList = _WebNodeList[WebNode_id]["WebParamList"]
for par_name, x1, x2, x3 in WebParamList:
value = kwargs.get(par_name, None)
if value is not None:
newConfig[par_name] = value
# First check if configuration is OK.
# Note that this is not currently required, as we use drop down choice menus
# for baud, parity and sop bits, so the values should always be correct!
#if not _CheckWebConfiguration(newConfig):
# return
# store to file the new configuration so that
# we can recoup the configuration the next time the PLC
# has a cold start (i.e. when Beremiz_service.py is retarted)
_SetModbusSavedConfiguration(WebNode_id, newConfig)
# Configure PLC with the current Modbus parameters
_SetModbusPLCConfiguration(WebNode_id, newConfig)
# Update the viewable configuration
# The PLC may have coerced the values on calling _SetModbusPLCConfiguration()
# so we do not set it directly to newConfig
_WebNodeList[WebNode_id]["WebviewConfiguration"] = _GetModbusPLCConfiguration(WebNode_id)
def OnModbusButtonReset(**kwargs):
"""
Function called when user clicks 'Delete' button in web interface
The function will delete the file containing the persistent
Modbus configution
"""
WebNode_id = kwargs.get("WebNode_id", None)
# Delete the file
_DelModbusSavedConfiguration(WebNode_id)
# Set the current configuration to the default (hardcoded in C)
new_config = _WebNodeList[WebNode_id]["DefaultConfiguration"]
_SetModbusPLCConfiguration(WebNode_id, new_config)
#Update the webviewconfiguration
_WebNodeList[WebNode_id]["WebviewConfiguration"] = new_config
# Reset ModbusSavedConfiguration
_WebNodeList[WebNode_id]["ModbusSavedConfiguration"] = None
def _AddWebNode(C_node_id, node_type, GetParamFuncs, SetParamFuncs):
"""
Load from the compiled code (.so file, aloready loaded into memmory)
the configuration parameters of a specific Modbus plugin node.
This function works with both client and server nodes, depending on the
Get/SetParamFunc dictionaries passed to it (either the client or the server
node versions of the Get/Set functions)
"""
WebNode_entry = {}
# Get the config_name from the C code...
config_name = GetParamFuncs["config_name"](C_node_id)
# Get the addr_type from the C code...
# addr_type will be one of "tcp", "rtu" or "ascii"
addr_type = GetParamFuncs["addr_type" ](C_node_id)
# For some operations we cannot use the config name (e.g. filename to store config)
# because the user may be using characters that are invalid for that purpose ('/' for
# example), so we create a hash of the config_name, and use that instead.
config_hash = hashlib.md5(config_name).hexdigest()
#PLCObject.LogMessage("Modbus web server extension::_AddWebNode("+str(C_node_id)+") config_name="+config_name)
# Add the new entry to the global list
# Note: it is OK, and actually necessary, to do this _before_ seting all the parameters in WebNode_entry
# WebNode_entry will be stored as a reference, so we can later insert parameters at will.
global _WebNodeList
_WebNodeList.append(WebNode_entry)
WebNode_id = len(_WebNodeList) - 1
# store all WebNode relevant data for future reference
#
# Note that "WebParamList" will reference one of:
# - TCPclient_parameters, TCPserver_parameters, RTUclient_parameters, RTUslave_parameters
WebNode_entry["C_node_id" ] = C_node_id
WebNode_entry["config_name" ] = config_name
WebNode_entry["config_hash" ] = config_hash
WebNode_entry["filename" ] = os.path.join(_ModbusConfFiledir, "Modbus_config_" + config_hash + ".json")
WebNode_entry["GetParamFuncs"] = GetParamFuncs
WebNode_entry["SetParamFuncs"] = SetParamFuncs
WebNode_entry["WebParamList" ] = WebParamListDictDict[node_type][addr_type]
WebNode_entry["addr_type" ] = addr_type # 'tcp', 'rtu', or 'ascii' (as returned by C function)
WebNode_entry["node_type" ] = node_type # 'client', 'server'
# Dictionary that contains the Modbus configuration currently being shown
# on the web interface
# This configuration will almost always be identical to the current
# configuration in the PLC (i.e., the current state stored in the
# C variables in the .so file).
# The configuration viewed on the web will only be different to the current
# configuration when the user edits the configuration, and when
# the user asks to save an edited configuration that contains an error.
WebNode_entry["WebviewConfiguration"] = None
# Upon PLC load, this Dictionary is initialised with the Modbus configuration
# hardcoded in the C file
# (i.e. the configuration inserted in Beremiz IDE when project was compiled)
WebNode_entry["DefaultConfiguration"] = _GetModbusPLCConfiguration(WebNode_id)
WebNode_entry["WebviewConfiguration"] = WebNode_entry["DefaultConfiguration"]
# Dictionary that stores the Modbus configuration currently stored in a file
# Currently only used to decide whether or not to show the "Delete" button on the
# web interface (only shown if "ModbusSavedConfiguration" is not None)
SavedConfig = _GetModbusSavedConfiguration(WebNode_id)
WebNode_entry["ModbusSavedConfiguration"] = SavedConfig
if SavedConfig is not None:
_SetModbusPLCConfiguration(WebNode_id, SavedConfig)
WebNode_entry["WebviewConfiguration"] = SavedConfig
# Define the format for the web form used to show/change the current parameters
# We first declare a dynamic function to work as callback to obtain the default values for each parameter
# Note: We transform every parameter into a string
# This is not strictly required for parameters of type annotate.Integer that will correctly
# accept the default value as an Integer python object
# This is obviously also not required for parameters of type annotate.String, that are
# always handled as strings.
# However, the annotate.Choice parameters (and all parameters that derive from it,
# sucn as Parity, Baud, etc.) require the default value as a string
# even though we store it as an integer, which is the data type expected
# by the set_***() C functions in mb_runtime.c
def __GetWebviewConfigurationValue(ctx, argument):
return str(_GetModbusWebviewConfigurationValue(ctx, WebNode_id, argument))
webFormInterface = [(name, web_dtype (label=web_label, default=__GetWebviewConfigurationValue))
for name, web_label, c_dtype, web_dtype in WebNode_entry["WebParamList"]]
# Configure the web interface to include the Modbus config parameters
def __OnButtonSave(**kwargs):
OnModbusButtonSave(WebNode_id=WebNode_id, **kwargs)
WebSettings = NS.newExtensionSetting("Modbus #"+ str(WebNode_id), config_hash)
WebSettings.addSettings(
"ModbusConfigParm" + config_hash, # name (internal, may not contain spaces, ...)
_("Modbus Configuration: ") + config_name, # description (user visible label)
webFormInterface, # fields
_("Apply"), # button label
__OnButtonSave) # callback
def __OnButtonReset(**kwargs):
return OnModbusButtonReset(WebNode_id = WebNode_id, **kwargs)
def getModbusConfigStatus():
if WebNode_entry["WebviewConfiguration"] == WebNode_entry["DefaultConfiguration"]:
return "Unchanged"
return "Modified"
WebSettings.addSettings(
"ModbusConfigDelSaved" + config_hash, # name (internal, may not contain spaces, ...)
_("Modbus Configuration: ") + config_name, # description (user visible label)
[ ("status",
annotate.String(label=_("Current state"),
immutable=True,
default=lambda *k:getModbusConfigStatus())),
], # fields (empty, no parameters required!)
_("Reset"), # button label
__OnButtonReset)
def _runtime_0_modbus_websettings_init():
"""
Callback function, called (by PLCObject.py) when a new PLC program
(i.e. XXX.so file) is transfered to the PLC runtime
and loaded into memory
"""
#PLCObject.LogMessage("Modbus web server extension::OnLoadPLC() Called...")
if PLCObject.PLClibraryHandle is None:
# PLC was loaded but we don't have access to the library of compiled code (.so lib)?
# Hmm... This shold never occur!!
return
# Get the number of Modbus Client and Servers (Modbus plugin)
# configured in the currently loaded PLC project (i.e., the .so file)
# If the "__modbus_plugin_client_node_count"
# or the "__modbus_plugin_server_node_count" C variables
# are not present in the .so file we conclude that the currently loaded
# PLC does not have the Modbus plugin included (situation (2b) described above init())
try:
# XXX TODO : stop reading from PLC .so file. This code is template code
# that can use modbus extension build data, such as client node count.
client_count = ctypes.c_int.in_dll(PLCObject.PLClibraryHandle, "__modbus_plugin_client_node_count").value
server_count = ctypes.c_int.in_dll(PLCObject.PLClibraryHandle, "__modbus_plugin_server_node_count").value
except Exception:
# Loaded PLC does not have the Modbus plugin => nothing to do
# (i.e. do _not_ configure and make available the Modbus web interface)
return
if client_count < 0: client_count = 0
if server_count < 0: server_count = 0
if (client_count == 0) and (server_count == 0):
# The Modbus plugin in the loaded PLC does not have any client and servers configured
# => nothing to do (i.e. do _not_ configure and make available the Modbus web interface)
return
# Map the get/set functions (written in C code) we will be using to get/set the configuration parameters
# Will contain references to the C functions (implemented in beremiz/modbus/mb_runtime.c)
GetClientParamFuncs = {}
SetClientParamFuncs = {}
GetServerParamFuncs = {}
SetServerParamFuncs = {}
# XXX TODO : stop reading from PLC .so file. This code is template code
# that can use modbus extension build data
for name, web_label, c_dtype, web_dtype in TCPclient_parameters + RTUclient_parameters + General_parameters:
ParamFuncName = "__modbus_get_ClientNode_" + name
GetClientParamFuncs[name] = getattr(PLCObject.PLClibraryHandle, ParamFuncName)
GetClientParamFuncs[name].restype = c_dtype
GetClientParamFuncs[name].argtypes = [ctypes.c_int]
for name, web_label, c_dtype, web_dtype in TCPclient_parameters + RTUclient_parameters:
ParamFuncName = "__modbus_set_ClientNode_" + name
SetClientParamFuncs[name] = getattr(PLCObject.PLClibraryHandle, ParamFuncName)
SetClientParamFuncs[name].restype = None
SetClientParamFuncs[name].argtypes = [ctypes.c_int, c_dtype]
# XXX TODO : stop reading from PLC .so file. This code is template code
# that can use modbus extension build data
for name, web_label, c_dtype, web_dtype in TCPserver_parameters + RTUslave_parameters + General_parameters:
ParamFuncName = "__modbus_get_ServerNode_" + name
GetServerParamFuncs[name] = getattr(PLCObject.PLClibraryHandle, ParamFuncName)
GetServerParamFuncs[name].restype = c_dtype
GetServerParamFuncs[name].argtypes = [ctypes.c_int]
for name, web_label, c_dtype, web_dtype in TCPserver_parameters + RTUslave_parameters:
ParamFuncName = "__modbus_set_ServerNode_" + name
SetServerParamFuncs[name] = getattr(PLCObject.PLClibraryHandle, ParamFuncName)
SetServerParamFuncs[name].restype = None
SetServerParamFuncs[name].argtypes = [ctypes.c_int, c_dtype]
for node_id in range(client_count):
_AddWebNode(node_id, "client" ,GetClientParamFuncs, SetClientParamFuncs)
for node_id in range(server_count):
_AddWebNode(node_id, "server", GetServerParamFuncs, SetServerParamFuncs)
def _runtime_0_modbus_websettings_cleanup():
"""
Callback function, called (by PLCObject.py) when a PLC program is unloaded from memory
"""
#PLCObject.LogMessage("Modbus web server extension::OnUnLoadPLC() Called...")
# Delete the Modbus specific web interface extensions
# (Safe to ask to delete, even if it has not been added!)
global _WebNodeList
for index, WebNode_entry in enumerate(_WebNodeList):
config_hash = WebNode_entry["config_hash"]
NS.removeExtensionSetting(config_hash)
# Dele all entries...
_WebNodeList = []
FUNCTION_BLOCK CounterST
VAR_INPUT
Reset : BOOL;
END_VAR
VAR_OUTPUT
Out1 : INT;
Out0 : INT;
END_VAR
VAR
Cnt1 : INT;
Cnt0 : INT;
END_VAR
VAR_EXTERNAL
ResetCounterValue : INT;
Relay0Value : BOOL;
Relay1Value : BOOL;
Relay2Value : BOOL;
Relay3Value : BOOL;
END_VAR
IF Reset THEN
Cnt0 := ResetCounterValue;
Cnt1 := ResetCounterValue;
Reset := False;
ELSE
Cnt0 := Cnt0 + 1;
Cnt1 := Cnt1 + 1;
IF Cnt1 > 10 THEN
Relay0Value := True;
Relay1Value := True;
ELSE
Relay0Value := True;
Relay1Value := False;
END_IF;
IF Cnt1 > 15 THEN
Relay1Value := False;
Cnt1 :=0;
END_IF;
END_IF;
Out1 := Cnt1;
Out0 := Cnt0;
END_FUNCTION_BLOCK
PROGRAM plc_prg
VAR_INPUT
Reset : BOOL;
END_VAR
VAR_OUTPUT
Cnt0 : INT;
Cnt1 : INT;
END_VAR
VAR
CounterST0 : CounterST;
END_VAR
CounterST0(Reset := Reset);
Cnt1 := CounterST0.Out1;
Cnt0 := CounterST0.Out0;
END_PROGRAM
CONFIGURATION config
VAR_GLOBAL
ResetCounterValue : INT := 0;
Relay0Value AT %QX0.0.0.0 : BOOL := 0;
Relay1Value AT %QX0.0.1.1 : BOOL := 0;
Relay2Value AT %QX0.0.2.2 : BOOL := 0;
Relay3Value AT %QX0.0.3.3 : BOOL := 0;
END_VAR
RESOURCE resource1 ON PLC
TASK task0(INTERVAL := T#20ms,PRIORITY := 0);
PROGRAM instance0 WITH task0 : plc_prg;
END_RESOURCE
END_CONFIGURATION
680fbe404b11a44c9877be3f7ae5b1fb
\ No newline at end of file
TYPE
LOGLEVEL : (CRITICAL, WARNING, INFO, DEBUG) := INFO;
END_TYPE
FUNCTION_BLOCK LOGGER
VAR_INPUT
TRIG : BOOL;
MSG : STRING;
LEVEL : LOGLEVEL := INFO;
END_VAR
VAR
TRIG0 : BOOL;
END_VAR
IF TRIG AND NOT TRIG0 THEN
{{
LogMessage(GetFbVar(LEVEL),(char*)GetFbVar(MSG, .body),GetFbVar(MSG, .len));
}}
END_IF;
TRIG0:=TRIG;
END_FUNCTION_BLOCK
FUNCTION_BLOCK python_eval
VAR_INPUT
TRIG : BOOL;
CODE : STRING;
END_VAR
VAR_OUTPUT
ACK : BOOL;
RESULT : STRING;
END_VAR
VAR
STATE : DWORD;
BUFFER : STRING;
PREBUFFER : STRING;
TRIGM1 : BOOL;
TRIGGED : BOOL;
END_VAR
{extern void __PythonEvalFB(int, PYTHON_EVAL*);__PythonEvalFB(0, data__);}
END_FUNCTION_BLOCK
FUNCTION_BLOCK python_poll
VAR_INPUT
TRIG : BOOL;
CODE : STRING;
END_VAR
VAR_OUTPUT
ACK : BOOL;
RESULT : STRING;
END_VAR
VAR
STATE : DWORD;
BUFFER : STRING;
PREBUFFER : STRING;
TRIGM1 : BOOL;
TRIGGED : BOOL;
END_VAR
{extern void __PythonEvalFB(int, PYTHON_EVAL*);__PythonEvalFB(1,(PYTHON_EVAL*)(void*)data__);}
END_FUNCTION_BLOCK
FUNCTION_BLOCK python_gear
VAR_INPUT
N : UINT;
TRIG : BOOL;
CODE : STRING;
END_VAR
VAR_OUTPUT
ACK : BOOL;
RESULT : STRING;
END_VAR
VAR
py_eval : python_eval;
COUNTER : UINT;
_TMP_ADD10_OUT : UINT;
_TMP_EQ13_OUT : BOOL;
_TMP_SEL15_OUT : UINT;
_TMP_AND7_OUT : BOOL;
END_VAR
_TMP_ADD10_OUT := ADD(COUNTER, 1);
_TMP_EQ13_OUT := EQ(N, _TMP_ADD10_OUT);
_TMP_SEL15_OUT := SEL(_TMP_EQ13_OUT, _TMP_ADD10_OUT, 0);
COUNTER := _TMP_SEL15_OUT;
_TMP_AND7_OUT := AND(_TMP_EQ13_OUT, TRIG);
py_eval(TRIG := _TMP_AND7_OUT, CODE := CODE);
ACK := py_eval.ACK;
RESULT := py_eval.RESULT;
END_FUNCTION_BLOCK
FUNCTION_BLOCK CounterST
VAR_INPUT
Reset : BOOL;
END_VAR
VAR_OUTPUT
Out1 : INT;
Out0 : INT;
END_VAR
VAR
Cnt1 : INT;
Cnt0 : INT;
END_VAR
VAR_EXTERNAL
ResetCounterValue : INT;
Relay0Value : BOOL;
Relay1Value : BOOL;
Relay2Value : BOOL;
Relay3Value : BOOL;
END_VAR
IF Reset THEN
Cnt0 := ResetCounterValue;
Cnt1 := ResetCounterValue;
Reset := False;
ELSE
Cnt0 := Cnt0 + 1;
Cnt1 := Cnt1 + 1;
IF Cnt1 > 10 THEN
Relay0Value := True;
Relay1Value := True;
ELSE
Relay0Value := True;
Relay1Value := False;
END_IF;
IF Cnt1 > 15 THEN
Relay1Value := False;
Cnt1 :=0;
END_IF;
END_IF;
Out1 := Cnt1;
Out0 := Cnt0;
END_FUNCTION_BLOCK
PROGRAM plc_prg
VAR_INPUT
Reset : BOOL;
END_VAR
VAR_OUTPUT
Cnt0 : INT;
Cnt1 : INT;
END_VAR
VAR
CounterST0 : CounterST;
END_VAR
CounterST0(Reset := Reset);
Cnt1 := CounterST0.Out1;
Cnt0 := CounterST0.Out0;
END_PROGRAM
CONFIGURATION config
VAR_GLOBAL
ResetCounterValue : INT := 0;
Relay0Value AT %QX0.0.0.0 : BOOL := 0;
Relay1Value AT %QX0.0.1.1 : BOOL := 0;
Relay2Value AT %QX0.0.2.2 : BOOL := 0;
Relay3Value AT %QX0.0.3.3 : BOOL := 0;
END_VAR
RESOURCE resource1 ON PLC
TASK task0(INTERVAL := T#20ms,PRIORITY := 0);
PROGRAM instance0 WITH task0 : plc_prg;
END_RESOURCE
END_CONFIGURATION
/*
* DEBUGGER code
*
* On "publish", when buffer is free, debugger stores arbitrary variables
* content into, and mark this buffer as filled
*
*
* Buffer content is read asynchronously, (from non real time part),
* and then buffer marked free again.
*
*
* */
#ifdef TARGET_DEBUG_AND_RETAIN_DISABLE
void __init_debug (void){}
void __cleanup_debug (void){}
void __retrieve_debug(void){}
void __publish_debug (void){}
#else
#include "iec_types_all.h"
#include "POUS.h"
/*for memcpy*/
#include <string.h>
#include <stdio.h>
#ifndef TARGET_ONLINE_DEBUG_DISABLE
#define BUFFER_SIZE 28
/* Atomically accessed variable for buffer state */
#define BUFFER_FREE 0
#define BUFFER_BUSY 1
static long buffer_state = BUFFER_FREE;
/* The buffer itself */
char debug_buffer[BUFFER_SIZE];
/* Buffer's cursor*/
static char* buffer_cursor = debug_buffer;
#endif
static unsigned int retain_offset = 0;
/***
* Declare programs
**/
extern PLC_PRG RESOURCE1__INSTANCE0;
/***
* Declare global variables from resources and conf
**/
extern __IEC_INT_t CONFIG__RESETCOUNTERVALUE;
extern __IEC_BOOL_p CONFIG__RELAY0VALUE;
extern __IEC_BOOL_p CONFIG__RELAY1VALUE;
extern __IEC_BOOL_p CONFIG__RELAY2VALUE;
extern __IEC_BOOL_p CONFIG__RELAY3VALUE;
extern PLC_PRG RESOURCE1__INSTANCE0;
typedef const struct {
void *ptr;
__IEC_types_enum type;
} dbgvardsc_t;
static dbgvardsc_t dbgvardsc[] = {
{&(CONFIG__RESETCOUNTERVALUE), INT_ENUM},
{&(CONFIG__RELAY0VALUE), BOOL_O_ENUM},
{&(CONFIG__RELAY1VALUE), BOOL_O_ENUM},
{&(CONFIG__RELAY2VALUE), BOOL_O_ENUM},
{&(CONFIG__RELAY3VALUE), BOOL_O_ENUM},
{&(RESOURCE1__INSTANCE0.RESET), BOOL_ENUM},
{&(RESOURCE1__INSTANCE0.CNT0), INT_ENUM},
{&(RESOURCE1__INSTANCE0.CNT1), INT_ENUM},
{&(RESOURCE1__INSTANCE0.COUNTERST0.EN), BOOL_ENUM},
{&(RESOURCE1__INSTANCE0.COUNTERST0.ENO), BOOL_ENUM},
{&(RESOURCE1__INSTANCE0.COUNTERST0.RESET), BOOL_ENUM},
{&(RESOURCE1__INSTANCE0.COUNTERST0.OUT1), INT_ENUM},
{&(RESOURCE1__INSTANCE0.COUNTERST0.OUT0), INT_ENUM},
{&(RESOURCE1__INSTANCE0.COUNTERST0.CNT1), INT_ENUM},
{&(RESOURCE1__INSTANCE0.COUNTERST0.CNT0), INT_ENUM},
{&(RESOURCE1__INSTANCE0.COUNTERST0.RESETCOUNTERVALUE), INT_P_ENUM},
{&(RESOURCE1__INSTANCE0.COUNTERST0.RELAY0VALUE), BOOL_P_ENUM},
{&(RESOURCE1__INSTANCE0.COUNTERST0.RELAY1VALUE), BOOL_P_ENUM},
{&(RESOURCE1__INSTANCE0.COUNTERST0.RELAY2VALUE), BOOL_P_ENUM},
{&(RESOURCE1__INSTANCE0.COUNTERST0.RELAY3VALUE), BOOL_P_ENUM}
};
typedef void(*__for_each_variable_do_fp)(dbgvardsc_t*);
void __for_each_variable_do(__for_each_variable_do_fp fp)
{
unsigned int i;
for(i = 0; i < sizeof(dbgvardsc)/sizeof(dbgvardsc_t); i++){
dbgvardsc_t *dsc = &dbgvardsc[i];
if(dsc->type != UNKNOWN_ENUM)
(*fp)(dsc);
}
}
#define __Unpack_desc_type dbgvardsc_t
#define __Unpack_case_t(TYPENAME) \
case TYPENAME##_ENUM :\
*flags = ((__IEC_##TYPENAME##_t *)varp)->flags;\
forced_value_p = *real_value_p = &((__IEC_##TYPENAME##_t *)varp)->value;\
break;
#define __Unpack_case_p(TYPENAME)\
case TYPENAME##_O_ENUM :\
*flags = __IEC_OUTPUT_FLAG;\
case TYPENAME##_P_ENUM :\
*flags |= ((__IEC_##TYPENAME##_p *)varp)->flags;\
*real_value_p = ((__IEC_##TYPENAME##_p *)varp)->value;\
forced_value_p = &((__IEC_##TYPENAME##_p *)varp)->fvalue;\
break;
#define __Is_a_string(dsc) (dsc->type == STRING_ENUM) ||\
(dsc->type == STRING_P_ENUM) ||\
(dsc->type == STRING_O_ENUM)
static void* UnpackVar(__Unpack_desc_type *dsc, void **real_value_p, char *flags)
{
void *varp = dsc->ptr;
void *forced_value_p = NULL;
*flags = 0;
/* find data to copy*/
switch(dsc->type){
__ANY(__Unpack_case_t)
__ANY(__Unpack_case_p)
default:
break;
}
if (*flags & __IEC_FORCE_FLAG)
return forced_value_p;
return *real_value_p;
}
void Remind(unsigned int offset, unsigned int count, void * p);
void RemindIterator(dbgvardsc_t *dsc)
{
void *real_value_p = NULL;
char flags = 0;
UnpackVar(dsc, &real_value_p, &flags);
if(flags & __IEC_RETAIN_FLAG){
USINT size = __get_type_enum_size(dsc->type);
/* compute next cursor positon*/
unsigned int next_retain_offset = retain_offset + size;
/* if buffer not full */
Remind(retain_offset, size, real_value_p);
/* increment cursor according size*/
retain_offset = next_retain_offset;
}
}
extern int CheckRetainBuffer(void);
extern void InitRetain(void);
void __init_debug(void)
{
/* init local static vars */
#ifndef TARGET_ONLINE_DEBUG_DISABLE
buffer_cursor = debug_buffer;
buffer_state = BUFFER_FREE;
#endif
retain_offset = 0;
InitRetain();
/* Iterate over all variables to fill debug buffer */
if(CheckRetainBuffer()){
__for_each_variable_do(RemindIterator);
}else{
char mstr[] = "RETAIN memory invalid - defaults used";
LogMessage(LOG_WARNING, mstr, sizeof(mstr));
}
retain_offset = 0;
}
extern void InitiateDebugTransfer(void);
extern void CleanupRetain(void);
extern unsigned long __tick;
void __cleanup_debug(void)
{
#ifndef TARGET_ONLINE_DEBUG_DISABLE
buffer_cursor = debug_buffer;
InitiateDebugTransfer();
#endif
CleanupRetain();
}
void __retrieve_debug(void)
{
}
void Retain(unsigned int offset, unsigned int count, void * p);
static inline void BufferIterator(dbgvardsc_t *dsc, int do_debug)
{
void *real_value_p = NULL;
void *visible_value_p = NULL;
char flags = 0;
visible_value_p = UnpackVar(dsc, &real_value_p, &flags);
if(flags & ( __IEC_DEBUG_FLAG | __IEC_RETAIN_FLAG)){
USINT size = __get_type_enum_size(dsc->type);
#ifndef TARGET_ONLINE_DEBUG_DISABLE
if(flags & __IEC_DEBUG_FLAG){
/* copy visible variable to buffer */;
if(do_debug){
/* compute next cursor positon.
No need to check overflow, as BUFFER_SIZE
is computed large enough */
if(__Is_a_string(dsc)){
/* optimization for strings */
size = ((STRING*)visible_value_p)->len + 1;
}
char* next_cursor = buffer_cursor + size;
/* copy data to the buffer */
memcpy(buffer_cursor, visible_value_p, size);
/* increment cursor according size*/
buffer_cursor = next_cursor;
}
/* re-force real value of outputs (M and Q)*/
if((flags & __IEC_FORCE_FLAG) && (flags & __IEC_OUTPUT_FLAG)){
memcpy(real_value_p, visible_value_p, size);
}
}
#endif
if(flags & __IEC_RETAIN_FLAG){
/* compute next cursor positon*/
unsigned int next_retain_offset = retain_offset + size;
/* if buffer not full */
Retain(retain_offset, size, real_value_p);
/* increment cursor according size*/
retain_offset = next_retain_offset;
}
}
}
void DebugIterator(dbgvardsc_t *dsc){
BufferIterator(dsc, 1);
}
void RetainIterator(dbgvardsc_t *dsc){
BufferIterator(dsc, 0);
}
unsigned int retain_size = 0;
/* GetRetainSizeIterator */
void GetRetainSizeIterator(dbgvardsc_t *dsc)
{
void *real_value_p = NULL;
char flags = 0;
UnpackVar(dsc, &real_value_p, &flags);
if(flags & __IEC_RETAIN_FLAG){
USINT size = __get_type_enum_size(dsc->type);
/* Calc retain buffer size */
retain_size += size;
}
}
/* Return size of all retain variables */
unsigned int GetRetainSize(void)
{
__for_each_variable_do(GetRetainSizeIterator);
return retain_size;
}
extern void PLC_GetTime(IEC_TIME*);
extern int TryEnterDebugSection(void);
extern long AtomicCompareExchange(long*, long, long);
extern long long AtomicCompareExchange64(long long* , long long , long long);
extern void LeaveDebugSection(void);
extern void ValidateRetainBuffer(void);
extern void InValidateRetainBuffer(void);
void __publish_debug(void)
{
retain_offset = 0;
InValidateRetainBuffer();
#ifndef TARGET_ONLINE_DEBUG_DISABLE
/* Check there is no running debugger re-configuration */
if(TryEnterDebugSection()){
/* Lock buffer */
long latest_state = AtomicCompareExchange(
&buffer_state,
BUFFER_FREE,
BUFFER_BUSY);
/* If buffer was free */
if(latest_state == BUFFER_FREE)
{
/* Reset buffer cursor */
buffer_cursor = debug_buffer;
/* Iterate over all variables to fill debug buffer */
__for_each_variable_do(DebugIterator);
/* Leave debug section,
* Trigger asynchronous transmission
* (returns immediately) */
InitiateDebugTransfer(); /* size */
}else{
/* when not debugging, do only retain */
__for_each_variable_do(RetainIterator);
}
LeaveDebugSection();
}else
#endif
{
/* when not debugging, do only retain */
__for_each_variable_do(RetainIterator);
}
ValidateRetainBuffer();
}
#ifndef TARGET_ONLINE_DEBUG_DISABLE
#define __RegisterDebugVariable_case_t(TYPENAME) \
case TYPENAME##_ENUM :\
((__IEC_##TYPENAME##_t *)varp)->flags |= flags;\
if(force)\
((__IEC_##TYPENAME##_t *)varp)->value = *((TYPENAME *)force);\
break;
#define __RegisterDebugVariable_case_p(TYPENAME)\
case TYPENAME##_P_ENUM :\
((__IEC_##TYPENAME##_p *)varp)->flags |= flags;\
if(force)\
((__IEC_##TYPENAME##_p *)varp)->fvalue = *((TYPENAME *)force);\
break;\
case TYPENAME##_O_ENUM :\
((__IEC_##TYPENAME##_p *)varp)->flags |= flags;\
if(force){\
((__IEC_##TYPENAME##_p *)varp)->fvalue = *((TYPENAME *)force);\
*(((__IEC_##TYPENAME##_p *)varp)->value) = *((TYPENAME *)force);\
}\
break;
void RegisterDebugVariable(unsigned int idx, void* force)
{
if(idx < sizeof(dbgvardsc)/sizeof(dbgvardsc_t)){
unsigned char flags = force ?
__IEC_DEBUG_FLAG | __IEC_FORCE_FLAG :
__IEC_DEBUG_FLAG;
dbgvardsc_t *dsc = &dbgvardsc[idx];
void *varp = dsc->ptr;
switch(dsc->type){
__ANY(__RegisterDebugVariable_case_t)
__ANY(__RegisterDebugVariable_case_p)
default:
break;
}
}
}
#define __ResetDebugVariablesIterator_case_t(TYPENAME) \
case TYPENAME##_ENUM :\
((__IEC_##TYPENAME##_t *)varp)->flags &= ~(__IEC_DEBUG_FLAG|__IEC_FORCE_FLAG);\
break;
#define __ResetDebugVariablesIterator_case_p(TYPENAME)\
case TYPENAME##_P_ENUM :\
case TYPENAME##_O_ENUM :\
((__IEC_##TYPENAME##_p *)varp)->flags &= ~(__IEC_DEBUG_FLAG|__IEC_FORCE_FLAG);\
break;
void ResetDebugVariablesIterator(dbgvardsc_t *dsc)
{
/* force debug flag to 0*/
void *varp = dsc->ptr;
switch(dsc->type){
__ANY(__ResetDebugVariablesIterator_case_t)
__ANY(__ResetDebugVariablesIterator_case_p)
default:
break;
}
}
void ResetDebugVariables(void)
{
__for_each_variable_do(ResetDebugVariablesIterator);
}
void FreeDebugData(void)
{
/* atomically mark buffer as free */
AtomicCompareExchange(
&buffer_state,
BUFFER_BUSY,
BUFFER_FREE);
}
int WaitDebugData(unsigned long *tick);
/* Wait until debug data ready and return pointer to it */
int GetDebugData(unsigned long *tick, unsigned long *size, void **buffer){
int wait_error = WaitDebugData(tick);
if(!wait_error){
*size = buffer_cursor - debug_buffer;
*buffer = debug_buffer;
}
return wait_error;
}
#endif
#endif
/**
* Head of code common to all C targets
**/
#include "beremiz.h"
#include <string.h>
/*
* Prototypes of functions provided by generated C softPLC
**/
void config_run__(unsigned long tick);
void config_init__(void);
/*
* Prototypes of functions provided by generated target C code
* */
long long AtomicCompareExchange64(long long*, long long, long long);
void __init_debug(void);
void __cleanup_debug(void);
/*void __retrieve_debug(void);*/
void __publish_debug(void);
/*
* Variables used by generated C softPLC and plugins
**/
IEC_TIME __CURRENT_TIME;
IEC_BOOL __DEBUG = 0;
unsigned long __tick = 0;
char *PLC_ID = NULL;
/*
* Variable generated by C softPLC and plugins
**/
extern unsigned long greatest_tick_count__;
/* Help to quit cleanly when init fail at a certain level */
static int init_level = 0;
/*
* Prototypes of functions exported by plugins
**/
int __init_py_ext(int argc,char **argv);
void __cleanup_py_ext(void);
void __retrieve_py_ext(void);
void __publish_py_ext(void);
int __init_0(int argc,char **argv);
void __cleanup_0(void);
void __retrieve_0(void);
void __publish_0(void);
/*
* Retrieve input variables, run PLC and publish output variables
**/
void __run(void)
{
__tick++;
if (greatest_tick_count__)
__tick %= greatest_tick_count__;
__retrieve_py_ext();
__retrieve_0();
/*__retrieve_debug();*/
config_run__(__tick);
__publish_debug();
__publish_0();
__publish_py_ext();
}
/*
* Initialize variables according to PLC's default values,
* and then init plugins with that values
**/
int __init(int argc,char **argv)
{
int res = 0;
init_level = 0;
/* Effective tick time with 1ms default value */
if(!common_ticktime__)
common_ticktime__ = 1000000;
config_init__();
__init_debug();
init_level=1; if((res = __init_py_ext(argc,argv))){return res;}
init_level=2; if((res = __init_0(argc,argv))){return res;}
return res;
}
/*
* Calls plugin cleanup proc.
**/
void __cleanup(void)
{
if(init_level >= 2) __cleanup_0();
if(init_level >= 1) __cleanup_py_ext();
__cleanup_debug();
}
void PLC_GetTime(IEC_TIME *CURRENT_TIME);
void PLC_SetTimer(unsigned long long next, unsigned long long period);
/**
* Linux specific code
**/
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <signal.h>
#include <stdlib.h>
#include <pthread.h>
#include <locale.h>
#include <semaphore.h>
static sem_t Run_PLC;
long AtomicCompareExchange(long* atomicvar,long compared, long exchange)
{
return __sync_val_compare_and_swap(atomicvar, compared, exchange);
}
long long AtomicCompareExchange64(long long* atomicvar, long long compared, long long exchange)
{
return __sync_val_compare_and_swap(atomicvar, compared, exchange);
}
void PLC_GetTime(IEC_TIME *CURRENT_TIME)
{
struct timespec tmp;
clock_gettime(CLOCK_REALTIME, &tmp);
CURRENT_TIME->tv_sec = tmp.tv_sec;
CURRENT_TIME->tv_nsec = tmp.tv_nsec;
}
void PLC_timer_notify(sigval_t val)
{
PLC_GetTime(&__CURRENT_TIME);
sem_post(&Run_PLC);
}
timer_t PLC_timer;
void PLC_SetTimer(unsigned long long next, unsigned long long period)
{
struct itimerspec timerValues;
/*
printf("SetTimer(%lld,%lld)\n",next, period);
*/
memset (&timerValues, 0, sizeof (struct itimerspec));
{
#ifdef __lldiv_t_defined
lldiv_t nxt_div = lldiv(next, 1000000000);
lldiv_t period_div = lldiv(period, 1000000000);
timerValues.it_value.tv_sec = nxt_div.quot;
timerValues.it_value.tv_nsec = nxt_div.rem;
timerValues.it_interval.tv_sec = period_div.quot;
timerValues.it_interval.tv_nsec = period_div.rem;
#else
timerValues.it_value.tv_sec = next / 1000000000;
timerValues.it_value.tv_nsec = next % 1000000000;
timerValues.it_interval.tv_sec = period / 1000000000;
timerValues.it_interval.tv_nsec = period % 1000000000;
#endif
}
timer_settime (PLC_timer, 0, &timerValues, NULL);
}
//
void catch_signal(int sig)
{
// signal(SIGTERM, catch_signal);
signal(SIGINT, catch_signal);
printf("Got Signal %d\n",sig);
exit(0);
}
static unsigned long __debug_tick;
pthread_t PLC_thread;
static pthread_mutex_t python_wait_mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_mutex_t python_mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_mutex_t debug_wait_mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_mutex_t debug_mutex = PTHREAD_MUTEX_INITIALIZER;
int PLC_shutdown = 0;
int ForceSaveRetainReq(void) {
return PLC_shutdown;
}
void PLC_thread_proc(void *arg)
{
while (!PLC_shutdown) {
sem_wait(&Run_PLC);
__run();
}
pthread_exit(0);
}
#define maxval(a,b) ((a>b)?a:b)
int startPLC(int argc,char **argv)
{
struct sigevent sigev;
setlocale(LC_NUMERIC, "C");
PLC_shutdown = 0;
sem_init(&Run_PLC, 0, 0);
pthread_create(&PLC_thread, NULL, (void*) &PLC_thread_proc, NULL);
memset (&sigev, 0, sizeof (struct sigevent));
sigev.sigev_value.sival_int = 0;
sigev.sigev_notify = SIGEV_THREAD;
sigev.sigev_notify_attributes = NULL;
sigev.sigev_notify_function = PLC_timer_notify;
pthread_mutex_init(&debug_wait_mutex, NULL);
pthread_mutex_init(&debug_mutex, NULL);
pthread_mutex_init(&python_wait_mutex, NULL);
pthread_mutex_init(&python_mutex, NULL);
pthread_mutex_lock(&debug_wait_mutex);
pthread_mutex_lock(&python_wait_mutex);
timer_create (CLOCK_MONOTONIC, &sigev, &PLC_timer);
if( __init(argc,argv) == 0 ){
PLC_SetTimer(common_ticktime__,common_ticktime__);
/* install signal handler for manual break */
signal(SIGINT, catch_signal);
}else{
return 1;
}
return 0;
}
int TryEnterDebugSection(void)
{
if (pthread_mutex_trylock(&debug_mutex) == 0){
/* Only enter if debug active */
if(__DEBUG){
return 1;
}
pthread_mutex_unlock(&debug_mutex);
}
return 0;
}
void LeaveDebugSection(void)
{
pthread_mutex_unlock(&debug_mutex);
}
int stopPLC()
{
/* Stop the PLC */
PLC_shutdown = 1;
sem_post(&Run_PLC);
PLC_SetTimer(0,0);
pthread_join(PLC_thread, NULL);
sem_destroy(&Run_PLC);
timer_delete (PLC_timer);
__cleanup();
pthread_mutex_destroy(&debug_wait_mutex);
pthread_mutex_destroy(&debug_mutex);
pthread_mutex_destroy(&python_wait_mutex);
pthread_mutex_destroy(&python_mutex);
return 0;
}
extern unsigned long __tick;
int WaitDebugData(unsigned long *tick)
{
int res;
if (PLC_shutdown) return 1;
/* Wait signal from PLC thread */
res = pthread_mutex_lock(&debug_wait_mutex);
*tick = __debug_tick;
return res;
}
/* Called by PLC thread when debug_publish finished
* This is supposed to unlock debugger thread in WaitDebugData*/
void InitiateDebugTransfer()
{
/* remember tick */
__debug_tick = __tick;
/* signal debugger thread it can read data */
pthread_mutex_unlock(&debug_wait_mutex);
}
int suspendDebug(int disable)
{
/* Prevent PLC to enter debug code */
pthread_mutex_lock(&debug_mutex);
/*__DEBUG is protected by this mutex */
__DEBUG = !disable;
if (disable)
pthread_mutex_unlock(&debug_mutex);
return 0;
}
void resumeDebug(void)
{
__DEBUG = 1;
/* Let PLC enter debug code */
pthread_mutex_unlock(&debug_mutex);
}
/* from plc_python.c */
int WaitPythonCommands(void)
{
/* Wait signal from PLC thread */
return pthread_mutex_lock(&python_wait_mutex);
}
/* Called by PLC thread on each new python command*/
void UnBlockPythonCommands(void)
{
/* signal python thread it can read data */
pthread_mutex_unlock(&python_wait_mutex);
}
int TryLockPython(void)
{
return pthread_mutex_trylock(&python_mutex) == 0;
}
void UnLockPython(void)
{
pthread_mutex_unlock(&python_mutex);
}
void LockPython(void)
{
pthread_mutex_lock(&python_mutex);
}
struct RT_to_nRT_signal_s {
pthread_cond_t WakeCond;
pthread_mutex_t WakeCondLock;
};
typedef struct RT_to_nRT_signal_s RT_to_nRT_signal_t;
#define _LogAndReturnNull(text) \
{\
char mstr[256] = text " for ";\
strncat(mstr, name, 255);\
LogMessage(LOG_CRITICAL, mstr, strlen(mstr));\
return NULL;\
}
void *create_RT_to_nRT_signal(char* name){
RT_to_nRT_signal_t *sig = (RT_to_nRT_signal_t*)malloc(sizeof(RT_to_nRT_signal_t));
if(!sig)
_LogAndReturnNull("Failed allocating memory for RT_to_nRT signal");
pthread_cond_init(&sig->WakeCond, NULL);
pthread_mutex_init(&sig->WakeCondLock, NULL);
return (void*)sig;
}
void delete_RT_to_nRT_signal(void* handle){
RT_to_nRT_signal_t *sig = (RT_to_nRT_signal_t*)handle;
pthread_cond_destroy(&sig->WakeCond);
pthread_mutex_destroy(&sig->WakeCondLock);
free(sig);
}
int wait_RT_to_nRT_signal(void* handle){
int ret;
RT_to_nRT_signal_t *sig = (RT_to_nRT_signal_t*)handle;
pthread_mutex_lock(&sig->WakeCondLock);
ret = pthread_cond_wait(&sig->WakeCond, &sig->WakeCondLock);
pthread_mutex_unlock(&sig->WakeCondLock);
return ret;
}
int unblock_RT_to_nRT_signal(void* handle){
RT_to_nRT_signal_t *sig = (RT_to_nRT_signal_t*)handle;
return pthread_cond_signal(&sig->WakeCond);
}
void nRT_reschedule(void){
sched_yield();
}
/*
This file is part of Beremiz, a Integrated Development Environment for
programming IEC 61131-3 automates supporting plcopen standard and CanFestival.
See COPYING.runtime
Copyright (C) 2018: Sergey Surkov <surkov.sv@summatechnology.ru>
Copyright (C) 2018: Andrey Skvortsov <andrej.skvortzov@gmail.com>
*/
#ifndef HAVE_RETAIN
#include <stdio.h>
#include <stdint.h>
#include <unistd.h>
#include "iec_types.h"
int GetRetainSize(void);
/* Retain buffer. */
FILE *retain_buffer;
const char rb_file[] = "retain_buffer_file";
const char rb_file_bckp[] = "retain_buffer_file.bak";
/* Retain header struct. */
struct retain_info_t {
uint32_t retain_size;
uint32_t hash_size;
uint8_t* hash;
uint32_t header_offset;
uint32_t header_crc;
};
/* Init retain info structure. */
struct retain_info_t retain_info;
/* CRC lookup table and initial state. */
static const uint32_t crc32_table[256] = {
0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3,
0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91,
0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7,
0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5,
0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B,
0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59,
0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F,
0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D,
0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433,
0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01,
0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457,
0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65,
0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB,
0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9,
0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F,
0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD,
0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683,
0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1,
0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7,
0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5,
0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B,
0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79,
0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F,
0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D,
0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713,
0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21,
0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777,
0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45,
0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB,
0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9,
0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF,
0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D,
};
uint32_t retain_crc;
/* Calculate CRC32 for len bytes from pointer buf with init starting value. */
uint32_t GenerateCRC32Sum(const void* buf, unsigned int len, uint32_t init)
{
uint32_t crc = ~init;
unsigned char* current = (unsigned char*) buf;
while (len--)
crc = crc32_table[(crc ^ *current++) & 0xFF] ^ (crc >> 8);
return ~crc;
}
/* Calc CRC32 for retain file byte by byte. */
int CheckFileCRC(FILE* file_buffer)
{
/* Set the magic constant for one-pass CRC calc according to ZIP CRC32. */
const uint32_t magic_number = 0x2144df1c;
/* CRC initial state. */
uint32_t calc_crc32 = 0;
char data_block = 0;
while(!feof(file_buffer)){
if (fread(&data_block, sizeof(data_block), 1, file_buffer))
calc_crc32 = GenerateCRC32Sum(&data_block, sizeof(char), calc_crc32);
}
/* Compare crc result with a magic number. */
return (calc_crc32 == magic_number) ? 1 : 0;
}
/* Compare current hash with hash from file byte by byte. */
int CheckFilehash(void)
{
int k,ret;
int offset = sizeof(retain_info.retain_size);
rewind(retain_buffer);
fseek(retain_buffer, offset , SEEK_SET);
uint32_t size;
ret = fread(&size, sizeof(size), 1, retain_buffer);
if (size != retain_info.hash_size)
return 0;
for(k = 0; k < retain_info.hash_size; k++){
uint8_t file_digit;
ret = fread(&file_digit, sizeof(char), 1, retain_buffer);
if (file_digit != *(retain_info.hash+k))
return 0;
}
return 1;
}
void InitRetain(void)
{
int i;
/* Get retain size in bytes */
retain_info.retain_size = GetRetainSize();
/* Hash stored in retain file as array of char in hex digits
(that's why we divide strlen in two). */
retain_info.hash_size = PLC_ID ? strlen(PLC_ID)/2 : 0;
//retain_info.hash_size = 0;
retain_info.hash = malloc(retain_info.hash_size);
/* Transform hash string into byte sequence. */
for (i = 0; i < retain_info.hash_size; i++) {
int byte = 0;
sscanf((PLC_ID + i*2), "%02X", &byte);
retain_info.hash[i] = byte;
}
/* Calc header offset. */
retain_info.header_offset = sizeof(retain_info.retain_size) + \
sizeof(retain_info.hash_size) + \
retain_info.hash_size;
/* Set header CRC initial state. */
retain_info.header_crc = 0;
/* Calc crc for header. */
retain_info.header_crc = GenerateCRC32Sum(
&retain_info.retain_size,
sizeof(retain_info.retain_size),
retain_info.header_crc);
retain_info.header_crc = GenerateCRC32Sum(
&retain_info.hash_size,
sizeof(retain_info.hash_size),
retain_info.header_crc);
retain_info.header_crc = GenerateCRC32Sum(
retain_info.hash,
retain_info.hash_size,
retain_info.header_crc);
}
void CleanupRetain(void)
{
/* Free hash memory. */
free(retain_info.hash);
}
int CheckRetainFile(const char * file)
{
retain_buffer = fopen(file, "rb");
if (retain_buffer) {
/* Check CRC32 and hash. */
if (CheckFileCRC(retain_buffer))
if (CheckFilehash())
return 1;
fclose(retain_buffer);
retain_buffer = NULL;
}
return 0;
}
int CheckRetainBuffer(void)
{
retain_buffer = NULL;
if (!retain_info.retain_size)
return 1;
/* Check latest retain file. */
if (CheckRetainFile(rb_file))
return 1;
/* Check if we have backup. */
if (CheckRetainFile(rb_file_bckp))
return 1;
/* We don't have any valid retain buffer - nothing to remind. */
return 0;
}
#ifndef FILE_RETAIN_SAVE_PERIOD_S
#define FILE_RETAIN_SAVE_PERIOD_S 1.0
#endif
static double CalcDiffSeconds(IEC_TIME* t1, IEC_TIME *t2)
{
IEC_TIME dt ={
t1->tv_sec - t2->tv_sec,
t1->tv_nsec - t2->tv_nsec
};
if ((dt.tv_nsec < -1000000000) || ((dt.tv_sec > 0) && (dt.tv_nsec < 0))){
dt.tv_sec--;
dt.tv_nsec += 1000000000;
}
if ((dt.tv_nsec > +1000000000) || ((dt.tv_sec < 0) && (dt.tv_nsec > 0))){
dt.tv_sec++;
dt.tv_nsec -= 1000000000;
}
return dt.tv_sec + 1e-9*dt.tv_nsec;
}
int RetainSaveNeeded(void)
{
int ret = 0;
static IEC_TIME last_save;
IEC_TIME now;
double diff_s;
/* no retain */
if (!retain_info.retain_size)
return 0;
/* periodic retain flush to avoid high I/O load */
PLC_GetTime(&now);
diff_s = CalcDiffSeconds(&now, &last_save);
if ((diff_s > FILE_RETAIN_SAVE_PERIOD_S) || ForceSaveRetainReq()) {
ret = 1;
last_save = now;
}
return ret;
}
void ValidateRetainBuffer(void)
{
if (!retain_buffer)
return;
/* Add retain data CRC to the end of buffer file. */
fseek(retain_buffer, 0, SEEK_END);
fwrite(&retain_crc, sizeof(uint32_t), 1, retain_buffer);
/* Sync file buffer and close file. */
#ifdef __WIN32
fflush(retain_buffer);
#else
fsync(fileno(retain_buffer));
#endif
fclose(retain_buffer);
retain_buffer = NULL;
}
void InValidateRetainBuffer(void)
{
if (!RetainSaveNeeded())
return;
/* Rename old retain file into *.bak if it exists. */
rename(rb_file, rb_file_bckp);
/* Set file CRC initial value. */
retain_crc = retain_info.header_crc;
/* Create new retain file. */
retain_buffer = fopen(rb_file, "wb+");
if (!retain_buffer) {
fprintf(stderr, "Failed to create retain file : %s\n", rb_file);
return;
}
/* Write header to the new file. */
fwrite(&retain_info.retain_size,
sizeof(retain_info.retain_size), 1, retain_buffer);
fwrite(&retain_info.hash_size,
sizeof(retain_info.hash_size), 1, retain_buffer);
fwrite(retain_info.hash ,
sizeof(char), retain_info.hash_size, retain_buffer);
}
void Retain(unsigned int offset, unsigned int count, void *p)
{
if (!retain_buffer)
return;
/* Generate CRC 32 for each data block. */
retain_crc = GenerateCRC32Sum(p, count, retain_crc);
/* Save current var in file. */
fseek(retain_buffer, retain_info.header_offset+offset, SEEK_SET);
fwrite(p, count, 1, retain_buffer);
}
void Remind(unsigned int offset, unsigned int count, void *p)
{
int ret;
/* Remind variable from file. */
fseek(retain_buffer, retain_info.header_offset+offset, SEEK_SET);
ret = fread((void *)p, count, 1, retain_buffer);
}
#endif // !HAVE_RETAIN
/**
* Tail of code common to all C targets
**/
/**
* LOGGING
**/
#ifndef TARGET_LOGGING_DISABLE
#ifndef LOG_BUFFER_SIZE
#define LOG_BUFFER_SIZE (1<<14) /*16Ko*/
#endif
#ifndef LOG_BUFFER_ATTRS
#define LOG_BUFFER_ATTRS
#endif
#define LOG_BUFFER_MASK (LOG_BUFFER_SIZE-1)
static char LogBuff[LOG_LEVELS][LOG_BUFFER_SIZE] LOG_BUFFER_ATTRS;
static void inline copy_to_log(uint8_t level, uint32_t buffpos, void* buf, uint32_t size){
if(buffpos + size < LOG_BUFFER_SIZE){
memcpy(&LogBuff[level][buffpos], buf, size);
}else{
uint32_t remaining = LOG_BUFFER_SIZE - buffpos;
memcpy(&LogBuff[level][buffpos], buf, remaining);
memcpy(LogBuff[level], (char*)buf + remaining, size - remaining);
}
}
static void inline copy_from_log(uint8_t level, uint32_t buffpos, void* buf, uint32_t size){
if(buffpos + size < LOG_BUFFER_SIZE){
memcpy(buf, &LogBuff[level][buffpos], size);
}else{
uint32_t remaining = LOG_BUFFER_SIZE - buffpos;
memcpy(buf, &LogBuff[level][buffpos], remaining);
memcpy((char*)buf + remaining, LogBuff[level], size - remaining);
}
}
/* Log buffer structure
|<-Tail1.msgsize->|<-sizeof(mTail)->|<--Tail2.msgsize-->|<-sizeof(mTail)->|...
| Message1 Body | Tail1 | Message2 Body | Tail2 |
*/
typedef struct {
uint32_t msgidx;
uint32_t msgsize;
unsigned long tick;
IEC_TIME time;
} mTail;
/* Log cursor : 64b
|63 ... 32|31 ... 0|
| Message | Buffer |
| counter | Index | */
static uint64_t LogCursor[LOG_LEVELS] LOG_BUFFER_ATTRS = {0x0,0x0,0x0,0x0};
void ResetLogCount(void) {
uint8_t level;
for(level=0;level<LOG_LEVELS;level++){
LogCursor[level] = 0;
}
}
/* Store one log message of give size */
int LogMessage(uint8_t level, char* buf, uint32_t size){
if(size < LOG_BUFFER_SIZE - sizeof(mTail)){
uint32_t buffpos;
uint64_t new_cursor, old_cursor;
mTail tail;
tail.msgsize = size;
tail.tick = __tick;
PLC_GetTime(&tail.time);
/* We cannot increment both msg index and string pointer
in a single atomic operation but we can detect having been interrupted.
So we can try with atomic compare and swap in a loop until operation
succeeds non interrupted */
do{
old_cursor = LogCursor[level];
buffpos = (uint32_t)old_cursor;
tail.msgidx = (old_cursor >> 32);
new_cursor = ((uint64_t)(tail.msgidx + 1)<<32)
| (uint64_t)((buffpos + size + sizeof(mTail)) & LOG_BUFFER_MASK);
}while(AtomicCompareExchange64(
(long long*)&LogCursor[level],
(long long)old_cursor,
(long long)new_cursor)!=(long long)old_cursor);
copy_to_log(level, buffpos, buf, size);
copy_to_log(level, (buffpos + size) & LOG_BUFFER_MASK, &tail, sizeof(mTail));
return 1; /* Success */
}else{
char mstr[] = "Logging error : message too big";
LogMessage(LOG_CRITICAL, mstr, sizeof(mstr));
}
return 0;
}
uint32_t GetLogCount(uint8_t level){
return (uint64_t)LogCursor[level] >> 32;
}
/* Return message size and content */
uint32_t GetLogMessage(uint8_t level, uint32_t msgidx, char* buf, uint32_t max_size, uint32_t* tick, uint32_t* tv_sec, uint32_t* tv_nsec){
uint64_t cursor = LogCursor[level];
if(cursor){
/* seach cursor */
uint32_t stailpos = (uint32_t)cursor;
uint32_t smsgidx;
mTail tail;
tail.msgidx = cursor >> 32;
tail.msgsize = 0;
/* Message search loop */
do {
smsgidx = tail.msgidx;
stailpos = (stailpos - sizeof(mTail) - tail.msgsize ) & LOG_BUFFER_MASK;
copy_from_log(level, stailpos, &tail, sizeof(mTail));
}while((tail.msgidx == smsgidx - 1) && (tail.msgidx > msgidx));
if(tail.msgidx == msgidx){
uint32_t sbuffpos = (stailpos - tail.msgsize ) & LOG_BUFFER_MASK;
uint32_t totalsize = tail.msgsize;
*tick = tail.tick;
*tv_sec = tail.time.tv_sec;
*tv_nsec = tail.time.tv_nsec;
copy_from_log(level, sbuffpos, buf,
totalsize > max_size ? max_size : totalsize);
return totalsize;
}
}
return 0;
}
#endif
#ifndef TARGET_EXT_SYNC_DISABLE
#define CALIBRATED -2
#define NOT_CALIBRATED -1
static int calibration_count = NOT_CALIBRATED;
static IEC_TIME cal_begin;
static long long Tsync = 0;
static long long FreqCorr = 0;
static int Nticks = 0;
static unsigned long last_tick = 0;
/*
* Called on each external periodic sync event
* make PLC tick synchronous with external sync
* ratio defines when PLC tick occurs between two external sync
* @param sync_align_ratio
* 0->100 : align ratio
* < 0 : no align, calibrate period
**/
void align_tick(int sync_align_ratio)
{
/*
printf("align_tick(%d)\n", calibrate);
*/
if(sync_align_ratio < 0){ /* Calibration */
if(calibration_count == CALIBRATED)
/* Re-calibration*/
calibration_count = NOT_CALIBRATED;
if(calibration_count == NOT_CALIBRATED)
/* Calibration start, get time*/
PLC_GetTime(&cal_begin);
calibration_count++;
}else{ /* do alignment (if possible) */
if(calibration_count >= 0){
/* End of calibration */
/* Get final time */
IEC_TIME cal_end;
PLC_GetTime(&cal_end);
/*adjust calibration_count*/
calibration_count++;
/* compute mean of Tsync, over calibration period */
Tsync = ((long long)(cal_end.tv_sec - cal_begin.tv_sec) * (long long)1000000000 +
(cal_end.tv_nsec - cal_begin.tv_nsec)) / calibration_count;
if( (Nticks = (Tsync / common_ticktime__)) > 0){
FreqCorr = (Tsync % common_ticktime__); /* to be divided by Nticks */
}else{
FreqCorr = Tsync - (common_ticktime__ % Tsync);
}
/*
printf("Tsync = %ld\n", Tsync);
printf("calibration_count = %d\n", calibration_count);
printf("Nticks = %d\n", Nticks);
*/
calibration_count = CALIBRATED;
}
if(calibration_count == CALIBRATED){
/* Get Elapsed time since last PLC tick (__CURRENT_TIME) */
IEC_TIME now;
long long elapsed;
long long Tcorr;
long long PhaseCorr;
long long PeriodicTcorr;
PLC_GetTime(&now);
elapsed = (now.tv_sec - __CURRENT_TIME.tv_sec) * 1000000000 + now.tv_nsec - __CURRENT_TIME.tv_nsec;
if(Nticks > 0){
PhaseCorr = elapsed - (common_ticktime__ + FreqCorr/Nticks)*sync_align_ratio/100; /* to be divided by Nticks */
Tcorr = common_ticktime__ + (PhaseCorr + FreqCorr) / Nticks;
if(Nticks < 2){
/* When Sync source period is near Tick time */
/* PhaseCorr may not be applied to Periodic time given to timer */
PeriodicTcorr = common_ticktime__ + FreqCorr / Nticks;
}else{
PeriodicTcorr = Tcorr;
}
}else if(__tick > last_tick){
last_tick = __tick;
PhaseCorr = elapsed - (Tsync*sync_align_ratio/100);
PeriodicTcorr = Tcorr = common_ticktime__ + PhaseCorr + FreqCorr;
}else{
/*PLC did not run meanwhile. Nothing to do*/
return;
}
/* DO ALIGNEMENT */
PLC_SetTimer(Tcorr - elapsed, PeriodicTcorr);
}
}
}
#endif
/*
* Python Asynchronous execution code
*
* PLC put python commands in a fifo, respecting execution order
* with the help of C pragmas inserted in python_eval FB code
*
* Buffer content is read asynchronously, (from non real time part),
* commands are executed and result stored for later use by PLC.
*
* In this implementation, fifo is a list of pointer to python_eval
* function blocks structures. Some local variables have been added in
* python_eval interface. We use those local variables as buffer and state
* flags.
*
* */
#include "iec_types_all.h"
#include "POUS.h"
#include <string.h>
/* The fifo (fixed size, as number of FB is fixed) */
static PYTHON_EVAL* EvalFBs[1];
/* Producer and consumer cursors */
static int Current_PLC_EvalFB;
static int Current_Python_EvalFB;
/* A global IEC-Python gateway state, for use inside python_eval FBs*/
static int PythonState;
#define PYTHON_LOCKED_BY_PYTHON 0
#define PYTHON_LOCKED_BY_PLC 1
#define PYTHON_MUSTWAKEUP 2
#define PYTHON_FINISHED 4
/* Each python_eval FunctionBlock have it own state */
#define PYTHON_FB_FREE 0
#define PYTHON_FB_REQUESTED 1
#define PYTHON_FB_PROCESSING 2
#define PYTHON_FB_ANSWERED 3
int WaitPythonCommands(void);
void UnBlockPythonCommands(void);
int TryLockPython(void);
void UnLockPython(void);
void LockPython(void);
int __init_py_ext()
{
int i;
/* Initialize cursors */
Current_Python_EvalFB = 0;
Current_PLC_EvalFB = 0;
PythonState = PYTHON_LOCKED_BY_PYTHON;
for(i = 0; i < 1; i++)
EvalFBs[i] = NULL;
return 0;
}
void __cleanup_py_ext()
{
PythonState = PYTHON_FINISHED;
UnBlockPythonCommands();
}
void __retrieve_py_ext()
{
/* Check Python thread is not being
* modifying internal python_eval data */
PythonState = TryLockPython() ?
PYTHON_LOCKED_BY_PLC :
PYTHON_LOCKED_BY_PYTHON;
/* If python thread _is_ in, then PythonState remains PYTHON_LOCKED_BY_PYTHON
* and python_eval will no do anything */
}
void __publish_py_ext()
{
if(PythonState & PYTHON_LOCKED_BY_PLC){
/* If runnig PLC did push something in the fifo*/
if(PythonState & PYTHON_MUSTWAKEUP){
/* WakeUp python thread */
UnBlockPythonCommands();
}
UnLockPython();
}
}
/**
* Called by the PLC, each time a python_eval
* FB instance is executed
*/
void __PythonEvalFB(int poll, PYTHON_EVAL* data__)
{
if(!__GET_VAR(data__->TRIG)){
/* ACK is False when TRIG is false, except a pulse when receiving result */
__SET_VAR(data__->, ACK,, 0);
}
/* detect rising edge on TRIG to trigger evaluation */
if(((__GET_VAR(data__->TRIG) && !__GET_VAR(data__->TRIGM1)) ||
/* polling is equivalent to trig on value rather than on rising edge*/
(poll && __GET_VAR(data__->TRIG) )) &&
/* trig only if not already trigged */
__GET_VAR(data__->TRIGGED) == 0){
/* mark as trigged */
__SET_VAR(data__->, TRIGGED,, 1);
/* make a safe copy of the code */
__SET_VAR(data__->, PREBUFFER,, __GET_VAR(data__->CODE));
}
/* retain value for next rising edge detection */
__SET_VAR(data__->, TRIGM1,, __GET_VAR(data__->TRIG));
/* python thread is not in ? */
if( PythonState & PYTHON_LOCKED_BY_PLC){
/* if some answer are waiting, publish*/
if(__GET_VAR(data__->STATE) == PYTHON_FB_ANSWERED){
/* Copy buffer content into result*/
__SET_VAR(data__->, RESULT,, __GET_VAR(data__->BUFFER));
/* signal result presence to PLC*/
__SET_VAR(data__->, ACK,, 1);
/* Mark as free */
__SET_VAR(data__->, STATE,, PYTHON_FB_FREE);
/* mark as not trigged */
if(!poll)
__SET_VAR(data__->, TRIGGED,, 0);
/*printf("__PythonEvalFB pop %d - %*s\n",Current_PLC_EvalFB, data__->BUFFER.len, data__->BUFFER.body);*/
}else if(poll){
/* when in polling, no answer == ack down */
__SET_VAR(data__->, ACK,, 0);
}
/* got the order to act ?*/
if(__GET_VAR(data__->TRIGGED) == 1 &&
/* and not already being processed */
__GET_VAR(data__->STATE) == PYTHON_FB_FREE)
{
/* Enter the block in the fifo
* Don't have to check if fifo cell is free
* as fifo size == FB count, and a FB cannot
* be requested twice */
EvalFBs[Current_PLC_EvalFB] = data__;
/* copy into BUFFER local*/
__SET_VAR(data__->, BUFFER,, __GET_VAR(data__->PREBUFFER));
/* Set ACK pin to low so that we can set a rising edge on result */
if(!poll){
/* when not polling, a new answer imply reseting ack*/
__SET_VAR(data__->, ACK,, 0);
}else{
/* when in polling, acting reset trigger */
__SET_VAR(data__->, TRIGGED,, 0);
}
/* Mark FB busy */
__SET_VAR(data__->, STATE,, PYTHON_FB_REQUESTED);
/* Have to wakeup python thread in case he was asleep */
PythonState |= PYTHON_MUSTWAKEUP;
/*printf("__PythonEvalFB push %d - %*s\n",Current_PLC_EvalFB, data__->BUFFER.len, data__->BUFFER.body);*/
/* Get a new line */
Current_PLC_EvalFB = (Current_PLC_EvalFB + 1) % 1;
}
}
}
char* PythonIterator(char* result, void** id)
{
char* next_command;
PYTHON_EVAL* data__;
//printf("PythonIterator result %s\n", result);
/*emergency exit*/
if(PythonState & PYTHON_FINISHED) return NULL;
/* take python mutex to prevent changing PLC data while PLC running */
LockPython();
/* Get current FB */
data__ = EvalFBs[Current_Python_EvalFB];
if(data__ && /* may be null at first run */
__GET_VAR(data__->STATE) == PYTHON_FB_PROCESSING){ /* some answer awaited*/
/* If result not None */
if(result){
/* Get results len */
__SET_VAR(data__->, BUFFER, .len, strlen(result));
/* prevent results overrun */
if(__GET_VAR(data__->BUFFER, .len) > STR_MAX_LEN)
{
__SET_VAR(data__->, BUFFER, .len, STR_MAX_LEN);
/* TODO : signal error */
}
/* Copy results to buffer */
strncpy((char*)__GET_VAR(data__->BUFFER, .body), result, __GET_VAR(data__->BUFFER,.len));
}else{
__SET_VAR(data__->, BUFFER, .len, 0);
}
/* remove block from fifo*/
EvalFBs[Current_Python_EvalFB] = NULL;
/* Mark block as answered */
__SET_VAR(data__->, STATE,, PYTHON_FB_ANSWERED);
/* Get a new line */
Current_Python_EvalFB = (Current_Python_EvalFB + 1) % 1;
//printf("PythonIterator ++ Current_Python_EvalFB %d\n", Current_Python_EvalFB);
}
/* while next slot is empty */
while(((data__ = EvalFBs[Current_Python_EvalFB]) == NULL) ||
/* or doesn't contain command */
__GET_VAR(data__->STATE) != PYTHON_FB_REQUESTED)
{
UnLockPython();
/* wait next FB to eval */
//printf("PythonIterator wait\n");
if(WaitPythonCommands()) return NULL;
/*emergency exit*/
if(PythonState & PYTHON_FINISHED) return NULL;
LockPython();
}
/* Mark block as processing */
__SET_VAR(data__->, STATE,, PYTHON_FB_PROCESSING);
//printf("PythonIterator\n");
/* make BUFFER a null terminated string */
__SET_VAR(data__->, BUFFER, .body[__GET_VAR(data__->BUFFER, .len)], 0);
/* next command is BUFFER */
next_command = (char*)__GET_VAR(data__->BUFFER, .body);
*id=data__;
/* free python mutex */
UnLockPython();
/* return the next command to eval */
return next_command;
}
/*******************************************/
/* FILE GENERATED BY iec2c */
/* Editing this file is not recommended... */
/*******************************************/
#include "iec_std_lib.h"
// RESOURCE RESOURCE1
extern unsigned long long common_ticktime__;
#include "accessor.h"
#include "POUS.h"
#include "config.h"
#include "POUS.c"
BOOL TASK0;
PLC_PRG RESOURCE1__INSTANCE0;
#define INSTANCE0 RESOURCE1__INSTANCE0
void RESOURCE1_init__(void) {
BOOL retain;
retain = 0;
TASK0 = __BOOL_LITERAL(FALSE);
PLC_PRG_init__(&INSTANCE0,retain);
}
void RESOURCE1_run__(unsigned long tick) {
TASK0 = !(tick % 1);
if (TASK0) {
PLC_PRG_body__(&INSTANCE0);
}
}
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# This file is part of Beremiz runtime.
#
# Copyright (C) 2020: Mario de Sousa
#
# See COPYING.Runtime file for copyrights details.
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
##############################################################################################
# This file implements an extension to the web server embedded in the Beremiz_service.py #
# runtime manager (webserver is in runtime/NevowServer.py). #
# #
# The extension implemented in this file allows for runtime configuration #
# of Modbus plugin parameters #
##############################################################################################
import json
import os
import ctypes
import string
import hashlib
from formless import annotate, webform
import runtime.NevowServer as NS
# Directory in which to store the persistent configurations
# Should be a directory that does not get wiped on reboot!
_ModbusConfFiledir = WorkingDir
# List of all Web Extension Setting nodes we are handling.
# One WebNode each for:
# - Modbus TCP client
# - Modbus TCP server
# - Modbus RTU client
# - Modbus RTU slave
# configured in the loaded PLC (i.e. the .so file loaded into memory)
# Each entry will be a dictionary. See _AddWebNode() for the details
# of the data structure in each entry.
_WebNodeList = []
class MB_StrippedString(annotate.String):
def __init__(self, *args, **kwargs):
annotate.String.__init__(self, strip = True, *args, **kwargs)
class MB_StopBits(annotate.Choice):
_choices = [0, 1, 2]
def coerce(self, val, configurable):
return int(val)
def __init__(self, *args, **kwargs):
annotate.Choice.__init__(self, choices = self._choices, *args, **kwargs)
class MB_Baud(annotate.Choice):
_choices = [110, 300, 600, 1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200]
def coerce(self, val, configurable):
return int(val)
def __init__(self, *args, **kwargs):
annotate.Choice.__init__(self, choices = self._choices, *args, **kwargs)
class MB_Parity(annotate.Choice):
# For more info on what this class really does, have a look at the code in
# file twisted/nevow/annotate.py
# grab this code from $git clone https://github.com/twisted/nevow/
#
# Warning: do _not_ name this variable choice[] without underscore, as that name is
# already used for another similar variable by the underlying class annotate.Choice
_choices = [ 0, 1, 2 ]
_label = ["none", "odd", "even"]
def choice_to_label(self, key):
#PLCObject.LogMessage("Modbus web server extension::choice_to_label() " + str(key))
return self._label[key]
def coerce(self, val, configurable):
"""Coerce a value with the help of an object, which is the object
we are configuring.
"""
# Basically, make sure the value the user introduced is valid, and transform
# into something that is valid if necessary or mark it as an error
# (by raising an exception ??).
#
# We are simply using this functions to transform the input value (a string)
# into an integer. Note that although the available options are all
# integers (0, 1 or 2), even though what is shown on the user interface
# are actually strings, i.e. the labels), these parameters are for some
# reason being parsed as strings, so we need to map them back to an
# integer.
#
#PLCObject.LogMessage("Modbus web server extension::coerce " + val )
return int(val)
def __init__(self, *args, **kwargs):
annotate.Choice.__init__(self,
choices = self._choices,
stringify = self.choice_to_label,
*args, **kwargs)
# Parameters we will need to get from the C code, but that will not be shown
# on the web interface. Common to all modbus entry types (client/server, tcp/rtu/ascii)
#
# The annotate type entry is basically useless and is completely ignored.
# We kee that entry so that this list can later be correctly merged with the
# following lists...
General_parameters = [
# param. name label ctype type annotate type
# (C code var name) (used on web interface) (C data type) (web data type)
# (annotate.String,
# annotate.Integer, ...)
("config_name" , _("") , ctypes.c_char_p, annotate.String),
("addr_type" , _("") , ctypes.c_char_p, annotate.String)
]
# Parameters we will need to get from the C code, and that _will_ be shown
# on the web interface.
TCPclient_parameters = [
# param. name label ctype type annotate type
# (C code var name) (used on web interface) (C data type) (web data type)
# (annotate.String,
# annotate.Integer, ...)
("host" , _("Remote IP Address") , ctypes.c_char_p, MB_StrippedString),
("port" , _("Remote Port Number") , ctypes.c_char_p, MB_StrippedString),
("comm_period" , _("Invocation Rate (ms)") , ctypes.c_ulonglong, annotate.Integer )
]
RTUclient_parameters = [
# param. name label ctype type annotate type
# (C code var name) (used on web interface) (C data type) (web data type)
# (annotate.String,
# annotate.Integer, ...)
("device" , _("Serial Port") , ctypes.c_char_p, MB_StrippedString),
("baud" , _("Baud Rate") , ctypes.c_int, MB_Baud ),
("parity" , _("Parity") , ctypes.c_int, MB_Parity ),
("stop_bits" , _("Stop Bits") , ctypes.c_int, MB_StopBits ),
("comm_period" , _("Invocation Rate (ms)") , ctypes.c_ulonglong, annotate.Integer)
]
TCPserver_parameters = [
# param. name label ctype type annotate type
# (C code var name) (used on web interface) (C data type) (web data type)
# (annotate.String,
# annotate.Integer, ...)
("host" , _("Local IP Address") , ctypes.c_char_p, MB_StrippedString),
("port" , _("Local Port Number") , ctypes.c_char_p, MB_StrippedString),
("slave_id" , _("Slave ID") , ctypes.c_ubyte, annotate.Integer )
]
RTUslave_parameters = [
# param. name label ctype type annotate type
# (C code var name) (used on web interface) (C data type) (web data type)
# (annotate.String,
# annotate.Integer, ...)
("device" , _("Serial Port") , ctypes.c_char_p, MB_StrippedString),
("baud" , _("Baud Rate") , ctypes.c_int, MB_Baud ),
("parity" , _("Parity") , ctypes.c_int, MB_Parity ),
("stop_bits" , _("Stop Bits") , ctypes.c_int, MB_StopBits ),
("slave_id" , _("Slave ID") , ctypes.c_ubyte, annotate.Integer)
]
# Dictionary containing List of Web viewable parameters
# Note: the dictionary key must be the same as the string returned by the
# __modbus_get_ClientNode_addr_type()
# __modbus_get_ServerNode_addr_type()
# functions implemented in C (see modbus/mb_runtime.c)
_client_WebParamListDict = {}
_client_WebParamListDict["tcp" ] = TCPclient_parameters
_client_WebParamListDict["rtu" ] = RTUclient_parameters
_client_WebParamListDict["ascii"] = [] # (Note: ascii not yet implemented in Beremiz modbus plugin)
_server_WebParamListDict = {}
_server_WebParamListDict["tcp" ] = TCPserver_parameters
_server_WebParamListDict["rtu" ] = RTUslave_parameters
_server_WebParamListDict["ascii"] = [] # (Note: ascii not yet implemented in Beremiz modbus plugin)
WebParamListDictDict = {}
WebParamListDictDict['client'] = _client_WebParamListDict
WebParamListDictDict['server'] = _server_WebParamListDict
def _SetModbusSavedConfiguration(WebNode_id, newConfig):
""" Stores a dictionary in a persistant file containing the Modbus parameter configuration """
WebNode_entry = _WebNodeList[WebNode_id]
if WebNode_entry["DefaultConfiguration"] == newConfig:
_DelModbusSavedConfiguration(WebNode_id)
WebNode_entry["ModbusSavedConfiguration"] = None
else:
# Add the addr_type and node_type to the data that will be saved to file
# This allows us to confirm the saved data contains the correct addr_type
# when loading from file
save_info = {}
save_info["addr_type"] = WebNode_entry["addr_type"]
save_info["node_type"] = WebNode_entry["node_type"]
save_info["config" ] = newConfig
filename = WebNode_entry["filename"]
with open(os.path.realpath(filename), 'w') as f:
json.dump(save_info, f, sort_keys=True, indent=4)
WebNode_entry["ModbusSavedConfiguration"] = newConfig
def _DelModbusSavedConfiguration(WebNode_id):
""" Deletes the file cotaining the persistent Modbus configuration """
filename = _WebNodeList[WebNode_id]["filename"]
if os.path.exists(filename):
os.remove(filename)
def _GetModbusSavedConfiguration(WebNode_id):
"""
Returns a dictionary containing the Modbus parameter configuration
that was last saved to file. If no file exists, or file contains
wrong addr_type (i.e. 'tcp', 'rtu' or 'ascii' -> does not match the
addr_type of the WebNode_id), then return None
"""
filename = _WebNodeList[WebNode_id]["filename"]
try:
#if os.path.isfile(filename):
save_info = json.load(open(os.path.realpath(filename)))
except Exception:
return None
if save_info["addr_type"] != _WebNodeList[WebNode_id]["addr_type"]:
return None
if save_info["node_type"] != _WebNodeList[WebNode_id]["node_type"]:
return None
if "config" not in save_info:
return None
saved_config = save_info["config"]
#if _CheckConfiguration(saved_config):
# return saved_config
#else:
# return None
return saved_config
def _GetModbusPLCConfiguration(WebNode_id):
"""
Returns a dictionary containing the current Modbus parameter configuration
stored in the C variables in the loaded PLC (.so file)
"""
current_config = {}
C_node_id = _WebNodeList[WebNode_id]["C_node_id"]
WebParamList = _WebNodeList[WebNode_id]["WebParamList"]
GetParamFuncs = _WebNodeList[WebNode_id]["GetParamFuncs"]
for par_name, x1, x2, x3 in WebParamList:
value = GetParamFuncs[par_name](C_node_id)
if value is not None:
current_config[par_name] = value
return current_config
def _SetModbusPLCConfiguration(WebNode_id, newconfig):
"""
Stores the Modbus parameter configuration into the
the C variables in the loaded PLC (.so file)
"""
C_node_id = _WebNodeList[WebNode_id]["C_node_id"]
SetParamFuncs = _WebNodeList[WebNode_id]["SetParamFuncs"]
for par_name in newconfig:
value = newconfig[par_name]
if value is not None:
SetParamFuncs[par_name](C_node_id, value)
def _GetModbusWebviewConfigurationValue(ctx, WebNode_id, argument):
"""
Callback function, called by the web interface (NevowServer.py)
to fill in the default value of each parameter of the web form
Note that the real callback function is a dynamically created function that
will simply call this function to do the work. It will also pass the WebNode_id
as a parameter.
"""
try:
return _WebNodeList[WebNode_id]["WebviewConfiguration"][argument.name]
except Exception:
return ""
def OnModbusButtonSave(**kwargs):
"""
Function called when user clicks 'Save' button in web interface
The function will configure the Modbus plugin in the PLC with the values
specified in the web interface. However, values must be validated first!
Note that this function does not get called directly. The real callback
function is the dynamic __OnButtonSave() function, which will add the
"WebNode_id" argument, and call this function to do the work.
"""
#PLCObject.LogMessage("Modbus web server extension::OnModbusButtonSave() Called")
newConfig = {}
WebNode_id = kwargs.get("WebNode_id", None)
WebParamList = _WebNodeList[WebNode_id]["WebParamList"]
for par_name, x1, x2, x3 in WebParamList:
value = kwargs.get(par_name, None)
if value is not None:
newConfig[par_name] = value
# First check if configuration is OK.
# Note that this is not currently required, as we use drop down choice menus
# for baud, parity and sop bits, so the values should always be correct!
#if not _CheckWebConfiguration(newConfig):
# return
# store to file the new configuration so that
# we can recoup the configuration the next time the PLC
# has a cold start (i.e. when Beremiz_service.py is retarted)
_SetModbusSavedConfiguration(WebNode_id, newConfig)
# Configure PLC with the current Modbus parameters
_SetModbusPLCConfiguration(WebNode_id, newConfig)
# Update the viewable configuration
# The PLC may have coerced the values on calling _SetModbusPLCConfiguration()
# so we do not set it directly to newConfig
_WebNodeList[WebNode_id]["WebviewConfiguration"] = _GetModbusPLCConfiguration(WebNode_id)
def OnModbusButtonReset(**kwargs):
"""
Function called when user clicks 'Delete' button in web interface
The function will delete the file containing the persistent
Modbus configution
"""
WebNode_id = kwargs.get("WebNode_id", None)
# Delete the file
_DelModbusSavedConfiguration(WebNode_id)
# Set the current configuration to the default (hardcoded in C)
new_config = _WebNodeList[WebNode_id]["DefaultConfiguration"]
_SetModbusPLCConfiguration(WebNode_id, new_config)
#Update the webviewconfiguration
_WebNodeList[WebNode_id]["WebviewConfiguration"] = new_config
# Reset ModbusSavedConfiguration
_WebNodeList[WebNode_id]["ModbusSavedConfiguration"] = None
def _AddWebNode(C_node_id, node_type, GetParamFuncs, SetParamFuncs):
"""
Load from the compiled code (.so file, aloready loaded into memmory)
the configuration parameters of a specific Modbus plugin node.
This function works with both client and server nodes, depending on the
Get/SetParamFunc dictionaries passed to it (either the client or the server
node versions of the Get/Set functions)
"""
WebNode_entry = {}
# Get the config_name from the C code...
config_name = GetParamFuncs["config_name"](C_node_id)
# Get the addr_type from the C code...
# addr_type will be one of "tcp", "rtu" or "ascii"
addr_type = GetParamFuncs["addr_type" ](C_node_id)
# For some operations we cannot use the config name (e.g. filename to store config)
# because the user may be using characters that are invalid for that purpose ('/' for
# example), so we create a hash of the config_name, and use that instead.
config_hash = hashlib.md5(config_name).hexdigest()
#PLCObject.LogMessage("Modbus web server extension::_AddWebNode("+str(C_node_id)+") config_name="+config_name)
# Add the new entry to the global list
# Note: it is OK, and actually necessary, to do this _before_ seting all the parameters in WebNode_entry
# WebNode_entry will be stored as a reference, so we can later insert parameters at will.
global _WebNodeList
_WebNodeList.append(WebNode_entry)
WebNode_id = len(_WebNodeList) - 1
# store all WebNode relevant data for future reference
#
# Note that "WebParamList" will reference one of:
# - TCPclient_parameters, TCPserver_parameters, RTUclient_parameters, RTUslave_parameters
WebNode_entry["C_node_id" ] = C_node_id
WebNode_entry["config_name" ] = config_name
WebNode_entry["config_hash" ] = config_hash
WebNode_entry["filename" ] = os.path.join(_ModbusConfFiledir, "Modbus_config_" + config_hash + ".json")
WebNode_entry["GetParamFuncs"] = GetParamFuncs
WebNode_entry["SetParamFuncs"] = SetParamFuncs
WebNode_entry["WebParamList" ] = WebParamListDictDict[node_type][addr_type]
WebNode_entry["addr_type" ] = addr_type # 'tcp', 'rtu', or 'ascii' (as returned by C function)
WebNode_entry["node_type" ] = node_type # 'client', 'server'
# Dictionary that contains the Modbus configuration currently being shown
# on the web interface
# This configuration will almost always be identical to the current
# configuration in the PLC (i.e., the current state stored in the
# C variables in the .so file).
# The configuration viewed on the web will only be different to the current
# configuration when the user edits the configuration, and when
# the user asks to save an edited configuration that contains an error.
WebNode_entry["WebviewConfiguration"] = None
# Upon PLC load, this Dictionary is initialised with the Modbus configuration
# hardcoded in the C file
# (i.e. the configuration inserted in Beremiz IDE when project was compiled)
WebNode_entry["DefaultConfiguration"] = _GetModbusPLCConfiguration(WebNode_id)
WebNode_entry["WebviewConfiguration"] = WebNode_entry["DefaultConfiguration"]
# Dictionary that stores the Modbus configuration currently stored in a file
# Currently only used to decide whether or not to show the "Delete" button on the
# web interface (only shown if "ModbusSavedConfiguration" is not None)
SavedConfig = _GetModbusSavedConfiguration(WebNode_id)
WebNode_entry["ModbusSavedConfiguration"] = SavedConfig
if SavedConfig is not None:
_SetModbusPLCConfiguration(WebNode_id, SavedConfig)
WebNode_entry["WebviewConfiguration"] = SavedConfig
# Define the format for the web form used to show/change the current parameters
# We first declare a dynamic function to work as callback to obtain the default values for each parameter
# Note: We transform every parameter into a string
# This is not strictly required for parameters of type annotate.Integer that will correctly
# accept the default value as an Integer python object
# This is obviously also not required for parameters of type annotate.String, that are
# always handled as strings.
# However, the annotate.Choice parameters (and all parameters that derive from it,
# sucn as Parity, Baud, etc.) require the default value as a string
# even though we store it as an integer, which is the data type expected
# by the set_***() C functions in mb_runtime.c
def __GetWebviewConfigurationValue(ctx, argument):
return str(_GetModbusWebviewConfigurationValue(ctx, WebNode_id, argument))
webFormInterface = [(name, web_dtype (label=web_label, default=__GetWebviewConfigurationValue))
for name, web_label, c_dtype, web_dtype in WebNode_entry["WebParamList"]]
# Configure the web interface to include the Modbus config parameters
def __OnButtonSave(**kwargs):
OnModbusButtonSave(WebNode_id=WebNode_id, **kwargs)
WebSettings = NS.newExtensionSetting("Modbus #"+ str(WebNode_id), config_hash)
WebSettings.addSettings(
"ModbusConfigParm" + config_hash, # name (internal, may not contain spaces, ...)
_("Modbus Configuration: ") + config_name, # description (user visible label)
webFormInterface, # fields
_("Apply"), # button label
__OnButtonSave) # callback
def __OnButtonReset(**kwargs):
return OnModbusButtonReset(WebNode_id = WebNode_id, **kwargs)
def getModbusConfigStatus():
if WebNode_entry["WebviewConfiguration"] == WebNode_entry["DefaultConfiguration"]:
return "Unchanged"
return "Modified"
WebSettings.addSettings(
"ModbusConfigDelSaved" + config_hash, # name (internal, may not contain spaces, ...)
_("Modbus Configuration: ") + config_name, # description (user visible label)
[ ("status",
annotate.String(label=_("Current state"),
immutable=True,
default=lambda *k:getModbusConfigStatus())),
], # fields (empty, no parameters required!)
_("Reset"), # button label
__OnButtonReset)
def _runtime_0_modbus_websettings_init():
"""
Callback function, called (by PLCObject.py) when a new PLC program
(i.e. XXX.so file) is transfered to the PLC runtime
and loaded into memory
"""
#PLCObject.LogMessage("Modbus web server extension::OnLoadPLC() Called...")
if PLCObject.PLClibraryHandle is None:
# PLC was loaded but we don't have access to the library of compiled code (.so lib)?
# Hmm... This shold never occur!!
return
# Get the number of Modbus Client and Servers (Modbus plugin)
# configured in the currently loaded PLC project (i.e., the .so file)
# If the "__modbus_plugin_client_node_count"
# or the "__modbus_plugin_server_node_count" C variables
# are not present in the .so file we conclude that the currently loaded
# PLC does not have the Modbus plugin included (situation (2b) described above init())
try:
# XXX TODO : stop reading from PLC .so file. This code is template code
# that can use modbus extension build data, such as client node count.
client_count = ctypes.c_int.in_dll(PLCObject.PLClibraryHandle, "__modbus_plugin_client_node_count").value
server_count = ctypes.c_int.in_dll(PLCObject.PLClibraryHandle, "__modbus_plugin_server_node_count").value
except Exception:
# Loaded PLC does not have the Modbus plugin => nothing to do
# (i.e. do _not_ configure and make available the Modbus web interface)
return
if client_count < 0: client_count = 0
if server_count < 0: server_count = 0
if (client_count == 0) and (server_count == 0):
# The Modbus plugin in the loaded PLC does not have any client and servers configured
# => nothing to do (i.e. do _not_ configure and make available the Modbus web interface)
return
# Map the get/set functions (written in C code) we will be using to get/set the configuration parameters
# Will contain references to the C functions (implemented in beremiz/modbus/mb_runtime.c)
GetClientParamFuncs = {}
SetClientParamFuncs = {}
GetServerParamFuncs = {}
SetServerParamFuncs = {}
# XXX TODO : stop reading from PLC .so file. This code is template code
# that can use modbus extension build data
for name, web_label, c_dtype, web_dtype in TCPclient_parameters + RTUclient_parameters + General_parameters:
ParamFuncName = "__modbus_get_ClientNode_" + name
GetClientParamFuncs[name] = getattr(PLCObject.PLClibraryHandle, ParamFuncName)
GetClientParamFuncs[name].restype = c_dtype
GetClientParamFuncs[name].argtypes = [ctypes.c_int]
for name, web_label, c_dtype, web_dtype in TCPclient_parameters + RTUclient_parameters:
ParamFuncName = "__modbus_set_ClientNode_" + name
SetClientParamFuncs[name] = getattr(PLCObject.PLClibraryHandle, ParamFuncName)
SetClientParamFuncs[name].restype = None
SetClientParamFuncs[name].argtypes = [ctypes.c_int, c_dtype]
# XXX TODO : stop reading from PLC .so file. This code is template code
# that can use modbus extension build data
for name, web_label, c_dtype, web_dtype in TCPserver_parameters + RTUslave_parameters + General_parameters:
ParamFuncName = "__modbus_get_ServerNode_" + name
GetServerParamFuncs[name] = getattr(PLCObject.PLClibraryHandle, ParamFuncName)
GetServerParamFuncs[name].restype = c_dtype
GetServerParamFuncs[name].argtypes = [ctypes.c_int]
for name, web_label, c_dtype, web_dtype in TCPserver_parameters + RTUslave_parameters:
ParamFuncName = "__modbus_set_ServerNode_" + name
SetServerParamFuncs[name] = getattr(PLCObject.PLClibraryHandle, ParamFuncName)
SetServerParamFuncs[name].restype = None
SetServerParamFuncs[name].argtypes = [ctypes.c_int, c_dtype]
for node_id in range(client_count):
_AddWebNode(node_id, "client" ,GetClientParamFuncs, SetClientParamFuncs)
for node_id in range(server_count):
_AddWebNode(node_id, "server", GetServerParamFuncs, SetServerParamFuncs)
def _runtime_0_modbus_websettings_cleanup():
"""
Callback function, called (by PLCObject.py) when a PLC program is unloaded from memory
"""
#PLCObject.LogMessage("Modbus web server extension::OnUnLoadPLC() Called...")
# Delete the Modbus specific web interface extensions
# (Safe to ask to delete, even if it has not been added!)
global _WebNodeList
for index, WebNode_entry in enumerate(_WebNodeList):
config_hash = WebNode_entry["config_hash"]
NS.removeExtensionSetting(config_hash)
# Dele all entries...
_WebNodeList = []
__LOCATED_VAR(DINT,__QD1_0,Q,D,1,0)
__LOCATED_VAR(DINT,__QD1_1,Q,D,1,1)
__LOCATED_VAR(DINT,__QD1_2,Q,D,1,2)
__LOCATED_VAR(DINT,__QD1_3,Q,D,1,3)
void LOGGER_init__(LOGGER *data__, BOOL retain) {
__INIT_VAR(data__->EN,__BOOL_LITERAL(TRUE),retain)
__INIT_VAR(data__->ENO,__BOOL_LITERAL(TRUE),retain)
__INIT_VAR(data__->TRIG,__BOOL_LITERAL(FALSE),retain)
__INIT_VAR(data__->MSG,__STRING_LITERAL(0,""),retain)
__INIT_VAR(data__->LEVEL,LOGLEVEL__INFO,retain)
__INIT_VAR(data__->TRIG0,__BOOL_LITERAL(FALSE),retain)
}
// Code part
void LOGGER_body__(LOGGER *data__) {
// Control execution
if (!__GET_VAR(data__->EN)) {
__SET_VAR(data__->,ENO,,__BOOL_LITERAL(FALSE));
return;
}
else {
__SET_VAR(data__->,ENO,,__BOOL_LITERAL(TRUE));
}
// Initialise TEMP variables
if ((__GET_VAR(data__->TRIG,) && !(__GET_VAR(data__->TRIG0,)))) {
#define GetFbVar(var,...) __GET_VAR(data__->var,__VA_ARGS__)
#define SetFbVar(var,val,...) __SET_VAR(data__->,var,__VA_ARGS__,val)
LogMessage(GetFbVar(LEVEL),(char*)GetFbVar(MSG, .body),GetFbVar(MSG, .len));
#undef GetFbVar
#undef SetFbVar
;
};
__SET_VAR(data__->,TRIG0,,__GET_VAR(data__->TRIG,));
goto __end;
__end:
return;
} // LOGGER_body__()
void PYTHON_EVAL_init__(PYTHON_EVAL *data__, BOOL retain) {
__INIT_VAR(data__->EN,__BOOL_LITERAL(TRUE),retain)
__INIT_VAR(data__->ENO,__BOOL_LITERAL(TRUE),retain)
__INIT_VAR(data__->TRIG,__BOOL_LITERAL(FALSE),retain)
__INIT_VAR(data__->CODE,__STRING_LITERAL(0,""),retain)
__INIT_VAR(data__->ACK,__BOOL_LITERAL(FALSE),retain)
__INIT_VAR(data__->RESULT,__STRING_LITERAL(0,""),retain)
__INIT_VAR(data__->STATE,0,retain)
__INIT_VAR(data__->BUFFER,__STRING_LITERAL(0,""),retain)
__INIT_VAR(data__->PREBUFFER,__STRING_LITERAL(0,""),retain)
__INIT_VAR(data__->TRIGM1,__BOOL_LITERAL(FALSE),retain)
__INIT_VAR(data__->TRIGGED,__BOOL_LITERAL(FALSE),retain)
}
// Code part
void PYTHON_EVAL_body__(PYTHON_EVAL *data__) {
// Control execution
if (!__GET_VAR(data__->EN)) {
__SET_VAR(data__->,ENO,,__BOOL_LITERAL(FALSE));
return;
}
else {
__SET_VAR(data__->,ENO,,__BOOL_LITERAL(TRUE));
}
// Initialise TEMP variables
__IL_DEFVAR_T __IL_DEFVAR;
__IL_DEFVAR_T __IL_DEFVAR_BACK;
#define GetFbVar(var,...) __GET_VAR(data__->var,__VA_ARGS__)
#define SetFbVar(var,val,...) __SET_VAR(data__->,var,__VA_ARGS__,val)
extern void __PythonEvalFB(int, PYTHON_EVAL*);__PythonEvalFB(0, data__);
#undef GetFbVar
#undef SetFbVar
;
goto __end;
__end:
return;
} // PYTHON_EVAL_body__()
void PYTHON_POLL_init__(PYTHON_POLL *data__, BOOL retain) {
__INIT_VAR(data__->EN,__BOOL_LITERAL(TRUE),retain)
__INIT_VAR(data__->ENO,__BOOL_LITERAL(TRUE),retain)
__INIT_VAR(data__->TRIG,__BOOL_LITERAL(FALSE),retain)
__INIT_VAR(data__->CODE,__STRING_LITERAL(0,""),retain)
__INIT_VAR(data__->ACK,__BOOL_LITERAL(FALSE),retain)
__INIT_VAR(data__->RESULT,__STRING_LITERAL(0,""),retain)
__INIT_VAR(data__->STATE,0,retain)
__INIT_VAR(data__->BUFFER,__STRING_LITERAL(0,""),retain)
__INIT_VAR(data__->PREBUFFER,__STRING_LITERAL(0,""),retain)
__INIT_VAR(data__->TRIGM1,__BOOL_LITERAL(FALSE),retain)
__INIT_VAR(data__->TRIGGED,__BOOL_LITERAL(FALSE),retain)
}
// Code part
void PYTHON_POLL_body__(PYTHON_POLL *data__) {
// Control execution
if (!__GET_VAR(data__->EN)) {
__SET_VAR(data__->,ENO,,__BOOL_LITERAL(FALSE));
return;
}
else {
__SET_VAR(data__->,ENO,,__BOOL_LITERAL(TRUE));
}
// Initialise TEMP variables
__IL_DEFVAR_T __IL_DEFVAR;
__IL_DEFVAR_T __IL_DEFVAR_BACK;
#define GetFbVar(var,...) __GET_VAR(data__->var,__VA_ARGS__)
#define SetFbVar(var,val,...) __SET_VAR(data__->,var,__VA_ARGS__,val)
extern void __PythonEvalFB(int, PYTHON_EVAL*);__PythonEvalFB(1,(PYTHON_EVAL*)(void*)data__);
#undef GetFbVar
#undef SetFbVar
;
goto __end;
__end:
return;
} // PYTHON_POLL_body__()
void PYTHON_GEAR_init__(PYTHON_GEAR *data__, BOOL retain) {
__INIT_VAR(data__->EN,__BOOL_LITERAL(TRUE),retain)
__INIT_VAR(data__->ENO,__BOOL_LITERAL(TRUE),retain)
__INIT_VAR(data__->N,0,retain)
__INIT_VAR(data__->TRIG,__BOOL_LITERAL(FALSE),retain)
__INIT_VAR(data__->CODE,__STRING_LITERAL(0,""),retain)
__INIT_VAR(data__->ACK,__BOOL_LITERAL(FALSE),retain)
__INIT_VAR(data__->RESULT,__STRING_LITERAL(0,""),retain)
PYTHON_EVAL_init__(&data__->PY_EVAL,retain);
__INIT_VAR(data__->COUNTER,0,retain)
__INIT_VAR(data__->_TMP_ADD10_OUT,0,retain)
__INIT_VAR(data__->_TMP_EQ13_OUT,__BOOL_LITERAL(FALSE),retain)
__INIT_VAR(data__->_TMP_SEL15_OUT,0,retain)
__INIT_VAR(data__->_TMP_AND7_OUT,__BOOL_LITERAL(FALSE),retain)
}
// Code part
void PYTHON_GEAR_body__(PYTHON_GEAR *data__) {
// Control execution
if (!__GET_VAR(data__->EN)) {
__SET_VAR(data__->,ENO,,__BOOL_LITERAL(FALSE));
return;
}
else {
__SET_VAR(data__->,ENO,,__BOOL_LITERAL(TRUE));
}
// Initialise TEMP variables
__SET_VAR(data__->,_TMP_ADD10_OUT,,ADD__UINT__UINT(
(BOOL)__BOOL_LITERAL(TRUE),
NULL,
(UINT)2,
(UINT)__GET_VAR(data__->COUNTER,),
(UINT)1));
__SET_VAR(data__->,_TMP_EQ13_OUT,,EQ__BOOL__UINT(
(BOOL)__BOOL_LITERAL(TRUE),
NULL,
(UINT)2,
(UINT)__GET_VAR(data__->N,),
(UINT)__GET_VAR(data__->_TMP_ADD10_OUT,)));
__SET_VAR(data__->,_TMP_SEL15_OUT,,SEL__UINT__BOOL__UINT__UINT(
(BOOL)__BOOL_LITERAL(TRUE),
NULL,
(BOOL)__GET_VAR(data__->_TMP_EQ13_OUT,),
(UINT)__GET_VAR(data__->_TMP_ADD10_OUT,),
(UINT)0));
__SET_VAR(data__->,COUNTER,,__GET_VAR(data__->_TMP_SEL15_OUT,));
__SET_VAR(data__->,_TMP_AND7_OUT,,AND__BOOL__BOOL(
(BOOL)__BOOL_LITERAL(TRUE),
NULL,
(UINT)2,
(BOOL)__GET_VAR(data__->_TMP_EQ13_OUT,),
(BOOL)__GET_VAR(data__->TRIG,)));
__SET_VAR(data__->PY_EVAL.,TRIG,,__GET_VAR(data__->_TMP_AND7_OUT,));
__SET_VAR(data__->PY_EVAL.,CODE,,__GET_VAR(data__->CODE,));
PYTHON_EVAL_body__(&data__->PY_EVAL);
__SET_VAR(data__->,ACK,,__GET_VAR(data__->PY_EVAL.ACK,));
__SET_VAR(data__->,RESULT,,__GET_VAR(data__->PY_EVAL.RESULT,));
goto __end;
__end:
return;
} // PYTHON_GEAR_body__()
void COUNTERST_init__(COUNTERST *data__, BOOL retain) {
__INIT_VAR(data__->EN,__BOOL_LITERAL(TRUE),retain)
__INIT_VAR(data__->ENO,__BOOL_LITERAL(TRUE),retain)
__INIT_VAR(data__->RESET,__BOOL_LITERAL(FALSE),retain)
__INIT_VAR(data__->OUT1,0,retain)
__INIT_VAR(data__->OUT0,0,retain)
__INIT_VAR(data__->CNT1,0,retain)
__INIT_VAR(data__->CNT0,0,retain)
__INIT_EXTERNAL(INT,RESETCOUNTERVALUE,data__->RESETCOUNTERVALUE,retain)
__INIT_EXTERNAL(DINT,RELAY0,data__->RELAY0,retain)
__INIT_EXTERNAL(DINT,RELAY1,data__->RELAY1,retain)
__INIT_EXTERNAL(DINT,RELAY2,data__->RELAY2,retain)
__INIT_EXTERNAL(DINT,RELAY3,data__->RELAY3,retain)
}
// Code part
void COUNTERST_body__(COUNTERST *data__) {
// Control execution
if (!__GET_VAR(data__->EN)) {
__SET_VAR(data__->,ENO,,__BOOL_LITERAL(FALSE));
return;
}
else {
__SET_VAR(data__->,ENO,,__BOOL_LITERAL(TRUE));
}
// Initialise TEMP variables
if (__GET_VAR(data__->RESET,)) {
__SET_VAR(data__->,CNT0,,__GET_EXTERNAL(data__->RESETCOUNTERVALUE,));
__SET_VAR(data__->,CNT1,,__GET_EXTERNAL(data__->RESETCOUNTERVALUE,));
__SET_VAR(data__->,RESET,,__BOOL_LITERAL(FALSE));
} else {
__SET_VAR(data__->,CNT0,,(__GET_VAR(data__->CNT0,) + 1));
__SET_VAR(data__->,CNT1,,(__GET_VAR(data__->CNT1,) + 1));
if ((__GET_VAR(data__->CNT1,) == 50)) {
__SET_EXTERNAL(data__->,RELAY0,,1);
__SET_EXTERNAL(data__->,RELAY1,,1);
__SET_EXTERNAL(data__->,RELAY2,,1);
__SET_EXTERNAL(data__->,RELAY3,,1);
};
if ((__GET_VAR(data__->CNT1,) == 100)) {
__SET_EXTERNAL(data__->,RELAY0,,0);
__SET_EXTERNAL(data__->,RELAY1,,0);
__SET_EXTERNAL(data__->,RELAY2,,0);
__SET_EXTERNAL(data__->,RELAY3,,0);
__SET_VAR(data__->,CNT1,,0);
};
};
__SET_VAR(data__->,OUT1,,__GET_VAR(data__->CNT1,));
__SET_VAR(data__->,OUT0,,__GET_VAR(data__->CNT0,));
goto __end;
__end:
return;
} // COUNTERST_body__()
void PLC_PRG_init__(PLC_PRG *data__, BOOL retain) {
__INIT_VAR(data__->RESET,__BOOL_LITERAL(FALSE),retain)
__INIT_VAR(data__->CNT0,0,retain)
__INIT_VAR(data__->CNT1,0,retain)
COUNTERST_init__(&data__->COUNTERST0,retain);
}
// Code part
void PLC_PRG_body__(PLC_PRG *data__) {
// Initialise TEMP variables
__SET_VAR(data__->COUNTERST0.,RESET,,__GET_VAR(data__->RESET,));
COUNTERST_body__(&data__->COUNTERST0);
__SET_VAR(data__->,CNT1,,__GET_VAR(data__->COUNTERST0.OUT1,));
__SET_VAR(data__->,CNT0,,__GET_VAR(data__->COUNTERST0.OUT0,));
goto __end;
__end:
return;
} // PLC_PRG_body__()
#include "beremiz.h"
#ifndef __POUS_H
#define __POUS_H
#include "accessor.h"
#include "iec_std_lib.h"
__DECLARE_ENUMERATED_TYPE(LOGLEVEL,
LOGLEVEL__CRITICAL,
LOGLEVEL__WARNING,
LOGLEVEL__INFO,
LOGLEVEL__DEBUG
)
// FUNCTION_BLOCK LOGGER
// Data part
typedef struct {
// FB Interface - IN, OUT, IN_OUT variables
__DECLARE_VAR(BOOL,EN)
__DECLARE_VAR(BOOL,ENO)
__DECLARE_VAR(BOOL,TRIG)
__DECLARE_VAR(STRING,MSG)
__DECLARE_VAR(LOGLEVEL,LEVEL)
// FB private variables - TEMP, private and located variables
__DECLARE_VAR(BOOL,TRIG0)
} LOGGER;
void LOGGER_init__(LOGGER *data__, BOOL retain);
// Code part
void LOGGER_body__(LOGGER *data__);
// FUNCTION_BLOCK PYTHON_EVAL
// Data part
typedef struct {
// FB Interface - IN, OUT, IN_OUT variables
__DECLARE_VAR(BOOL,EN)
__DECLARE_VAR(BOOL,ENO)
__DECLARE_VAR(BOOL,TRIG)
__DECLARE_VAR(STRING,CODE)
__DECLARE_VAR(BOOL,ACK)
__DECLARE_VAR(STRING,RESULT)
// FB private variables - TEMP, private and located variables
__DECLARE_VAR(DWORD,STATE)
__DECLARE_VAR(STRING,BUFFER)
__DECLARE_VAR(STRING,PREBUFFER)
__DECLARE_VAR(BOOL,TRIGM1)
__DECLARE_VAR(BOOL,TRIGGED)
} PYTHON_EVAL;
void PYTHON_EVAL_init__(PYTHON_EVAL *data__, BOOL retain);
// Code part
void PYTHON_EVAL_body__(PYTHON_EVAL *data__);
// FUNCTION_BLOCK PYTHON_POLL
// Data part
typedef struct {
// FB Interface - IN, OUT, IN_OUT variables
__DECLARE_VAR(BOOL,EN)
__DECLARE_VAR(BOOL,ENO)
__DECLARE_VAR(BOOL,TRIG)
__DECLARE_VAR(STRING,CODE)
__DECLARE_VAR(BOOL,ACK)
__DECLARE_VAR(STRING,RESULT)
// FB private variables - TEMP, private and located variables
__DECLARE_VAR(DWORD,STATE)
__DECLARE_VAR(STRING,BUFFER)
__DECLARE_VAR(STRING,PREBUFFER)
__DECLARE_VAR(BOOL,TRIGM1)
__DECLARE_VAR(BOOL,TRIGGED)
} PYTHON_POLL;
void PYTHON_POLL_init__(PYTHON_POLL *data__, BOOL retain);
// Code part
void PYTHON_POLL_body__(PYTHON_POLL *data__);
// FUNCTION_BLOCK PYTHON_GEAR
// Data part
typedef struct {
// FB Interface - IN, OUT, IN_OUT variables
__DECLARE_VAR(BOOL,EN)
__DECLARE_VAR(BOOL,ENO)
__DECLARE_VAR(UINT,N)
__DECLARE_VAR(BOOL,TRIG)
__DECLARE_VAR(STRING,CODE)
__DECLARE_VAR(BOOL,ACK)
__DECLARE_VAR(STRING,RESULT)
// FB private variables - TEMP, private and located variables
PYTHON_EVAL PY_EVAL;
__DECLARE_VAR(UINT,COUNTER)
__DECLARE_VAR(UINT,_TMP_ADD10_OUT)
__DECLARE_VAR(BOOL,_TMP_EQ13_OUT)
__DECLARE_VAR(UINT,_TMP_SEL15_OUT)
__DECLARE_VAR(BOOL,_TMP_AND7_OUT)
} PYTHON_GEAR;
void PYTHON_GEAR_init__(PYTHON_GEAR *data__, BOOL retain);
// Code part
void PYTHON_GEAR_body__(PYTHON_GEAR *data__);
// FUNCTION_BLOCK COUNTERST
// Data part
typedef struct {
// FB Interface - IN, OUT, IN_OUT variables
__DECLARE_VAR(BOOL,EN)
__DECLARE_VAR(BOOL,ENO)
__DECLARE_VAR(BOOL,RESET)
__DECLARE_VAR(INT,OUT1)
__DECLARE_VAR(INT,OUT0)
// FB private variables - TEMP, private and located variables
__DECLARE_VAR(INT,CNT1)
__DECLARE_VAR(INT,CNT0)
__DECLARE_EXTERNAL(INT,RESETCOUNTERVALUE)
__DECLARE_EXTERNAL(DINT,RELAY0)
__DECLARE_EXTERNAL(DINT,RELAY1)
__DECLARE_EXTERNAL(DINT,RELAY2)
__DECLARE_EXTERNAL(DINT,RELAY3)
} COUNTERST;
void COUNTERST_init__(COUNTERST *data__, BOOL retain);
// Code part
void COUNTERST_body__(COUNTERST *data__);
// PROGRAM PLC_PRG
// Data part
typedef struct {
// PROGRAM Interface - IN, OUT, IN_OUT variables
__DECLARE_VAR(BOOL,RESET)
__DECLARE_VAR(INT,CNT0)
__DECLARE_VAR(INT,CNT1)
// PROGRAM private variables - TEMP, private and located variables
COUNTERST COUNTERST0;
} PLC_PRG;
void PLC_PRG_init__(PLC_PRG *data__, BOOL retain);
// Code part
void PLC_PRG_body__(PLC_PRG *data__);
#endif //__POUS_H
// Programs
0;CONFIG.RESOURCE1.INSTANCE0;PLC_PRG;
// Variables
0;VAR;CONFIG.RESETCOUNTERVALUE;CONFIG.RESETCOUNTERVALUE;INT;INT;
1;OUT;CONFIG.RELAY0;CONFIG.RELAY0;DINT;DINT;
2;OUT;CONFIG.RELAY1;CONFIG.RELAY1;DINT;DINT;
3;OUT;CONFIG.RELAY2;CONFIG.RELAY2;DINT;DINT;
4;OUT;CONFIG.RELAY3;CONFIG.RELAY3;DINT;DINT;
5;FB;CONFIG.RESOURCE1.INSTANCE0;CONFIG.RESOURCE1.INSTANCE0;PLC_PRG;;
6;VAR;CONFIG.RESOURCE1.INSTANCE0.RESET;CONFIG.RESOURCE1.INSTANCE0.RESET;BOOL;BOOL;
7;VAR;CONFIG.RESOURCE1.INSTANCE0.CNT0;CONFIG.RESOURCE1.INSTANCE0.CNT0;INT;INT;
8;VAR;CONFIG.RESOURCE1.INSTANCE0.CNT1;CONFIG.RESOURCE1.INSTANCE0.CNT1;INT;INT;
9;FB;CONFIG.RESOURCE1.INSTANCE0.COUNTERST0;CONFIG.RESOURCE1.INSTANCE0.COUNTERST0;COUNTERST;;
10;VAR;CONFIG.RESOURCE1.INSTANCE0.COUNTERST0.EN;CONFIG.RESOURCE1.INSTANCE0.COUNTERST0.EN;BOOL;BOOL;
11;VAR;CONFIG.RESOURCE1.INSTANCE0.COUNTERST0.ENO;CONFIG.RESOURCE1.INSTANCE0.COUNTERST0.ENO;BOOL;BOOL;
12;VAR;CONFIG.RESOURCE1.INSTANCE0.COUNTERST0.RESET;CONFIG.RESOURCE1.INSTANCE0.COUNTERST0.RESET;BOOL;BOOL;
13;VAR;CONFIG.RESOURCE1.INSTANCE0.COUNTERST0.OUT1;CONFIG.RESOURCE1.INSTANCE0.COUNTERST0.OUT1;INT;INT;
14;VAR;CONFIG.RESOURCE1.INSTANCE0.COUNTERST0.OUT0;CONFIG.RESOURCE1.INSTANCE0.COUNTERST0.OUT0;INT;INT;
15;VAR;CONFIG.RESOURCE1.INSTANCE0.COUNTERST0.CNT1;CONFIG.RESOURCE1.INSTANCE0.COUNTERST0.CNT1;INT;INT;
16;VAR;CONFIG.RESOURCE1.INSTANCE0.COUNTERST0.CNT0;CONFIG.RESOURCE1.INSTANCE0.COUNTERST0.CNT0;INT;INT;
17;EXT;CONFIG.RESOURCE1.INSTANCE0.COUNTERST0.RESETCOUNTERVALUE;CONFIG.RESOURCE1.INSTANCE0.COUNTERST0.RESETCOUNTERVALUE;INT;INT;
18;EXT;CONFIG.RESOURCE1.INSTANCE0.COUNTERST0.RELAY0;CONFIG.RESOURCE1.INSTANCE0.COUNTERST0.RELAY0;DINT;DINT;
19;EXT;CONFIG.RESOURCE1.INSTANCE0.COUNTERST0.RELAY1;CONFIG.RESOURCE1.INSTANCE0.COUNTERST0.RELAY1;DINT;DINT;
20;EXT;CONFIG.RESOURCE1.INSTANCE0.COUNTERST0.RELAY2;CONFIG.RESOURCE1.INSTANCE0.COUNTERST0.RELAY2;DINT;DINT;
21;EXT;CONFIG.RESOURCE1.INSTANCE0.COUNTERST0.RELAY3;CONFIG.RESOURCE1.INSTANCE0.COUNTERST0.RELAY3;DINT;DINT;
// Ticktime
20000000
#ifndef _BEREMIZ_H_
#define _BEREMIZ_H_
/* Beremiz' header file for use by extensions */
#include "iec_types.h"
#define LOG_LEVELS 4
#define LOG_CRITICAL 0
#define LOG_WARNING 1
#define LOG_INFO 2
#define LOG_DEBUG 3
extern unsigned long long common_ticktime__;
#ifdef TARGET_LOGGING_DISABLE
static inline int LogMessage(uint8_t level, char* buf, uint32_t size)
{
(void)level;
(void)buf;
(void)size;
return 0;
}
#else
int LogMessage(uint8_t level, char* buf, uint32_t size);
#endif
long AtomicCompareExchange(long* atomicvar,long compared, long exchange);
void *create_RT_to_nRT_signal(char* name);
void delete_RT_to_nRT_signal(void* handle);
int wait_RT_to_nRT_signal(void* handle);
int unblock_RT_to_nRT_signal(void* handle);
void nRT_reschedule(void);
#endif
/*******************************************/
/* FILE GENERATED BY iec2c */
/* Editing this file is not recommended... */
/*******************************************/
#include "iec_std_lib.h"
#include "accessor.h"
#include "POUS.h"
// CONFIGURATION CONFIG
__DECLARE_GLOBAL(INT,CONFIG,RESETCOUNTERVALUE)
__DECLARE_GLOBAL_LOCATION(DINT,__QD1_0)
__DECLARE_GLOBAL_LOCATED(DINT,CONFIG,RELAY0)
__DECLARE_GLOBAL_LOCATION(DINT,__QD1_1)
__DECLARE_GLOBAL_LOCATED(DINT,CONFIG,RELAY1)
__DECLARE_GLOBAL_LOCATION(DINT,__QD1_2)
__DECLARE_GLOBAL_LOCATED(DINT,CONFIG,RELAY2)
__DECLARE_GLOBAL_LOCATION(DINT,__QD1_3)
__DECLARE_GLOBAL_LOCATED(DINT,CONFIG,RELAY3)
void RESOURCE1_init__(void);
void config_init__(void) {
BOOL retain;
retain = 0;
__INIT_GLOBAL(INT,RESETCOUNTERVALUE,__INITIAL_VALUE(0),retain)
__INIT_GLOBAL_LOCATED(CONFIG,RELAY0,__QD1_0,retain)
__INIT_GLOBAL(DINT,RELAY0,__INITIAL_VALUE(0),retain)
__INIT_GLOBAL_LOCATED(CONFIG,RELAY1,__QD1_1,retain)
__INIT_GLOBAL(DINT,RELAY1,__INITIAL_VALUE(0),retain)
__INIT_GLOBAL_LOCATED(CONFIG,RELAY2,__QD1_2,retain)
__INIT_GLOBAL(DINT,RELAY2,__INITIAL_VALUE(0),retain)
__INIT_GLOBAL_LOCATED(CONFIG,RELAY3,__QD1_3,retain)
__INIT_GLOBAL(DINT,RELAY3,__INITIAL_VALUE(0),retain)
RESOURCE1_init__();
}
void RESOURCE1_run__(unsigned long tick);
void config_run__(unsigned long tick) {
RESOURCE1_run__(tick);
}
unsigned long long common_ticktime__ = 20000000ULL * 1ULL; /*ns*/
unsigned long greatest_tick_count__ = (unsigned long)0UL; /*tick*/
#include "beremiz.h"
__DECLARE_GLOBAL_PROTOTYPE(INT,RESETCOUNTERVALUE)
__DECLARE_GLOBAL_PROTOTYPE(DINT,RELAY0)
__DECLARE_GLOBAL_PROTOTYPE(DINT,RELAY1)
__DECLARE_GLOBAL_PROTOTYPE(DINT,RELAY2)
__DECLARE_GLOBAL_PROTOTYPE(DINT,RELAY3)
FUNCTION_BLOCK CounterST
VAR_INPUT
Reset : BOOL;
END_VAR
VAR_OUTPUT
Out1 : INT;
Out0 : INT;
END_VAR
VAR
Cnt1 : INT;
Cnt0 : INT;
END_VAR
VAR_EXTERNAL
ResetCounterValue : INT;
Relay0 : DINT;
Relay1 : DINT;
Relay2 : DINT;
Relay3 : DINT;
END_VAR
IF Reset THEN
Cnt0 := ResetCounterValue;
Cnt1 := ResetCounterValue;
Reset := False;
ELSE
Cnt0 := Cnt0 + 1;
Cnt1 := Cnt1 + 1;
IF Cnt1 = 50 THEN
Relay0 := 1;
Relay1 := 1;
Relay2 := 1;
Relay3 := 1;
END_IF;
IF Cnt1 = 100 THEN
Relay0 := 0;
Relay1 := 0;
Relay2 := 0;
Relay3 := 0;
Cnt1 := 0;
END_IF;
END_IF;
Out1 := Cnt1;
Out0 := Cnt0;
END_FUNCTION_BLOCK
PROGRAM plc_prg
VAR_INPUT
Reset : BOOL;
END_VAR
VAR_OUTPUT
Cnt0 : INT;
Cnt1 : INT;
END_VAR
VAR
CounterST0 : CounterST;
END_VAR
CounterST0(Reset := Reset);
Cnt1 := CounterST0.Out1;
Cnt0 := CounterST0.Out0;
END_PROGRAM
CONFIGURATION config
VAR_GLOBAL
ResetCounterValue : INT := 0;
Relay0 AT %QD1.0 : DINT := 0;
Relay1 AT %QD1.1 : DINT := 0;
Relay2 AT %QD1.2 : DINT := 0;
Relay3 AT %QD1.3 : DINT := 0;
END_VAR
RESOURCE resource1 ON PLC
TASK task0(INTERVAL := T#20ms,PRIORITY := 0);
PROGRAM instance0 WITH task0 : plc_prg;
END_RESOURCE
END_CONFIGURATION
6144dfafd181ce04ff6374e450b5c333
\ No newline at end of file
/* code generated by beremiz OPC-UA extension */
#include <open62541/client_config_default.h>
#include <open62541/client_highlevel.h>
#include <open62541/plugin/log_stdout.h>
UA_Client *client;
#define DECL_VAR(ua_type, C_type, c_loc_name) \
UA_Variant *c_loc_name##_variant; \
C_type c_loc_name##_buf = 0; \
C_type *c_loc_name = &c_loc_name##_buf;
DECL_VAR(UA_Int32, uint32_t, __ID1_0)
DECL_VAR(UA_Int32, uint32_t, __ID1_1)
DECL_VAR(UA_Int32, uint32_t, __ID1_2)
DECL_VAR(UA_Int32, uint32_t, __ID1_3)
DECL_VAR(UA_Int32, uint32_t, __QD1_0)
DECL_VAR(UA_Int32, uint32_t, __QD1_1)
DECL_VAR(UA_Int32, uint32_t, __QD1_2)
DECL_VAR(UA_Int32, uint32_t, __QD1_3)
#define FREE_VARIANT(ua_type, c_loc_name) \
UA_Variant_delete(c_loc_name##_variant);
void __cleanup_1(void)
{
UA_Client_disconnect(client);
UA_Client_delete(client);
FREE_VARIANT(UA_Int32, __ID1_0)
FREE_VARIANT(UA_Int32, __ID1_1)
FREE_VARIANT(UA_Int32, __ID1_2)
FREE_VARIANT(UA_Int32, __ID1_3)
FREE_VARIANT(UA_Int32, __QD1_0)
FREE_VARIANT(UA_Int32, __QD1_1)
FREE_VARIANT(UA_Int32, __QD1_2)
FREE_VARIANT(UA_Int32, __QD1_3)
}
#define ALLOC_VARIANT(ua_type, c_loc_name) \
c_loc_name##_variant = UA_Variant_new();
int __init_1(int argc,char **argv)
{
UA_StatusCode retval;
client = UA_Client_new();
UA_ClientConfig_setDefault(UA_Client_getConfig(client));
ALLOC_VARIANT(UA_Int32, __ID1_0)
ALLOC_VARIANT(UA_Int32, __ID1_1)
ALLOC_VARIANT(UA_Int32, __ID1_2)
ALLOC_VARIANT(UA_Int32, __ID1_3)
ALLOC_VARIANT(UA_Int32, __QD1_0)
ALLOC_VARIANT(UA_Int32, __QD1_1)
ALLOC_VARIANT(UA_Int32, __QD1_2)
ALLOC_VARIANT(UA_Int32, __QD1_3)
/* Connect to server */
retval = UA_Client_connect(client, "opc.tcp://192.168.0.78:4840");
if(retval != UA_STATUSCODE_GOOD) {
UA_Client_delete(client);
return EXIT_FAILURE;
}
}
#define READ_VALUE(ua_type, ua_type_enum, c_loc_name, ua_nodeid_type, ua_nsidx, ua_node_id) \
retval = UA_Client_readValueAttribute( \
client, ua_nodeid_type(ua_nsidx, ua_node_id), c_loc_name##_variant); \
if(retval == UA_STATUSCODE_GOOD && UA_Variant_isScalar(c_loc_name##_variant) && \
c_loc_name##_variant->type == &UA_TYPES[ua_type_enum]) { \
c_loc_name##_buf = *(ua_type*)c_loc_name##_variant->data; \
}
void __retrieve_1(void)
{
UA_StatusCode retval;
READ_VALUE(UA_Int32, UA_TYPES_INT32, __ID1_0, UA_NODEID_STRING, 1, "relay0")
READ_VALUE(UA_Int32, UA_TYPES_INT32, __ID1_1, UA_NODEID_STRING, 1, "relay1")
READ_VALUE(UA_Int32, UA_TYPES_INT32, __ID1_2, UA_NODEID_STRING, 1, "relay2")
READ_VALUE(UA_Int32, UA_TYPES_INT32, __ID1_3, UA_NODEID_STRING, 1, "relay3")
READ_VALUE(UA_Int32, UA_TYPES_INT32, __QD1_0, UA_NODEID_STRING, 1, "relay0")
READ_VALUE(UA_Int32, UA_TYPES_INT32, __QD1_1, UA_NODEID_STRING, 1, "relay1")
READ_VALUE(UA_Int32, UA_TYPES_INT32, __QD1_2, UA_NODEID_STRING, 1, "relay2")
READ_VALUE(UA_Int32, UA_TYPES_INT32, __QD1_3, UA_NODEID_STRING, 1, "relay3")
}
#define WRITE_VALUE(ua_type, ua_type_enum, c_loc_name, ua_nodeid_type, ua_nsidx, ua_node_id) \
UA_Variant_setScalarCopy(c_loc_name##_variant, (ua_type*)c_loc_name, &UA_TYPES[ua_type_enum]); \
UA_Client_writeValueAttribute(client, ua_nodeid_type(ua_nsidx, ua_node_id), c_loc_name##_variant);
void __publish_1(void)
{
WRITE_VALUE(UA_Int32, UA_TYPES_INT32, __ID1_0, UA_NODEID_STRING, 1, "relay0")
WRITE_VALUE(UA_Int32, UA_TYPES_INT32, __ID1_1, UA_NODEID_STRING, 1, "relay1")
WRITE_VALUE(UA_Int32, UA_TYPES_INT32, __ID1_2, UA_NODEID_STRING, 1, "relay2")
WRITE_VALUE(UA_Int32, UA_TYPES_INT32, __ID1_3, UA_NODEID_STRING, 1, "relay3")
WRITE_VALUE(UA_Int32, UA_TYPES_INT32, __QD1_0, UA_NODEID_STRING, 1, "relay0")
WRITE_VALUE(UA_Int32, UA_TYPES_INT32, __QD1_1, UA_NODEID_STRING, 1, "relay1")
WRITE_VALUE(UA_Int32, UA_TYPES_INT32, __QD1_2, UA_NODEID_STRING, 1, "relay2")
WRITE_VALUE(UA_Int32, UA_TYPES_INT32, __QD1_3, UA_NODEID_STRING, 1, "relay3")
}
TYPE
LOGLEVEL : (CRITICAL, WARNING, INFO, DEBUG) := INFO;
END_TYPE
FUNCTION_BLOCK LOGGER
VAR_INPUT
TRIG : BOOL;
MSG : STRING;
LEVEL : LOGLEVEL := INFO;
END_VAR
VAR
TRIG0 : BOOL;
END_VAR
IF TRIG AND NOT TRIG0 THEN
{{
LogMessage(GetFbVar(LEVEL),(char*)GetFbVar(MSG, .body),GetFbVar(MSG, .len));
}}
END_IF;
TRIG0:=TRIG;
END_FUNCTION_BLOCK
FUNCTION_BLOCK python_eval
VAR_INPUT
TRIG : BOOL;
CODE : STRING;
END_VAR
VAR_OUTPUT
ACK : BOOL;
RESULT : STRING;
END_VAR
VAR
STATE : DWORD;
BUFFER : STRING;
PREBUFFER : STRING;
TRIGM1 : BOOL;
TRIGGED : BOOL;
END_VAR
{extern void __PythonEvalFB(int, PYTHON_EVAL*);__PythonEvalFB(0, data__);}
END_FUNCTION_BLOCK
FUNCTION_BLOCK python_poll
VAR_INPUT
TRIG : BOOL;
CODE : STRING;
END_VAR
VAR_OUTPUT
ACK : BOOL;
RESULT : STRING;
END_VAR
VAR
STATE : DWORD;
BUFFER : STRING;
PREBUFFER : STRING;
TRIGM1 : BOOL;
TRIGGED : BOOL;
END_VAR
{extern void __PythonEvalFB(int, PYTHON_EVAL*);__PythonEvalFB(1,(PYTHON_EVAL*)(void*)data__);}
END_FUNCTION_BLOCK
FUNCTION_BLOCK python_gear
VAR_INPUT
N : UINT;
TRIG : BOOL;
CODE : STRING;
END_VAR
VAR_OUTPUT
ACK : BOOL;
RESULT : STRING;
END_VAR
VAR
py_eval : python_eval;
COUNTER : UINT;
_TMP_ADD10_OUT : UINT;
_TMP_EQ13_OUT : BOOL;
_TMP_SEL15_OUT : UINT;
_TMP_AND7_OUT : BOOL;
END_VAR
_TMP_ADD10_OUT := ADD(COUNTER, 1);
_TMP_EQ13_OUT := EQ(N, _TMP_ADD10_OUT);
_TMP_SEL15_OUT := SEL(_TMP_EQ13_OUT, _TMP_ADD10_OUT, 0);
COUNTER := _TMP_SEL15_OUT;
_TMP_AND7_OUT := AND(_TMP_EQ13_OUT, TRIG);
py_eval(TRIG := _TMP_AND7_OUT, CODE := CODE);
ACK := py_eval.ACK;
RESULT := py_eval.RESULT;
END_FUNCTION_BLOCK
FUNCTION_BLOCK CounterST
VAR_INPUT
Reset : BOOL;
END_VAR
VAR_OUTPUT
Out1 : INT;
Out0 : INT;
END_VAR
VAR
Cnt1 : INT;
Cnt0 : INT;
END_VAR
VAR_EXTERNAL
ResetCounterValue : INT;
Relay0 : DINT;
Relay1 : DINT;
Relay2 : DINT;
Relay3 : DINT;
END_VAR
IF Reset THEN
Cnt0 := ResetCounterValue;
Cnt1 := ResetCounterValue;
Reset := False;
ELSE
Cnt0 := Cnt0 + 1;
Cnt1 := Cnt1 + 1;
IF Cnt1 = 50 THEN
Relay0 := 1;
Relay1 := 1;
Relay2 := 1;
Relay3 := 1;
END_IF;
IF Cnt1 = 100 THEN
Relay0 := 0;
Relay1 := 0;
Relay2 := 0;
Relay3 := 0;
Cnt1 := 0;
END_IF;
END_IF;
Out1 := Cnt1;
Out0 := Cnt0;
END_FUNCTION_BLOCK
PROGRAM plc_prg
VAR_INPUT
Reset : BOOL;
END_VAR
VAR_OUTPUT
Cnt0 : INT;
Cnt1 : INT;
END_VAR
VAR
CounterST0 : CounterST;
END_VAR
CounterST0(Reset := Reset);
Cnt1 := CounterST0.Out1;
Cnt0 := CounterST0.Out0;
END_PROGRAM
CONFIGURATION config
VAR_GLOBAL
ResetCounterValue : INT := 0;
Relay0 AT %QD1.0 : DINT := 0;
Relay1 AT %QD1.1 : DINT := 0;
Relay2 AT %QD1.2 : DINT := 0;
Relay3 AT %QD1.3 : DINT := 0;
END_VAR
RESOURCE resource1 ON PLC
TASK task0(INTERVAL := T#20ms,PRIORITY := 0);
PROGRAM instance0 WITH task0 : plc_prg;
END_RESOURCE
END_CONFIGURATION
/*
* DEBUGGER code
*
* On "publish", when buffer is free, debugger stores arbitrary variables
* content into, and mark this buffer as filled
*
*
* Buffer content is read asynchronously, (from non real time part),
* and then buffer marked free again.
*
*
* */
#ifdef TARGET_DEBUG_AND_RETAIN_DISABLE
void __init_debug (void){}
void __cleanup_debug (void){}
void __retrieve_debug(void){}
void __publish_debug (void){}
#else
#include "iec_types_all.h"
#include "POUS.h"
/*for memcpy*/
#include <string.h>
#include <stdio.h>
#ifndef TARGET_ONLINE_DEBUG_DISABLE
#define BUFFER_SIZE 52
/* Atomically accessed variable for buffer state */
#define BUFFER_FREE 0
#define BUFFER_BUSY 1
static long buffer_state = BUFFER_FREE;
/* The buffer itself */
char debug_buffer[BUFFER_SIZE];
/* Buffer's cursor*/
static char* buffer_cursor = debug_buffer;
#endif
static unsigned int retain_offset = 0;
/***
* Declare programs
**/
extern PLC_PRG RESOURCE1__INSTANCE0;
/***
* Declare global variables from resources and conf
**/
extern __IEC_INT_t CONFIG__RESETCOUNTERVALUE;
extern __IEC_DINT_p CONFIG__RELAY0;
extern __IEC_DINT_p CONFIG__RELAY1;
extern __IEC_DINT_p CONFIG__RELAY2;
extern __IEC_DINT_p CONFIG__RELAY3;
extern PLC_PRG RESOURCE1__INSTANCE0;
typedef const struct {
void *ptr;
__IEC_types_enum type;
} dbgvardsc_t;
static dbgvardsc_t dbgvardsc[] = {
{&(CONFIG__RESETCOUNTERVALUE), INT_ENUM},
{&(CONFIG__RELAY0), DINT_O_ENUM},
{&(CONFIG__RELAY1), DINT_O_ENUM},
{&(CONFIG__RELAY2), DINT_O_ENUM},
{&(CONFIG__RELAY3), DINT_O_ENUM},
{&(RESOURCE1__INSTANCE0.RESET), BOOL_ENUM},
{&(RESOURCE1__INSTANCE0.CNT0), INT_ENUM},
{&(RESOURCE1__INSTANCE0.CNT1), INT_ENUM},
{&(RESOURCE1__INSTANCE0.COUNTERST0.EN), BOOL_ENUM},
{&(RESOURCE1__INSTANCE0.COUNTERST0.ENO), BOOL_ENUM},
{&(RESOURCE1__INSTANCE0.COUNTERST0.RESET), BOOL_ENUM},
{&(RESOURCE1__INSTANCE0.COUNTERST0.OUT1), INT_ENUM},
{&(RESOURCE1__INSTANCE0.COUNTERST0.OUT0), INT_ENUM},
{&(RESOURCE1__INSTANCE0.COUNTERST0.CNT1), INT_ENUM},
{&(RESOURCE1__INSTANCE0.COUNTERST0.CNT0), INT_ENUM},
{&(RESOURCE1__INSTANCE0.COUNTERST0.RESETCOUNTERVALUE), INT_P_ENUM},
{&(RESOURCE1__INSTANCE0.COUNTERST0.RELAY0), DINT_P_ENUM},
{&(RESOURCE1__INSTANCE0.COUNTERST0.RELAY1), DINT_P_ENUM},
{&(RESOURCE1__INSTANCE0.COUNTERST0.RELAY2), DINT_P_ENUM},
{&(RESOURCE1__INSTANCE0.COUNTERST0.RELAY3), DINT_P_ENUM}
};
typedef void(*__for_each_variable_do_fp)(dbgvardsc_t*);
void __for_each_variable_do(__for_each_variable_do_fp fp)
{
unsigned int i;
for(i = 0; i < sizeof(dbgvardsc)/sizeof(dbgvardsc_t); i++){
dbgvardsc_t *dsc = &dbgvardsc[i];
if(dsc->type != UNKNOWN_ENUM)
(*fp)(dsc);
}
}
#define __Unpack_desc_type dbgvardsc_t
#define __Unpack_case_t(TYPENAME) \
case TYPENAME##_ENUM :\
*flags = ((__IEC_##TYPENAME##_t *)varp)->flags;\
forced_value_p = *real_value_p = &((__IEC_##TYPENAME##_t *)varp)->value;\
break;
#define __Unpack_case_p(TYPENAME)\
case TYPENAME##_O_ENUM :\
*flags = __IEC_OUTPUT_FLAG;\
case TYPENAME##_P_ENUM :\
*flags |= ((__IEC_##TYPENAME##_p *)varp)->flags;\
*real_value_p = ((__IEC_##TYPENAME##_p *)varp)->value;\
forced_value_p = &((__IEC_##TYPENAME##_p *)varp)->fvalue;\
break;
#define __Is_a_string(dsc) (dsc->type == STRING_ENUM) ||\
(dsc->type == STRING_P_ENUM) ||\
(dsc->type == STRING_O_ENUM)
static void* UnpackVar(__Unpack_desc_type *dsc, void **real_value_p, char *flags)
{
void *varp = dsc->ptr;
void *forced_value_p = NULL;
*flags = 0;
/* find data to copy*/
switch(dsc->type){
__ANY(__Unpack_case_t)
__ANY(__Unpack_case_p)
default:
break;
}
if (*flags & __IEC_FORCE_FLAG)
return forced_value_p;
return *real_value_p;
}
void Remind(unsigned int offset, unsigned int count, void * p);
void RemindIterator(dbgvardsc_t *dsc)
{
void *real_value_p = NULL;
char flags = 0;
UnpackVar(dsc, &real_value_p, &flags);
if(flags & __IEC_RETAIN_FLAG){
USINT size = __get_type_enum_size(dsc->type);
/* compute next cursor positon*/
unsigned int next_retain_offset = retain_offset + size;
/* if buffer not full */
Remind(retain_offset, size, real_value_p);
/* increment cursor according size*/
retain_offset = next_retain_offset;
}
}
extern int CheckRetainBuffer(void);
extern void InitRetain(void);
void __init_debug(void)
{
/* init local static vars */
#ifndef TARGET_ONLINE_DEBUG_DISABLE
buffer_cursor = debug_buffer;
buffer_state = BUFFER_FREE;
#endif
retain_offset = 0;
InitRetain();
/* Iterate over all variables to fill debug buffer */
if(CheckRetainBuffer()){
__for_each_variable_do(RemindIterator);
}else{
char mstr[] = "RETAIN memory invalid - defaults used";
LogMessage(LOG_WARNING, mstr, sizeof(mstr));
}
retain_offset = 0;
}
extern void InitiateDebugTransfer(void);
extern void CleanupRetain(void);
extern unsigned long __tick;
void __cleanup_debug(void)
{
#ifndef TARGET_ONLINE_DEBUG_DISABLE
buffer_cursor = debug_buffer;
InitiateDebugTransfer();
#endif
CleanupRetain();
}
void __retrieve_debug(void)
{
}
void Retain(unsigned int offset, unsigned int count, void * p);
static inline void BufferIterator(dbgvardsc_t *dsc, int do_debug)
{
void *real_value_p = NULL;
void *visible_value_p = NULL;
char flags = 0;
visible_value_p = UnpackVar(dsc, &real_value_p, &flags);
if(flags & ( __IEC_DEBUG_FLAG | __IEC_RETAIN_FLAG)){
USINT size = __get_type_enum_size(dsc->type);
#ifndef TARGET_ONLINE_DEBUG_DISABLE
if(flags & __IEC_DEBUG_FLAG){
/* copy visible variable to buffer */;
if(do_debug){
/* compute next cursor positon.
No need to check overflow, as BUFFER_SIZE
is computed large enough */
if(__Is_a_string(dsc)){
/* optimization for strings */
size = ((STRING*)visible_value_p)->len + 1;
}
char* next_cursor = buffer_cursor + size;
/* copy data to the buffer */
memcpy(buffer_cursor, visible_value_p, size);
/* increment cursor according size*/
buffer_cursor = next_cursor;
}
/* re-force real value of outputs (M and Q)*/
if((flags & __IEC_FORCE_FLAG) && (flags & __IEC_OUTPUT_FLAG)){
memcpy(real_value_p, visible_value_p, size);
}
}
#endif
if(flags & __IEC_RETAIN_FLAG){
/* compute next cursor positon*/
unsigned int next_retain_offset = retain_offset + size;
/* if buffer not full */
Retain(retain_offset, size, real_value_p);
/* increment cursor according size*/
retain_offset = next_retain_offset;
}
}
}
void DebugIterator(dbgvardsc_t *dsc){
BufferIterator(dsc, 1);
}
void RetainIterator(dbgvardsc_t *dsc){
BufferIterator(dsc, 0);
}
unsigned int retain_size = 0;
/* GetRetainSizeIterator */
void GetRetainSizeIterator(dbgvardsc_t *dsc)
{
void *real_value_p = NULL;
char flags = 0;
UnpackVar(dsc, &real_value_p, &flags);
if(flags & __IEC_RETAIN_FLAG){
USINT size = __get_type_enum_size(dsc->type);
/* Calc retain buffer size */
retain_size += size;
}
}
/* Return size of all retain variables */
unsigned int GetRetainSize(void)
{
__for_each_variable_do(GetRetainSizeIterator);
return retain_size;
}
extern void PLC_GetTime(IEC_TIME*);
extern int TryEnterDebugSection(void);
extern long AtomicCompareExchange(long*, long, long);
extern long long AtomicCompareExchange64(long long* , long long , long long);
extern void LeaveDebugSection(void);
extern void ValidateRetainBuffer(void);
extern void InValidateRetainBuffer(void);
void __publish_debug(void)
{
retain_offset = 0;
InValidateRetainBuffer();
#ifndef TARGET_ONLINE_DEBUG_DISABLE
/* Check there is no running debugger re-configuration */
if(TryEnterDebugSection()){
/* Lock buffer */
long latest_state = AtomicCompareExchange(
&buffer_state,
BUFFER_FREE,
BUFFER_BUSY);
/* If buffer was free */
if(latest_state == BUFFER_FREE)
{
/* Reset buffer cursor */
buffer_cursor = debug_buffer;
/* Iterate over all variables to fill debug buffer */
__for_each_variable_do(DebugIterator);
/* Leave debug section,
* Trigger asynchronous transmission
* (returns immediately) */
InitiateDebugTransfer(); /* size */
}else{
/* when not debugging, do only retain */
__for_each_variable_do(RetainIterator);
}
LeaveDebugSection();
}else
#endif
{
/* when not debugging, do only retain */
__for_each_variable_do(RetainIterator);
}
ValidateRetainBuffer();
}
#ifndef TARGET_ONLINE_DEBUG_DISABLE
#define __RegisterDebugVariable_case_t(TYPENAME) \
case TYPENAME##_ENUM :\
((__IEC_##TYPENAME##_t *)varp)->flags |= flags;\
if(force)\
((__IEC_##TYPENAME##_t *)varp)->value = *((TYPENAME *)force);\
break;
#define __RegisterDebugVariable_case_p(TYPENAME)\
case TYPENAME##_P_ENUM :\
((__IEC_##TYPENAME##_p *)varp)->flags |= flags;\
if(force)\
((__IEC_##TYPENAME##_p *)varp)->fvalue = *((TYPENAME *)force);\
break;\
case TYPENAME##_O_ENUM :\
((__IEC_##TYPENAME##_p *)varp)->flags |= flags;\
if(force){\
((__IEC_##TYPENAME##_p *)varp)->fvalue = *((TYPENAME *)force);\
*(((__IEC_##TYPENAME##_p *)varp)->value) = *((TYPENAME *)force);\
}\
break;
void RegisterDebugVariable(unsigned int idx, void* force)
{
if(idx < sizeof(dbgvardsc)/sizeof(dbgvardsc_t)){
unsigned char flags = force ?
__IEC_DEBUG_FLAG | __IEC_FORCE_FLAG :
__IEC_DEBUG_FLAG;
dbgvardsc_t *dsc = &dbgvardsc[idx];
void *varp = dsc->ptr;
switch(dsc->type){
__ANY(__RegisterDebugVariable_case_t)
__ANY(__RegisterDebugVariable_case_p)
default:
break;
}
}
}
#define __ResetDebugVariablesIterator_case_t(TYPENAME) \
case TYPENAME##_ENUM :\
((__IEC_##TYPENAME##_t *)varp)->flags &= ~(__IEC_DEBUG_FLAG|__IEC_FORCE_FLAG);\
break;
#define __ResetDebugVariablesIterator_case_p(TYPENAME)\
case TYPENAME##_P_ENUM :\
case TYPENAME##_O_ENUM :\
((__IEC_##TYPENAME##_p *)varp)->flags &= ~(__IEC_DEBUG_FLAG|__IEC_FORCE_FLAG);\
break;
void ResetDebugVariablesIterator(dbgvardsc_t *dsc)
{
/* force debug flag to 0*/
void *varp = dsc->ptr;
switch(dsc->type){
__ANY(__ResetDebugVariablesIterator_case_t)
__ANY(__ResetDebugVariablesIterator_case_p)
default:
break;
}
}
void ResetDebugVariables(void)
{
__for_each_variable_do(ResetDebugVariablesIterator);
}
void FreeDebugData(void)
{
/* atomically mark buffer as free */
AtomicCompareExchange(
&buffer_state,
BUFFER_BUSY,
BUFFER_FREE);
}
int WaitDebugData(unsigned long *tick);
/* Wait until debug data ready and return pointer to it */
int GetDebugData(unsigned long *tick, unsigned long *size, void **buffer){
int wait_error = WaitDebugData(tick);
if(!wait_error){
*size = buffer_cursor - debug_buffer;
*buffer = debug_buffer;
}
return wait_error;
}
#endif
#endif
/**
* Head of code common to all C targets
**/
#include "beremiz.h"
#include <string.h>
/*
* Prototypes of functions provided by generated C softPLC
**/
void config_run__(unsigned long tick);
void config_init__(void);
/*
* Prototypes of functions provided by generated target C code
* */
long long AtomicCompareExchange64(long long*, long long, long long);
void __init_debug(void);
void __cleanup_debug(void);
/*void __retrieve_debug(void);*/
void __publish_debug(void);
/*
* Variables used by generated C softPLC and plugins
**/
IEC_TIME __CURRENT_TIME;
IEC_BOOL __DEBUG = 0;
unsigned long __tick = 0;
char *PLC_ID = NULL;
/*
* Variable generated by C softPLC and plugins
**/
extern unsigned long greatest_tick_count__;
/* Help to quit cleanly when init fail at a certain level */
static int init_level = 0;
/*
* Prototypes of functions exported by plugins
**/
int __init_py_ext(int argc,char **argv);
void __cleanup_py_ext(void);
void __retrieve_py_ext(void);
void __publish_py_ext(void);
int __init_1(int argc,char **argv);
void __cleanup_1(void);
void __retrieve_1(void);
void __publish_1(void);
/*
* Retrieve input variables, run PLC and publish output variables
**/
void __run(void)
{
__tick++;
if (greatest_tick_count__)
__tick %= greatest_tick_count__;
__retrieve_py_ext();
__retrieve_1();
/*__retrieve_debug();*/
config_run__(__tick);
__publish_debug();
__publish_1();
__publish_py_ext();
}
/*
* Initialize variables according to PLC's default values,
* and then init plugins with that values
**/
int __init(int argc,char **argv)
{
int res = 0;
init_level = 0;
/* Effective tick time with 1ms default value */
if(!common_ticktime__)
common_ticktime__ = 1000000;
config_init__();
__init_debug();
init_level=1; if((res = __init_py_ext(argc,argv))){return res;}
init_level=2; if((res = __init_1(argc,argv))){return res;}
return res;
}
/*
* Calls plugin cleanup proc.
**/
void __cleanup(void)
{
if(init_level >= 2) __cleanup_1();
if(init_level >= 1) __cleanup_py_ext();
__cleanup_debug();
}
void PLC_GetTime(IEC_TIME *CURRENT_TIME);
void PLC_SetTimer(unsigned long long next, unsigned long long period);
/**
* Linux specific code
**/
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <signal.h>
#include <stdlib.h>
#include <pthread.h>
#include <locale.h>
#include <semaphore.h>
static sem_t Run_PLC;
long AtomicCompareExchange(long* atomicvar,long compared, long exchange)
{
return __sync_val_compare_and_swap(atomicvar, compared, exchange);
}
long long AtomicCompareExchange64(long long* atomicvar, long long compared, long long exchange)
{
return __sync_val_compare_and_swap(atomicvar, compared, exchange);
}
void PLC_GetTime(IEC_TIME *CURRENT_TIME)
{
struct timespec tmp;
clock_gettime(CLOCK_REALTIME, &tmp);
CURRENT_TIME->tv_sec = tmp.tv_sec;
CURRENT_TIME->tv_nsec = tmp.tv_nsec;
}
void PLC_timer_notify(sigval_t val)
{
PLC_GetTime(&__CURRENT_TIME);
sem_post(&Run_PLC);
}
timer_t PLC_timer;
void PLC_SetTimer(unsigned long long next, unsigned long long period)
{
struct itimerspec timerValues;
/*
printf("SetTimer(%lld,%lld)\n",next, period);
*/
memset (&timerValues, 0, sizeof (struct itimerspec));
{
#ifdef __lldiv_t_defined
lldiv_t nxt_div = lldiv(next, 1000000000);
lldiv_t period_div = lldiv(period, 1000000000);
timerValues.it_value.tv_sec = nxt_div.quot;
timerValues.it_value.tv_nsec = nxt_div.rem;
timerValues.it_interval.tv_sec = period_div.quot;
timerValues.it_interval.tv_nsec = period_div.rem;
#else
timerValues.it_value.tv_sec = next / 1000000000;
timerValues.it_value.tv_nsec = next % 1000000000;
timerValues.it_interval.tv_sec = period / 1000000000;
timerValues.it_interval.tv_nsec = period % 1000000000;
#endif
}
timer_settime (PLC_timer, 0, &timerValues, NULL);
}
//
void catch_signal(int sig)
{
// signal(SIGTERM, catch_signal);
signal(SIGINT, catch_signal);
printf("Got Signal %d\n",sig);
exit(0);
}
static unsigned long __debug_tick;
pthread_t PLC_thread;
static pthread_mutex_t python_wait_mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_mutex_t python_mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_mutex_t debug_wait_mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_mutex_t debug_mutex = PTHREAD_MUTEX_INITIALIZER;
int PLC_shutdown = 0;
int ForceSaveRetainReq(void) {
return PLC_shutdown;
}
void PLC_thread_proc(void *arg)
{
while (!PLC_shutdown) {
sem_wait(&Run_PLC);
__run();
}
pthread_exit(0);
}
#define maxval(a,b) ((a>b)?a:b)
int startPLC(int argc,char **argv)
{
struct sigevent sigev;
setlocale(LC_NUMERIC, "C");
PLC_shutdown = 0;
sem_init(&Run_PLC, 0, 0);
pthread_create(&PLC_thread, NULL, (void*) &PLC_thread_proc, NULL);
memset (&sigev, 0, sizeof (struct sigevent));
sigev.sigev_value.sival_int = 0;
sigev.sigev_notify = SIGEV_THREAD;
sigev.sigev_notify_attributes = NULL;
sigev.sigev_notify_function = PLC_timer_notify;
pthread_mutex_init(&debug_wait_mutex, NULL);
pthread_mutex_init(&debug_mutex, NULL);
pthread_mutex_init(&python_wait_mutex, NULL);
pthread_mutex_init(&python_mutex, NULL);
pthread_mutex_lock(&debug_wait_mutex);
pthread_mutex_lock(&python_wait_mutex);
timer_create (CLOCK_MONOTONIC, &sigev, &PLC_timer);
if( __init(argc,argv) == 0 ){
PLC_SetTimer(common_ticktime__,common_ticktime__);
/* install signal handler for manual break */
signal(SIGINT, catch_signal);
}else{
return 1;
}
return 0;
}
int TryEnterDebugSection(void)
{
if (pthread_mutex_trylock(&debug_mutex) == 0){
/* Only enter if debug active */
if(__DEBUG){
return 1;
}
pthread_mutex_unlock(&debug_mutex);
}
return 0;
}
void LeaveDebugSection(void)
{
pthread_mutex_unlock(&debug_mutex);
}
int stopPLC()
{
/* Stop the PLC */
PLC_shutdown = 1;
sem_post(&Run_PLC);
PLC_SetTimer(0,0);
pthread_join(PLC_thread, NULL);
sem_destroy(&Run_PLC);
timer_delete (PLC_timer);
__cleanup();
pthread_mutex_destroy(&debug_wait_mutex);
pthread_mutex_destroy(&debug_mutex);
pthread_mutex_destroy(&python_wait_mutex);
pthread_mutex_destroy(&python_mutex);
return 0;
}
extern unsigned long __tick;
int WaitDebugData(unsigned long *tick)
{
int res;
if (PLC_shutdown) return 1;
/* Wait signal from PLC thread */
res = pthread_mutex_lock(&debug_wait_mutex);
*tick = __debug_tick;
return res;
}
/* Called by PLC thread when debug_publish finished
* This is supposed to unlock debugger thread in WaitDebugData*/
void InitiateDebugTransfer()
{
/* remember tick */
__debug_tick = __tick;
/* signal debugger thread it can read data */
pthread_mutex_unlock(&debug_wait_mutex);
}
int suspendDebug(int disable)
{
/* Prevent PLC to enter debug code */
pthread_mutex_lock(&debug_mutex);
/*__DEBUG is protected by this mutex */
__DEBUG = !disable;
if (disable)
pthread_mutex_unlock(&debug_mutex);
return 0;
}
void resumeDebug(void)
{
__DEBUG = 1;
/* Let PLC enter debug code */
pthread_mutex_unlock(&debug_mutex);
}
/* from plc_python.c */
int WaitPythonCommands(void)
{
/* Wait signal from PLC thread */
return pthread_mutex_lock(&python_wait_mutex);
}
/* Called by PLC thread on each new python command*/
void UnBlockPythonCommands(void)
{
/* signal python thread it can read data */
pthread_mutex_unlock(&python_wait_mutex);
}
int TryLockPython(void)
{
return pthread_mutex_trylock(&python_mutex) == 0;
}
void UnLockPython(void)
{
pthread_mutex_unlock(&python_mutex);
}
void LockPython(void)
{
pthread_mutex_lock(&python_mutex);
}
struct RT_to_nRT_signal_s {
pthread_cond_t WakeCond;
pthread_mutex_t WakeCondLock;
};
typedef struct RT_to_nRT_signal_s RT_to_nRT_signal_t;
#define _LogAndReturnNull(text) \
{\
char mstr[256] = text " for ";\
strncat(mstr, name, 255);\
LogMessage(LOG_CRITICAL, mstr, strlen(mstr));\
return NULL;\
}
void *create_RT_to_nRT_signal(char* name){
RT_to_nRT_signal_t *sig = (RT_to_nRT_signal_t*)malloc(sizeof(RT_to_nRT_signal_t));
if(!sig)
_LogAndReturnNull("Failed allocating memory for RT_to_nRT signal");
pthread_cond_init(&sig->WakeCond, NULL);
pthread_mutex_init(&sig->WakeCondLock, NULL);
return (void*)sig;
}
void delete_RT_to_nRT_signal(void* handle){
RT_to_nRT_signal_t *sig = (RT_to_nRT_signal_t*)handle;
pthread_cond_destroy(&sig->WakeCond);
pthread_mutex_destroy(&sig->WakeCondLock);
free(sig);
}
int wait_RT_to_nRT_signal(void* handle){
int ret;
RT_to_nRT_signal_t *sig = (RT_to_nRT_signal_t*)handle;
pthread_mutex_lock(&sig->WakeCondLock);
ret = pthread_cond_wait(&sig->WakeCond, &sig->WakeCondLock);
pthread_mutex_unlock(&sig->WakeCondLock);
return ret;
}
int unblock_RT_to_nRT_signal(void* handle){
RT_to_nRT_signal_t *sig = (RT_to_nRT_signal_t*)handle;
return pthread_cond_signal(&sig->WakeCond);
}
void nRT_reschedule(void){
sched_yield();
}
/*
This file is part of Beremiz, a Integrated Development Environment for
programming IEC 61131-3 automates supporting plcopen standard and CanFestival.
See COPYING.runtime
Copyright (C) 2018: Sergey Surkov <surkov.sv@summatechnology.ru>
Copyright (C) 2018: Andrey Skvortsov <andrej.skvortzov@gmail.com>
*/
#ifndef HAVE_RETAIN
#include <stdio.h>
#include <stdint.h>
#include <unistd.h>
#include "iec_types.h"
int GetRetainSize(void);
/* Retain buffer. */
FILE *retain_buffer;
const char rb_file[] = "retain_buffer_file";
const char rb_file_bckp[] = "retain_buffer_file.bak";
/* Retain header struct. */
struct retain_info_t {
uint32_t retain_size;
uint32_t hash_size;
uint8_t* hash;
uint32_t header_offset;
uint32_t header_crc;
};
/* Init retain info structure. */
struct retain_info_t retain_info;
/* CRC lookup table and initial state. */
static const uint32_t crc32_table[256] = {
0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3,
0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91,
0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7,
0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5,
0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B,
0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59,
0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F,
0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D,
0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433,
0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01,
0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457,
0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65,
0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB,
0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9,
0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F,
0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD,
0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683,
0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1,
0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7,
0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5,
0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B,
0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79,
0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F,
0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D,
0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713,
0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21,
0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777,
0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45,
0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB,
0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9,
0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF,
0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D,
};
uint32_t retain_crc;
/* Calculate CRC32 for len bytes from pointer buf with init starting value. */
uint32_t GenerateCRC32Sum(const void* buf, unsigned int len, uint32_t init)
{
uint32_t crc = ~init;
unsigned char* current = (unsigned char*) buf;
while (len--)
crc = crc32_table[(crc ^ *current++) & 0xFF] ^ (crc >> 8);
return ~crc;
}
/* Calc CRC32 for retain file byte by byte. */
int CheckFileCRC(FILE* file_buffer)
{
/* Set the magic constant for one-pass CRC calc according to ZIP CRC32. */
const uint32_t magic_number = 0x2144df1c;
/* CRC initial state. */
uint32_t calc_crc32 = 0;
char data_block = 0;
while(!feof(file_buffer)){
if (fread(&data_block, sizeof(data_block), 1, file_buffer))
calc_crc32 = GenerateCRC32Sum(&data_block, sizeof(char), calc_crc32);
}
/* Compare crc result with a magic number. */
return (calc_crc32 == magic_number) ? 1 : 0;
}
/* Compare current hash with hash from file byte by byte. */
int CheckFilehash(void)
{
int k,ret;
int offset = sizeof(retain_info.retain_size);
rewind(retain_buffer);
fseek(retain_buffer, offset , SEEK_SET);
uint32_t size;
ret = fread(&size, sizeof(size), 1, retain_buffer);
if (size != retain_info.hash_size)
return 0;
for(k = 0; k < retain_info.hash_size; k++){
uint8_t file_digit;
ret = fread(&file_digit, sizeof(char), 1, retain_buffer);
if (file_digit != *(retain_info.hash+k))
return 0;
}
return 1;
}
void InitRetain(void)
{
int i;
/* Get retain size in bytes */
retain_info.retain_size = GetRetainSize();
/* Hash stored in retain file as array of char in hex digits
(that's why we divide strlen in two). */
retain_info.hash_size = PLC_ID ? strlen(PLC_ID)/2 : 0;
//retain_info.hash_size = 0;
retain_info.hash = malloc(retain_info.hash_size);
/* Transform hash string into byte sequence. */
for (i = 0; i < retain_info.hash_size; i++) {
int byte = 0;
sscanf((PLC_ID + i*2), "%02X", &byte);
retain_info.hash[i] = byte;
}
/* Calc header offset. */
retain_info.header_offset = sizeof(retain_info.retain_size) + \
sizeof(retain_info.hash_size) + \
retain_info.hash_size;
/* Set header CRC initial state. */
retain_info.header_crc = 0;
/* Calc crc for header. */
retain_info.header_crc = GenerateCRC32Sum(
&retain_info.retain_size,
sizeof(retain_info.retain_size),
retain_info.header_crc);
retain_info.header_crc = GenerateCRC32Sum(
&retain_info.hash_size,
sizeof(retain_info.hash_size),
retain_info.header_crc);
retain_info.header_crc = GenerateCRC32Sum(
retain_info.hash,
retain_info.hash_size,
retain_info.header_crc);
}
void CleanupRetain(void)
{
/* Free hash memory. */
free(retain_info.hash);
}
int CheckRetainFile(const char * file)
{
retain_buffer = fopen(file, "rb");
if (retain_buffer) {
/* Check CRC32 and hash. */
if (CheckFileCRC(retain_buffer))
if (CheckFilehash())
return 1;
fclose(retain_buffer);
retain_buffer = NULL;
}
return 0;
}
int CheckRetainBuffer(void)
{
retain_buffer = NULL;
if (!retain_info.retain_size)
return 1;
/* Check latest retain file. */
if (CheckRetainFile(rb_file))
return 1;
/* Check if we have backup. */
if (CheckRetainFile(rb_file_bckp))
return 1;
/* We don't have any valid retain buffer - nothing to remind. */
return 0;
}
#ifndef FILE_RETAIN_SAVE_PERIOD_S
#define FILE_RETAIN_SAVE_PERIOD_S 1.0
#endif
static double CalcDiffSeconds(IEC_TIME* t1, IEC_TIME *t2)
{
IEC_TIME dt ={
t1->tv_sec - t2->tv_sec,
t1->tv_nsec - t2->tv_nsec
};
if ((dt.tv_nsec < -1000000000) || ((dt.tv_sec > 0) && (dt.tv_nsec < 0))){
dt.tv_sec--;
dt.tv_nsec += 1000000000;
}
if ((dt.tv_nsec > +1000000000) || ((dt.tv_sec < 0) && (dt.tv_nsec > 0))){
dt.tv_sec++;
dt.tv_nsec -= 1000000000;
}
return dt.tv_sec + 1e-9*dt.tv_nsec;
}
int RetainSaveNeeded(void)
{
int ret = 0;
static IEC_TIME last_save;
IEC_TIME now;
double diff_s;
/* no retain */
if (!retain_info.retain_size)
return 0;
/* periodic retain flush to avoid high I/O load */
PLC_GetTime(&now);
diff_s = CalcDiffSeconds(&now, &last_save);
if ((diff_s > FILE_RETAIN_SAVE_PERIOD_S) || ForceSaveRetainReq()) {
ret = 1;
last_save = now;
}
return ret;
}
void ValidateRetainBuffer(void)
{
if (!retain_buffer)
return;
/* Add retain data CRC to the end of buffer file. */
fseek(retain_buffer, 0, SEEK_END);
fwrite(&retain_crc, sizeof(uint32_t), 1, retain_buffer);
/* Sync file buffer and close file. */
#ifdef __WIN32
fflush(retain_buffer);
#else
fsync(fileno(retain_buffer));
#endif
fclose(retain_buffer);
retain_buffer = NULL;
}
void InValidateRetainBuffer(void)
{
if (!RetainSaveNeeded())
return;
/* Rename old retain file into *.bak if it exists. */
rename(rb_file, rb_file_bckp);
/* Set file CRC initial value. */
retain_crc = retain_info.header_crc;
/* Create new retain file. */
retain_buffer = fopen(rb_file, "wb+");
if (!retain_buffer) {
fprintf(stderr, "Failed to create retain file : %s\n", rb_file);
return;
}
/* Write header to the new file. */
fwrite(&retain_info.retain_size,
sizeof(retain_info.retain_size), 1, retain_buffer);
fwrite(&retain_info.hash_size,
sizeof(retain_info.hash_size), 1, retain_buffer);
fwrite(retain_info.hash ,
sizeof(char), retain_info.hash_size, retain_buffer);
}
void Retain(unsigned int offset, unsigned int count, void *p)
{
if (!retain_buffer)
return;
/* Generate CRC 32 for each data block. */
retain_crc = GenerateCRC32Sum(p, count, retain_crc);
/* Save current var in file. */
fseek(retain_buffer, retain_info.header_offset+offset, SEEK_SET);
fwrite(p, count, 1, retain_buffer);
}
void Remind(unsigned int offset, unsigned int count, void *p)
{
int ret;
/* Remind variable from file. */
fseek(retain_buffer, retain_info.header_offset+offset, SEEK_SET);
ret = fread((void *)p, count, 1, retain_buffer);
}
#endif // !HAVE_RETAIN
/**
* Tail of code common to all C targets
**/
/**
* LOGGING
**/
#ifndef TARGET_LOGGING_DISABLE
#ifndef LOG_BUFFER_SIZE
#define LOG_BUFFER_SIZE (1<<14) /*16Ko*/
#endif
#ifndef LOG_BUFFER_ATTRS
#define LOG_BUFFER_ATTRS
#endif
#define LOG_BUFFER_MASK (LOG_BUFFER_SIZE-1)
static char LogBuff[LOG_LEVELS][LOG_BUFFER_SIZE] LOG_BUFFER_ATTRS;
static void inline copy_to_log(uint8_t level, uint32_t buffpos, void* buf, uint32_t size){
if(buffpos + size < LOG_BUFFER_SIZE){
memcpy(&LogBuff[level][buffpos], buf, size);
}else{
uint32_t remaining = LOG_BUFFER_SIZE - buffpos;
memcpy(&LogBuff[level][buffpos], buf, remaining);
memcpy(LogBuff[level], (char*)buf + remaining, size - remaining);
}
}
static void inline copy_from_log(uint8_t level, uint32_t buffpos, void* buf, uint32_t size){
if(buffpos + size < LOG_BUFFER_SIZE){
memcpy(buf, &LogBuff[level][buffpos], size);
}else{
uint32_t remaining = LOG_BUFFER_SIZE - buffpos;
memcpy(buf, &LogBuff[level][buffpos], remaining);
memcpy((char*)buf + remaining, LogBuff[level], size - remaining);
}
}
/* Log buffer structure
|<-Tail1.msgsize->|<-sizeof(mTail)->|<--Tail2.msgsize-->|<-sizeof(mTail)->|...
| Message1 Body | Tail1 | Message2 Body | Tail2 |
*/
typedef struct {
uint32_t msgidx;
uint32_t msgsize;
unsigned long tick;
IEC_TIME time;
} mTail;
/* Log cursor : 64b
|63 ... 32|31 ... 0|
| Message | Buffer |
| counter | Index | */
static uint64_t LogCursor[LOG_LEVELS] LOG_BUFFER_ATTRS = {0x0,0x0,0x0,0x0};
void ResetLogCount(void) {
uint8_t level;
for(level=0;level<LOG_LEVELS;level++){
LogCursor[level] = 0;
}
}
/* Store one log message of give size */
int LogMessage(uint8_t level, char* buf, uint32_t size){
if(size < LOG_BUFFER_SIZE - sizeof(mTail)){
uint32_t buffpos;
uint64_t new_cursor, old_cursor;
mTail tail;
tail.msgsize = size;
tail.tick = __tick;
PLC_GetTime(&tail.time);
/* We cannot increment both msg index and string pointer
in a single atomic operation but we can detect having been interrupted.
So we can try with atomic compare and swap in a loop until operation
succeeds non interrupted */
do{
old_cursor = LogCursor[level];
buffpos = (uint32_t)old_cursor;
tail.msgidx = (old_cursor >> 32);
new_cursor = ((uint64_t)(tail.msgidx + 1)<<32)
| (uint64_t)((buffpos + size + sizeof(mTail)) & LOG_BUFFER_MASK);
}while(AtomicCompareExchange64(
(long long*)&LogCursor[level],
(long long)old_cursor,
(long long)new_cursor)!=(long long)old_cursor);
copy_to_log(level, buffpos, buf, size);
copy_to_log(level, (buffpos + size) & LOG_BUFFER_MASK, &tail, sizeof(mTail));
return 1; /* Success */
}else{
char mstr[] = "Logging error : message too big";
LogMessage(LOG_CRITICAL, mstr, sizeof(mstr));
}
return 0;
}
uint32_t GetLogCount(uint8_t level){
return (uint64_t)LogCursor[level] >> 32;
}
/* Return message size and content */
uint32_t GetLogMessage(uint8_t level, uint32_t msgidx, char* buf, uint32_t max_size, uint32_t* tick, uint32_t* tv_sec, uint32_t* tv_nsec){
uint64_t cursor = LogCursor[level];
if(cursor){
/* seach cursor */
uint32_t stailpos = (uint32_t)cursor;
uint32_t smsgidx;
mTail tail;
tail.msgidx = cursor >> 32;
tail.msgsize = 0;
/* Message search loop */
do {
smsgidx = tail.msgidx;
stailpos = (stailpos - sizeof(mTail) - tail.msgsize ) & LOG_BUFFER_MASK;
copy_from_log(level, stailpos, &tail, sizeof(mTail));
}while((tail.msgidx == smsgidx - 1) && (tail.msgidx > msgidx));
if(tail.msgidx == msgidx){
uint32_t sbuffpos = (stailpos - tail.msgsize ) & LOG_BUFFER_MASK;
uint32_t totalsize = tail.msgsize;
*tick = tail.tick;
*tv_sec = tail.time.tv_sec;
*tv_nsec = tail.time.tv_nsec;
copy_from_log(level, sbuffpos, buf,
totalsize > max_size ? max_size : totalsize);
return totalsize;
}
}
return 0;
}
#endif
#ifndef TARGET_EXT_SYNC_DISABLE
#define CALIBRATED -2
#define NOT_CALIBRATED -1
static int calibration_count = NOT_CALIBRATED;
static IEC_TIME cal_begin;
static long long Tsync = 0;
static long long FreqCorr = 0;
static int Nticks = 0;
static unsigned long last_tick = 0;
/*
* Called on each external periodic sync event
* make PLC tick synchronous with external sync
* ratio defines when PLC tick occurs between two external sync
* @param sync_align_ratio
* 0->100 : align ratio
* < 0 : no align, calibrate period
**/
void align_tick(int sync_align_ratio)
{
/*
printf("align_tick(%d)\n", calibrate);
*/
if(sync_align_ratio < 0){ /* Calibration */
if(calibration_count == CALIBRATED)
/* Re-calibration*/
calibration_count = NOT_CALIBRATED;
if(calibration_count == NOT_CALIBRATED)
/* Calibration start, get time*/
PLC_GetTime(&cal_begin);
calibration_count++;
}else{ /* do alignment (if possible) */
if(calibration_count >= 0){
/* End of calibration */
/* Get final time */
IEC_TIME cal_end;
PLC_GetTime(&cal_end);
/*adjust calibration_count*/
calibration_count++;
/* compute mean of Tsync, over calibration period */
Tsync = ((long long)(cal_end.tv_sec - cal_begin.tv_sec) * (long long)1000000000 +
(cal_end.tv_nsec - cal_begin.tv_nsec)) / calibration_count;
if( (Nticks = (Tsync / common_ticktime__)) > 0){
FreqCorr = (Tsync % common_ticktime__); /* to be divided by Nticks */
}else{
FreqCorr = Tsync - (common_ticktime__ % Tsync);
}
/*
printf("Tsync = %ld\n", Tsync);
printf("calibration_count = %d\n", calibration_count);
printf("Nticks = %d\n", Nticks);
*/
calibration_count = CALIBRATED;
}
if(calibration_count == CALIBRATED){
/* Get Elapsed time since last PLC tick (__CURRENT_TIME) */
IEC_TIME now;
long long elapsed;
long long Tcorr;
long long PhaseCorr;
long long PeriodicTcorr;
PLC_GetTime(&now);
elapsed = (now.tv_sec - __CURRENT_TIME.tv_sec) * 1000000000 + now.tv_nsec - __CURRENT_TIME.tv_nsec;
if(Nticks > 0){
PhaseCorr = elapsed - (common_ticktime__ + FreqCorr/Nticks)*sync_align_ratio/100; /* to be divided by Nticks */
Tcorr = common_ticktime__ + (PhaseCorr + FreqCorr) / Nticks;
if(Nticks < 2){
/* When Sync source period is near Tick time */
/* PhaseCorr may not be applied to Periodic time given to timer */
PeriodicTcorr = common_ticktime__ + FreqCorr / Nticks;
}else{
PeriodicTcorr = Tcorr;
}
}else if(__tick > last_tick){
last_tick = __tick;
PhaseCorr = elapsed - (Tsync*sync_align_ratio/100);
PeriodicTcorr = Tcorr = common_ticktime__ + PhaseCorr + FreqCorr;
}else{
/*PLC did not run meanwhile. Nothing to do*/
return;
}
/* DO ALIGNEMENT */
PLC_SetTimer(Tcorr - elapsed, PeriodicTcorr);
}
}
}
#endif
/*
* Python Asynchronous execution code
*
* PLC put python commands in a fifo, respecting execution order
* with the help of C pragmas inserted in python_eval FB code
*
* Buffer content is read asynchronously, (from non real time part),
* commands are executed and result stored for later use by PLC.
*
* In this implementation, fifo is a list of pointer to python_eval
* function blocks structures. Some local variables have been added in
* python_eval interface. We use those local variables as buffer and state
* flags.
*
* */
#include "iec_types_all.h"
#include "POUS.h"
#include <string.h>
/* The fifo (fixed size, as number of FB is fixed) */
static PYTHON_EVAL* EvalFBs[1];
/* Producer and consumer cursors */
static int Current_PLC_EvalFB;
static int Current_Python_EvalFB;
/* A global IEC-Python gateway state, for use inside python_eval FBs*/
static int PythonState;
#define PYTHON_LOCKED_BY_PYTHON 0
#define PYTHON_LOCKED_BY_PLC 1
#define PYTHON_MUSTWAKEUP 2
#define PYTHON_FINISHED 4
/* Each python_eval FunctionBlock have it own state */
#define PYTHON_FB_FREE 0
#define PYTHON_FB_REQUESTED 1
#define PYTHON_FB_PROCESSING 2
#define PYTHON_FB_ANSWERED 3
int WaitPythonCommands(void);
void UnBlockPythonCommands(void);
int TryLockPython(void);
void UnLockPython(void);
void LockPython(void);
int __init_py_ext()
{
int i;
/* Initialize cursors */
Current_Python_EvalFB = 0;
Current_PLC_EvalFB = 0;
PythonState = PYTHON_LOCKED_BY_PYTHON;
for(i = 0; i < 1; i++)
EvalFBs[i] = NULL;
return 0;
}
void __cleanup_py_ext()
{
PythonState = PYTHON_FINISHED;
UnBlockPythonCommands();
}
void __retrieve_py_ext()
{
/* Check Python thread is not being
* modifying internal python_eval data */
PythonState = TryLockPython() ?
PYTHON_LOCKED_BY_PLC :
PYTHON_LOCKED_BY_PYTHON;
/* If python thread _is_ in, then PythonState remains PYTHON_LOCKED_BY_PYTHON
* and python_eval will no do anything */
}
void __publish_py_ext()
{
if(PythonState & PYTHON_LOCKED_BY_PLC){
/* If runnig PLC did push something in the fifo*/
if(PythonState & PYTHON_MUSTWAKEUP){
/* WakeUp python thread */
UnBlockPythonCommands();
}
UnLockPython();
}
}
/**
* Called by the PLC, each time a python_eval
* FB instance is executed
*/
void __PythonEvalFB(int poll, PYTHON_EVAL* data__)
{
if(!__GET_VAR(data__->TRIG)){
/* ACK is False when TRIG is false, except a pulse when receiving result */
__SET_VAR(data__->, ACK,, 0);
}
/* detect rising edge on TRIG to trigger evaluation */
if(((__GET_VAR(data__->TRIG) && !__GET_VAR(data__->TRIGM1)) ||
/* polling is equivalent to trig on value rather than on rising edge*/
(poll && __GET_VAR(data__->TRIG) )) &&
/* trig only if not already trigged */
__GET_VAR(data__->TRIGGED) == 0){
/* mark as trigged */
__SET_VAR(data__->, TRIGGED,, 1);
/* make a safe copy of the code */
__SET_VAR(data__->, PREBUFFER,, __GET_VAR(data__->CODE));
}
/* retain value for next rising edge detection */
__SET_VAR(data__->, TRIGM1,, __GET_VAR(data__->TRIG));
/* python thread is not in ? */
if( PythonState & PYTHON_LOCKED_BY_PLC){
/* if some answer are waiting, publish*/
if(__GET_VAR(data__->STATE) == PYTHON_FB_ANSWERED){
/* Copy buffer content into result*/
__SET_VAR(data__->, RESULT,, __GET_VAR(data__->BUFFER));
/* signal result presence to PLC*/
__SET_VAR(data__->, ACK,, 1);
/* Mark as free */
__SET_VAR(data__->, STATE,, PYTHON_FB_FREE);
/* mark as not trigged */
if(!poll)
__SET_VAR(data__->, TRIGGED,, 0);
/*printf("__PythonEvalFB pop %d - %*s\n",Current_PLC_EvalFB, data__->BUFFER.len, data__->BUFFER.body);*/
}else if(poll){
/* when in polling, no answer == ack down */
__SET_VAR(data__->, ACK,, 0);
}
/* got the order to act ?*/
if(__GET_VAR(data__->TRIGGED) == 1 &&
/* and not already being processed */
__GET_VAR(data__->STATE) == PYTHON_FB_FREE)
{
/* Enter the block in the fifo
* Don't have to check if fifo cell is free
* as fifo size == FB count, and a FB cannot
* be requested twice */
EvalFBs[Current_PLC_EvalFB] = data__;
/* copy into BUFFER local*/
__SET_VAR(data__->, BUFFER,, __GET_VAR(data__->PREBUFFER));
/* Set ACK pin to low so that we can set a rising edge on result */
if(!poll){
/* when not polling, a new answer imply reseting ack*/
__SET_VAR(data__->, ACK,, 0);
}else{
/* when in polling, acting reset trigger */
__SET_VAR(data__->, TRIGGED,, 0);
}
/* Mark FB busy */
__SET_VAR(data__->, STATE,, PYTHON_FB_REQUESTED);
/* Have to wakeup python thread in case he was asleep */
PythonState |= PYTHON_MUSTWAKEUP;
/*printf("__PythonEvalFB push %d - %*s\n",Current_PLC_EvalFB, data__->BUFFER.len, data__->BUFFER.body);*/
/* Get a new line */
Current_PLC_EvalFB = (Current_PLC_EvalFB + 1) % 1;
}
}
}
char* PythonIterator(char* result, void** id)
{
char* next_command;
PYTHON_EVAL* data__;
//printf("PythonIterator result %s\n", result);
/*emergency exit*/
if(PythonState & PYTHON_FINISHED) return NULL;
/* take python mutex to prevent changing PLC data while PLC running */
LockPython();
/* Get current FB */
data__ = EvalFBs[Current_Python_EvalFB];
if(data__ && /* may be null at first run */
__GET_VAR(data__->STATE) == PYTHON_FB_PROCESSING){ /* some answer awaited*/
/* If result not None */
if(result){
/* Get results len */
__SET_VAR(data__->, BUFFER, .len, strlen(result));
/* prevent results overrun */
if(__GET_VAR(data__->BUFFER, .len) > STR_MAX_LEN)
{
__SET_VAR(data__->, BUFFER, .len, STR_MAX_LEN);
/* TODO : signal error */
}
/* Copy results to buffer */
strncpy((char*)__GET_VAR(data__->BUFFER, .body), result, __GET_VAR(data__->BUFFER,.len));
}else{
__SET_VAR(data__->, BUFFER, .len, 0);
}
/* remove block from fifo*/
EvalFBs[Current_Python_EvalFB] = NULL;
/* Mark block as answered */
__SET_VAR(data__->, STATE,, PYTHON_FB_ANSWERED);
/* Get a new line */
Current_Python_EvalFB = (Current_Python_EvalFB + 1) % 1;
//printf("PythonIterator ++ Current_Python_EvalFB %d\n", Current_Python_EvalFB);
}
/* while next slot is empty */
while(((data__ = EvalFBs[Current_Python_EvalFB]) == NULL) ||
/* or doesn't contain command */
__GET_VAR(data__->STATE) != PYTHON_FB_REQUESTED)
{
UnLockPython();
/* wait next FB to eval */
//printf("PythonIterator wait\n");
if(WaitPythonCommands()) return NULL;
/*emergency exit*/
if(PythonState & PYTHON_FINISHED) return NULL;
LockPython();
}
/* Mark block as processing */
__SET_VAR(data__->, STATE,, PYTHON_FB_PROCESSING);
//printf("PythonIterator\n");
/* make BUFFER a null terminated string */
__SET_VAR(data__->, BUFFER, .body[__GET_VAR(data__->BUFFER, .len)], 0);
/* next command is BUFFER */
next_command = (char*)__GET_VAR(data__->BUFFER, .body);
*id=data__;
/* free python mutex */
UnLockPython();
/* return the next command to eval */
return next_command;
}
/*******************************************/
/* FILE GENERATED BY iec2c */
/* Editing this file is not recommended... */
/*******************************************/
#include "iec_std_lib.h"
// RESOURCE RESOURCE1
extern unsigned long long common_ticktime__;
#include "accessor.h"
#include "POUS.h"
#include "config.h"
#include "POUS.c"
BOOL TASK0;
PLC_PRG RESOURCE1__INSTANCE0;
#define INSTANCE0 RESOURCE1__INSTANCE0
void RESOURCE1_init__(void) {
BOOL retain;
retain = 0;
TASK0 = __BOOL_LITERAL(FALSE);
PLC_PRG_init__(&INSTANCE0,retain);
}
void RESOURCE1_run__(unsigned long tick) {
TASK0 = !(tick % 1);
if (TASK0) {
PLC_PRG_body__(&INSTANCE0);
}
}
<?xml version='1.0' encoding='utf-8'?> <?xml version='1.0' encoding='utf-8'?>
<OPCUAClient xmlns:xsd="http://www.w3.org/2001/XMLSchema" Server_URI="opc.tcp://192.168.0.78:4840"/> <OPCUAClient xmlns:xsd="http://www.w3.org/2001/XMLSchema" Server_URI="opc.tcp://192.168.1.106:4840"/>
#!/bin/bash
# =================================================
#
# Beremiz installation in Ubuntu 20.04
#
# =================================================
# run as root
if [[ $EUID -ne 0 ]]; then
echo 'run this script as sudo !' 1>&2
exit 1
fi
echo "OSTYPE:"
if [[ "$OSTYPE" == "linux-gnu"* ]]; then
ls -al /etc/*-release
cat /etc/arch-release
# Ubuntu
if [ -f /etc/os-release ]; then
. /etc/os-release
OS_NAME=$NAME
OS_VER_ID=$VERSION_ID
echo "System OS_NAME: $OS_NAME"
else
echo "tested release files do not exist -> happens with older or unsupported OS versions"
exit 2
fi
echo "Found OS information: $OS_NAME, version: $OS_VER_ID"
if [[ "$OS_NAME" == *"Ubuntu"* ]]; then
echo "Ubuntu"
export DEBIAN_FRONTEND=noninteractive
# Update system
# install required packages
apt -yq update
apt -yq install python-wxgtk3.0 python-lxml python-is-python2 python-numpy build-essential python-dev-is-python2 mercurial autoconf bison flex python2 virtualenv python-lxml python-numpy libgtk-3-dev libgl1-mesa-dev libglu1-mesa-dev
pip2 install pyro twisted nevow autobahn lxml opcua
# Path preparation
LOCAL_HOME_DIR=/home/${SUDO_USER:-$(whoami)}/
echo "$LOCAL_HOME_DIR"
# Build Beremiz
echo "Building Beremiz."
cd $LOCAL_HOME_DIR/
mkdir -p Beremiz
cd Beremiz/
hg clone https://hg.beremiz.org/beremiz
# Build Matiec
echo "Building Matiec."
hg clone https://hg.beremiz.org/matiec
autoreconf -i
./configure
make -j
pwd
# Build Modbus
echo "Building Modbus."
hg clone https://hg.beremiz.org/Modbus/
make
echo "Beremiz is installed."
echo "Run $ python2 ~/Beremiz/Beremiz/Beremiz.py to open Beremiz"
else
echo "Distro is not supported."
exit 3
fi
else
echo "ERROR: OS is not supported: $OSTYPE"
exit 4
fi
\ No newline at end of file
#!/bin/bash
# =================================================
#
# Open62541 installation in Ubuntu 20.04
#
# =================================================
# run as root
if [[ $EUID -ne 0 ]]; then
echo 'run this script as sudo !' 1>&2
exit 1
fi
echo "OSTYPE:"
if [[ "$OSTYPE" == "linux-gnu"* ]]; then
ls -al /etc/*-release
cat /etc/arch-release
# Ubuntu
if [ -f /etc/os-release ]; then
. /etc/os-release
OS_NAME=$NAME
OS_VER_ID=$VERSION_ID
echo "System OS_NAME: $OS_NAME"
else
echo "tested release files do not exist -> happens with older or unsupported OS versions"
exit 2
fi
echo "Found OS information: $OS_NAME, version: $OS_VER_ID"
if [[ "$OS_NAME" == *"Ubuntu"* ]]; then
echo "Ubuntu"
export DEBIAN_FRONTEND=noninteractive
# Path preparation
LOCAL_HOME_DIR=/home/${SUDO_USER:-$(whoami)}/
echo "$LOCAL_HOME_DIR"
# Build and install open62541
echo "Building and installing open62541."
cd $LOCAL_HOME_DIR/
git clone https://github.com/open62541/open62541.git
cd open62541/
git submodule update --init --recursive
mkdir -p build && cd build
cmake -DBUILD_SHARED_LIBS=ON -DCMAKE_BUILD_TYPE=RelWithDebInfo -DUA_NAMESPACE_ZERO=FULL -DUA_ENABLE_AMALGAMATION=ON ..
make -j
make install
echo "open62541 is installed."
echo "Transfer open62541.c and open62541.h to your project location from the buld directory"
echo "Ex: cp ~/open62541/build/open62541.* ~/osie/coupler/opc-ua-server/"
else
echo "Distro is not supported."
exit 3
fi
else
echo "ERROR: OS is not supported: $OSTYPE"
exit 4
fi
\ No newline at end of file
CC=arm-linux-gnueabihf-gcc
CFLAGS=-I.
OUT_DIR=bin/
server: server.c open62541.c
$(CC) -o $@ $^ $(CFLAGS)
@cp server $(OUT_DIR)
@rm server
clean:
@rm $(OUT_DIR)server 2>/dev/null || true
.PHONY: clean
\ No newline at end of file
# Usage of OPC UA server with Beremiz
## Prerequisites
### Installing Beremiz
Run beremiz_install.sh file under Init folder
### Installing open62541
Run open62541_install.sh file under Init folder
### Copy the open62541.h and open62541.c
If you are developing OPC UA application for LIM2 controller with MOD-IO, then your OPC UA location will be ~/osie/coupler/opc-ua-server/
$ cp ~/open62541/build/open62541.* ~/osie/coupler/opc-ua-server/
### Building your OPC UA server (Cross compilation to ARM architecture from Ubuntu)
$ arm-linux-gnueabihf-gcc open62541.c server.c -o server
\ No newline at end of file
...@@ -13,9 +13,7 @@ ...@@ -13,9 +13,7 @@
#include <sys/ioctl.h> #include <sys/ioctl.h>
#include <unistd.h> #include <unistd.h>
#include <signal.h> #include <signal.h>
#include <open62541/plugin/log_stdout.h> #include "open62541.h"
#include <open62541/server.h>
#include <open62541/server_config_default.h>
// global relay state // global relay state
uint8_t I2C_0_RELAYS_STATE = 0; // state of 4 relays at I2C slave 0 uint8_t I2C_0_RELAYS_STATE = 0; // state of 4 relays at I2C slave 0
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment