EC300 ARM Industrial Computer Development Guide - IOTROUTER
Schwebe-Animation
Hinweis zum chinesischen Neujahrsfest (5.-23. Februar) - IOTRouter wünscht Ihnen ein frohes neues Jahr!

EC300 ARM Industrial Computer Development Guide

The EC series ARM industrial computers run an optimized version of Ubuntu at their core, providing a stable foundation for industrial scenarios. Additionally, they come pre-installed with Node-RED und NeuronEX-Lite, providing a clear differentiation advantage.

  • Node-RED: An open-source visual programming tool. It offers a browser-based flow orchestration interface where devices and data processing can be connected and configured via drag-and-drop.
  • NeuronEX-Lite: A lightweight edge computing software designed for industrial IoT. It supports multiple industrial protocols, enabling fast device connectivity and millisecond-level real-time data processing.

This “Double N” software architecture allows developers to quickly build business logic through drag-and-drop (Node-RED) while providing professional capabilities such as protocol parsing, device connection, and data collection (NeuronEX-Lite). The EC series achieves a seamless combination of rapid business development and professional industrial data acquisition, significantly improving development efficiency and operational reliability in industrial IoT solutions.

To help developers understand and use EC gateways, we previously released the EC100 Development Guide. Now, we present its sister guide — EC300 Development Guide. Before starting, if you’ve forgotten basic info such as the EC gateway backend URL, please review the EC Series Gateway Learning Guide.

Development Preparation

This guide applies to the following EC300 series models:

  • EC300
  • EC301
  • EC300P

Tools

Tool Category Recommended Tool Herunterladen
SSH Tool MobaXterm or any preferred SSH tool MobaXterm_Installer_v20.5.zip
Serial Debug Tool XCOM or any preferred serial debug tool XCOM V2.6.zip
Hardware Tool Common auxiliary debug tools: CAN to USB / RS485 to USB / RS232 to USB.  Prepare yourself NONE
Cross-Compile Toolchain gcc-arm-10.2-2020.11-x86_64-aarch64-none-linux-gnu.zip

Software Environment

Software Version
OS Eingebettetes Linux
Kernel Linux 5.10.209
Nodejs v22.17.0
Python python3.8
Shell bash
GLIBC GLIBC_2.31
Docker V26.1.3
QT Library V5.15.10
Desktop Environment Xfce4

Device Resources

Kategorie Details Notes
Lagerung 16G total Factory default free space ≈ 9 GB;
Expandable via SD card or M.2 NVMe SSD
RAM 2G

Factory default free space ≈ 1.4 GB

CPU 4*Cortex-A53@1.8GHz Factory default free space ≈ 98%
NPU 1 TPOS Factory default free space ≈ 100%

Peripheral Interfaces

1. Debug Port

EC300 series exposes system debug via Type-C.

2. Serial Interfaces

Schnittstelle Device File EC300 EC300P EC301
RS485-1 /dev/ttyS7 ✔️ ✔️ ✔️
RS485-2 /dev/ttyS2 ✔️
RS232 /dev/ttyS3 ✔️

Note: RS485 handles sending/receiving automatically; users don’t need to switch manually.

2.1 Quick Test

Use the minicom tool for testing. For detailed instructions, you can Google it or consult an AI.

2.2 C Example

uart_example.c :


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <termios.h>
#include <errno.h>
#include <sys/select.h>

#define BUFFER_SIZE 256

typedef struct {
    int baud_rate;      // baud rate
    int data_bits;      // data bits (5,6,7,8)
    int stop_bits;      // stop bits (1,2)
    char parity;        // Parity bit (N: None, O: Odd, E: Even)
} SerialConfig;

int set_serial_attr(int fd, SerialConfig *config)
{
    struct termios tty;

    if (tcgetattr(fd, &tty) < 0) { perror("tcgetattr"); return -1; } // set the baud rate  
    speed_t speed; 
    switch (config->baud_rate) {
        case 9600:   speed = B9600; break;
        case 19200:  speed = B19200; break;
        case 38400:  speed = B38400; break;
        case 57600:  speed = B57600; break;
        case 115200: speed = B115200; break;
        default:
            fprintf(stderr, "Unsupported baud rate, using 115200\n");
            speed = B115200;
    }
    cfsetispeed(&tty, speed);
    cfsetospeed(&tty, speed);

    // set the data bits
    tty.c_cflag &= ~CSIZE;
    switch (config->data_bits) {
        case 5: tty.c_cflag |= CS5; break;
        case 6: tty.c_cflag |= CS6; break;
        case 7: tty.c_cflag |= CS7; break;
        case 8: tty.c_cflag |= CS8; break;
        default:
            fprintf(stderr, "Unsupported data bits, using 8\n");
            tty.c_cflag |= CS8;
    }

    // set the stop bits
    if (config->stop_bits == 2) {
        tty.c_cflag |= CSTOPB;
    } else {
        tty.c_cflag &= ~CSTOPB;
    }

    // set the parity bit
    switch (config->parity) {
        case 'N': case 'n':
            tty.c_cflag &= ~PARENB;  // Parity: None
            break;
        case 'O': case 'o':
            tty.c_cflag |= PARENB;   // Parity: Odd
            tty.c_cflag |= PARODD;
            break;
        case 'E': case 'e':
            tty.c_cflag |= PARENB;   // Parity: Even
            tty.c_cflag &= ~PARODD;
            break;
        default:
            fprintf(stderr, "Unsupported parity, using N\n");
            tty.c_cflag &= ~PARENB;
    }

    // Other settings
    tty.c_cflag |= (CLOCAL | CREAD);    // Enable Receive and Local Mode
    tty.c_cflag &= ~CRTSCTS;            // No hardware flow control

    tty.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); // Raw Input Mode
    tty.c_oflag &= ~OPOST;              // Raw Output Mode

    tty.c_cc[VMIN] = 1;                 // Minimum Read Characters
    tty.c_cc[VTIME] = 0;                // Read Timeout (Unit: 0.1 seconds)

    if (tcsetattr(fd, TCSANOW, &tty) < 0) {
        perror("tcsetattr");
        return -1;
    }

    return 0;
}

int main(int argc, char *argv[])
{
    int fd;
    char *portname;

    if (argc < 2) {
        fprintf(stderr, "Usage: %s \n", argv[0]);
        exit(EXIT_FAILURE);
    }

    portname = argv[1];

    // Configure Serial Port Parameters
    SerialConfig config = {
        .baud_rate = 115200,  // baud rate
        .data_bits = 8,       // data bits
        .stop_bits = 1,       // stop bits
        .parity = 'N'         // parity bit (N: None, O: Odd, E: Even)
    };

    fd = open(portname, O_RDWR | O_NOCTTY | O_NONBLOCK);
    if (fd < 0) {
        perror("open");
        exit(EXIT_FAILURE);
    }

    if (set_serial_attr(fd, &config)) {
        close(fd);
        exit(EXIT_FAILURE);
    }

    printf("Serial port echo test running on %s\n", portname);
    printf("Configuration: %d baud, %d data bits, %d stop bit, %c parity\n",
           config.baud_rate, config.data_bits, config.stop_bits, config.parity);
    printf("Press Ctrl+C to exit.\n");

    fd_set readfds;
    char buffer[BUFFER_SIZE];
    int n;

    while (1) {
        FD_ZERO(&readfds);
        FD_SET(fd, &readfds);

        // Wait indefinitely for data (timeout disabled)
        if (select(fd + 1, &readfds, NULL, NULL, NULL) < 0) { perror("select"); break; } if (FD_ISSET(fd, &readfds)) { n = read(fd, buffer, BUFFER_SIZE - 1); if (n > 0) {
                buffer[n] = '\0';
                printf("Received %d bytes: %s\n", n, buffer);

                // Echo Data
                write(fd, buffer, n);
            } else if (n < 0) {
                if (errno != EAGAIN && errno != EWOULDBLOCK) {
                    perror("read");
                    break;
                }
            }
        }
    }

    close(fd);
    return 0;
}


Compile: gcc uart_example.c -o uart_example

RS485-1 Test Run: ./uart_example /dev/ttyS7

2.3 Python Example

uart_example.py: 


import serial
import select
import sys
import tty

class SerialConfig:
    def __init__(self):
        self.baud_rate = 115200  # baud rate
        self.data_bits = 8       # data bits
        self.stop_bits = 1       # stop bits
        self.parity = 'N'        # parity bit (N: None, O: Odd, E: Even)

def set_serial_config(ser, config):
    """Configure Serial Port Parameters"""
    # set the baud rate
    ser.baudrate = config.baud_rate
    
    # set the data bits
    if config.data_bits == 5:
        ser.bytesize = serial.FIVEBITS
    elif config.data_bits == 6:
        ser.bytesize = serial.SIXBITS
    elif config.data_bits == 7:
        ser.bytesize = serial.SEVENBITS
    else:  # Default is 8 bits
        ser.bytesize = serial.EIGHTBITS
    
    # set the stop bits
    if config.stop_bits == 2:
        ser.stopbits = serial.STOPBITS_TWO
    else:  # Default is 1 bit
        ser.stopbits = serial.STOPBITS_ONE
    
    # set the parity bit
    if config.parity.upper() == 'O':
        ser.parity = serial.PARITY_ODD
    elif config.parity.upper() == 'E':
        ser.parity = serial.PARITY_EVEN
    else:  # Default is None (no parity)
        ser.parity = serial.PARITY_NONE
    
    # Disable hardware flow control
    ser.rtscts = False
    # Disable software flow control
    ser.xonxoff = False
    # Set timeout
    ser.timeout = 0.1  # 100 ms timeout
    
    return ser

def main():
    if len(sys.argv) < 2:
        print(f"Usage: {sys.argv[0]} <Serial device>")
        print(f"Example: {sys.argv[0]} /dev/ttyS7")
        sys.exit(1)
    
    portname = sys.argv[1]
    buff_size = 256
    
    # Initialize Serial Port Configuration
    config = SerialConfig()
    
    try:
        # Open the serial port
        ser = serial.Serial()
        ser.port = portname
        # Apply the configuration and open the serial port
        ser = set_serial_config(ser, config)
        ser.open()
        
        if not ser.is_open:
            print("Unable to open the serial port")
            sys.exit(1)
        
        print(f"Serial port {portname} opened successfully")
        print(f"Set: baud rate {config.baud_rate}, data bits {config.data_bits}, "
              f"Stop bits {config.stop_bits}, Parity {config.parity}")
        print("Press Ctrl+C to exit the program")
        
        # Use select to monitor serial port data
        while True:
            # Wait for serial port data to be readable
            readable, _, _ = select.select([ser.fileno()], [], [], None)
            
            if readable:
                # Read data
                data = ser.read(buff_size - 1)
                if data:
                    # Attempt to decode as a string; if it fails, display in hexadecimal
                    try:
                        text = data.decode('utf-8')
                    except UnicodeDecodeError:
                        text = f"[Binary data] {data.hex()}"
                    
                    print(f"Received {len(data)} bytes: {text}")
                    
                    # Echo data
                    ser.write(data)
    
    except serial.SerialException as e:
        print(f"Serial port error: {e}")
        sys.exit(1)
    except KeyboardInterrupt:
        print("\nUser interrupted, exiting the program")
    finally:
        if 'ser' in locals() and ser.is_open:
            ser.close()
            print("Serial port closed")

if __name__ == "__main__":
    main()

First, install the required dependencies:
pip install pyserial

RS485-1 Test Run: python uart_example.py /dev/ttyS7

3. LED

The EC300 series has 6 LEDs in total, 4 of which are user-programmable.

Hardware IO Index IO Number Chip Notes
POW / / / Power indicator
RUN 50 GPIO1_C2 gpiochip1 Used by the system; blinking when running
LED1 137 GPIO4_B1 gpiochip4 User-programmable
LED2 51 GPIO1_C3 gpiochip1 User-programmable
LED3 143 GPIO4_B7 gpiochip4 User-programmable
LED4 52 GPIO1_C4 gpiochip1 User-programmable

Note: The IO index is calculated from the IO number.

3.1 Quick Test

led_example.sh:

#!/bin/bash

# Check whether a GPIO index has been provided
if [ -z "$1" ]; then
    echo "Usage: $0 "
    exit 1
fi

GPIO=$1
GPIO_PATH="/sys/class/gpio/gpio$GPIO"
EXPORT_PATH="/sys/class/gpio/export"

# Check whether the GPIO has already been exported
if [ ! -d "$GPIO_PATH" ]; then
    echo "Exporting GPIO $GPIO..."
    echo "$GPIO" > "$EXPORT_PATH"
    if [ $? -ne 0 ]; then
        echo "Failed to export GPIO $GPIO. Check permissions or if it's in use."
        exit 1
    fi
else
    echo "GPIO $GPIO is already exported."
fi

# Set the GPIO direction to output
echo "out" > "$GPIO_PATH/direction"

# Set the initial value to low level
echo 0 > "$GPIO_PATH/value"

echo "Toggling GPIO $GPIO every 1 second. Press Ctrl+C to stop."

# Start toggling in a loop
while true; do
    echo 1 > "$GPIO_PATH/value"
    sleep 1
    echo 0 > "$GPIO_PATH/value"
    sleep 1
done

Test Run:
bash led_example.sh 137

3.2 C Example

led_example.c:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/gpio.h>
#include <errno.h>

#define GPIO_CHIP "/dev/gpiochip4"  // Use gpiochip1
#define GPIO_LINE 137%32             // The offset of the GPIO to be controlled on chip1
#define PERIOD_US 500000            // Period (microseconds)

int main() {
    struct gpiohandle_request req;
    struct gpiohandle_data data;
    int fd, ret;

    // Open the GPIO character device
    fd = open(GPIO_CHIP, O_RDWR);
    if (fd < 0) {
        perror("Unable to open the GPIO device");
        return EXIT_FAILURE;
    }

    // Set up the GPIO request
    memset(&req, 0, sizeof(req));
    req.lineoffsets[0] = GPIO_LINE;  // GPIO line number
    req.flags = GPIOHANDLE_REQUEST_OUTPUT;  // Set to output mode
    req.default_values[0] = 0;       // Set the initial value to low level
    strcpy(req.consumer_label, "gpio-toggle");  // Consumer label
    req.lines = 1;                   // Number of GPIO lines to control

    // Request GPIO control
    ret = ioctl(fd, GPIO_GET_LINEHANDLE_IOCTL, &req);
    if (ret < 0) {
        perror("Unable to acquire GPIO control");
        close(fd);
        return EXIT_FAILURE;
    }

    // Close the GPIO character device (the line handle has already been acquired)
    close(fd);

    printf("Controlling GPIO chip1 line %d with a period of %.1f seconds...\n", 
           GPIO_LINE, PERIOD_US/1000000.0);

    // Toggle GPIO level periodically
    while (1) {
        data.values[0] = 1;  // High level
        ret = ioctl(req.fd, GPIOHANDLE_SET_LINE_VALUES_IOCTL, &data);
        if (ret < 0) {
            perror("Failed to set GPIO to high level");
            break;
        }
        usleep(PERIOD_US/2);

        data.values[0] = 0;  // Low level
        ret = ioctl(req.fd, GPIOHANDLE_SET_LINE_VALUES_IOCTL, &data);
        if (ret < 0) {
            perror("Failed to set GPIO to low level");
            break;
        }
        usleep(PERIOD_US/2);
    }

    // Close the GPIO line handle
    close(req.fd);

    return EXIT_SUCCESS;
}

Compile: gcc led_example.c -o led_example
Test Run: ./led_example

3.3 Python Example

led_example.py:

import os
import time
import sys

# Define constants
GPIO_CHIP = 4          # GPIO chip number
GPIO_LINE = 137 % 32    # Offset of the GPIO to be controlled on the line
PERIOD_US = 500000      # Period (microseconds)

def export_gpio(gpio_num):
    """Export GPIO"""
    try:
        with open('/sys/class/gpio/export', 'w') as f:
            f.write(str(gpio_num))
        time.sleep(0.1)  # Wait for export to complete
        return True
    except IOError as e:
        if "already exists" in str(e):
            return True  # GPIO already exported
        print(f"Failed to export GPIO: {e}")
        return False

def set_gpio_direction(gpio_num, direction):
    """Set GPIO direction"""
    try:
        with open(f'/sys/class/gpio/gpio{gpio_num}/direction', 'w') as f:
            f.write(direction)
        return True
    except IOError as e:
        print(f"Failed to set GPIO direction: {e}")
        return False

def set_gpio_value(gpio_num, value):
    """Set GPIO value"""
    try:
        with open(f'/sys/class/gpio/gpio{gpio_num}/value', 'w') as f:
            f.write(str(value))
        return True
    except IOError as e:
        print(f"Failed to set GPIO value: {e}")
        return False

def unexport_gpio(gpio_num):
    """Unexport GPIO"""
    try:
        with open('/sys/class/gpio/unexport', 'w') as f:
            f.write(str(gpio_num))
        return True
    except IOError as e:
        print(f"Failed to unexport GPIO: {e}")
        return False

def cleanup_gpio(actual_gpio):
    """Clean up GPIO resources: first turn off LED, then unexport GPIO"""
    try:
        print("Turn off LED...")
        # Set GPIO to low level first to turn off LED
        if set_gpio_value(actual_gpio, 0):
            print("LED turned off")
        else:
            print("Failed to turn off LED")
        
        #  Wait a short time to ensure LED is off
        time.sleep(0.05)
        
        # Then unexport GPIO
        print("Unexport GPIO...")
        if unexport_gpio(actual_gpio):
            print("GPIO successfully unexported")
        else:
            print("Failed to unexport GPIO")
            
    except Exception as e:
        print(f"Error occurred while cleaning up GPIO resources: {e}")

def main():
    # Calculate the actual GPIO number
    actual_gpio = GPIO_CHIP * 32 + GPIO_LINE
    
    print(f"GPIO chip: {GPIO_CHIP}, GPIO line: {GPIO_LINE}")
    print(f"Actual GPIO number: {actual_gpio}")
    print(f"Blink period: {PERIOD_US/1000000.0:.1f} seconds")
    print("Press Ctrl+C to stop the program")
    
    try:
        # Export GPIO
        if not export_gpio(actual_gpio):
            return 1
        
        # Set GPIO to output mode
        if not set_gpio_direction(actual_gpio, 'out'):
            cleanup_gpio(actual_gpio)
            return 1
        
        print("Start controlling LED blinking...")
        
        # Toggle GPIO level periodically
        while True:
            # Set to high level
            if not set_gpio_value(actual_gpio, 1):
                break
            time.sleep(PERIOD_US / 2000000.0)  # Wait for half a period
            
            # Set to low level
            if not set_gpio_value(actual_gpio, 0):
                break
            time.sleep(PERIOD_US / 2000000.0)  # Wait for half a period
            
    except KeyboardInterrupt:
        print("\nProgram stopped")
    except Exception as e:
        print(f"An error occurred: {e}")
        import traceback
        traceback.print_exc()
    finally:
        # Clean up GPIO resources: first turn off LED, then unexport GPIO
        print("Start cleaning up GPIO resources...")
        cleanup_gpio(actual_gpio)
        print("Program ended")

if __name__ == "__main__":
    sys.exit(main())

Test Run: 
python led_example.py

4. CAN

The EC301 has two CAN interfaces:

Hardware Interface Netzwerk-Schnittstelle
CAN1 [H1 L1] can0
CAN2 [H2 L2] can1

EC300 ARM Industrial Computer Development Guide

4.1 Quick Test

Connect CAN1 and CAN2 together; test via the command line:

can_example.sh:

#!/bin/bash
#First, initialize can0 and can1 with a baud rate of 500k, then set can0 to receive data and can1 to send data.

ip link set can0 down
ip link set can1 down

ip link set can0 type can bitrate 500000
ip link set can1 type can bitrate 500000

ip link set can0 up
ip link set can1 up

echo 4096 > /sys/class/net/can0/tx_queue_len
echo 4096 > /sys/class/net/can1/tx_queue_len

candump can0 &

cansend can1 001234EF#00.11.22.33.44.55.66.77

4.2 C Example

can_example.c:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <linux/can.h>
#include <linux/can/raw.h>
#include <fcntl.h>
#include <errno.h>
#include <stdint.h>
#include <sys/wait.h> 

#define SEND_CAN "can0"   // CAN interface for sending data
#define RECV_CAN "can1"   // CAN interface for receiving data
#define BITRATE 500000    // Baud rate 500k
#define SAMPLE_POINT 0.8  
#define TX_QUEUE_LEN 4096 

// Configure CAN interface
int setup_can_interface(const char *ifname) {
    char cmd[512];
    FILE *fp;
    
    //  Close interface
    snprintf(cmd, sizeof(cmd), "ip link set %s down", ifname);
    system(cmd);  
    
    // Set CAN parameters
    snprintf(cmd, sizeof(cmd), 
             "ip link set %s type can bitrate %d sample-point %.1f", 
             ifname, BITRATE, SAMPLE_POINT);
    if (system(cmd) != 0) {
        fprintf(stderr, "Failed to configure CAN interface %s\n", ifname);
        return -1;
    }
    
    // Set TX queue length
    snprintf(cmd, sizeof(cmd), "/sys/class/net/%s/tx_queue_len", ifname);
    fp = fopen(cmd, "w");
    if (fp == NULL) {
        perror("Failed to open tx_queue_len");
        return -1;
    }
    fprintf(fp, "%d", TX_QUEUE_LEN);
    fclose(fp);
    
    // Enable interface
    snprintf(cmd, sizeof(cmd), "ip link set %s up", ifname);
    if (system(cmd) != 0) {
        fprintf(stderr, "Failed to bring up CAN interface %s\n", ifname);
        return -1;
    }
    
    printf("CAN interface %s configured:\n", ifname);
    printf("  Bitrate: %d\n", BITRATE);
    printf("  Sample point: %.1f\n", SAMPLE_POINT);
    printf("  TX queue length: %d\n", TX_QUEUE_LEN);
    
    return 0;
}

//Monitor CAN interface data in a child process
void receive_can_messages() {
    int recv_sock;
    struct sockaddr_can recv_addr;
    struct ifreq recv_ifr;
    struct can_frame frame;
    int nbytes;
    
    // Initialize receiver socket
    if ((recv_sock = socket(PF_CAN, SOCK_RAW, CAN_RAW)) < 0) {
        perror("Receive socket creation failed");
        exit(EXIT_FAILURE);
    }
    
    // Bind receiver interface
    strcpy(recv_ifr.ifr_name, RECV_CAN);
    if (ioctl(recv_sock, SIOCGIFINDEX, &recv_ifr) < 0) {
        perror("IOCTL for receive interface failed");
        close(recv_sock);
        exit(EXIT_FAILURE);
    }
    recv_addr.can_family = AF_CAN;
    recv_addr.can_ifindex = recv_ifr.ifr_ifindex;
    if (bind(recv_sock, (struct sockaddr *)&recv_addr, sizeof(recv_addr)) < 0) {
        perror("Receive socket bind failed");
        close(recv_sock);
        exit(EXIT_FAILURE);
    }
    
    printf("Receive process started (listening on %s)...\n", RECV_CAN);
    
  
    while (1) {
        nbytes = read(recv_sock, &frame, sizeof(frame));
        if (nbytes < 0) {
            if (errno == EAGAIN || errno == EWOULDBLOCK) {
                continue;  
            }
            perror("CAN frame read failed");
            break;
        }
        
       
        if (nbytes == CAN_MTU) {
            printf("[%s] Received CAN frame: ID 0x%08X, DLC %d, data: ", 
                   RECV_CAN, frame.can_id, frame.can_dlc);
            for (int i = 0; i < frame.can_dlc; i++) { 
                printf("%02X ", frame.data[i]); 
            } 
            printf("\n"); 
       } else { 
            printf("Received invalid frame (size: %d bytes)\n", nbytes);
       } 
    } 
    close(recv_sock); 
    exit(EXIT_SUCCESS); 
 }
 // Send CAN frame
 int send_can_frame(int sock, uint32_t can_id, uint8_t *data, uint8_t len) { 
     struct can_frame frame; 
     if (len > CAN_MAX_DLEN) { 
        fprintf(stderr, "Data length too long (max %d bytes)\n", CAN_MAX_DLEN);
        return -1;
    }
    

    memset(&frame, 0, sizeof(frame));
    frame.can_id = can_id;     // Frame ID
    frame.can_dlc = len;       // Data length
    memcpy(frame.data, data, len);  // Data content
    
    // Transmit frame
    int nbytes = write(sock, &frame, sizeof(frame));
    if (nbytes != sizeof(frame)) {
        perror("CAN frame send failed");
        return -1;
    }
    
    printf("[%s] Sent CAN frame: ID 0x%08X, data: ", SEND_CAN, can_id);
    for (int i = 0; i < len; i++) {
        printf("%02X ", data[i]);
    }
    printf("\n");
    
    return 0;
}

int main() {
    int send_sock;
    struct sockaddr_can send_addr;
    struct ifreq send_ifr;
    pid_t recv_pid; 

    //  Configure CAN interfaces for sender and receiver
    if (setup_can_interface(SEND_CAN) < 0) {
        return 1;
    }
    if (setup_can_interface(RECV_CAN) < 0) {
        return 1;
    }

    //  Create child process for receiving data
    recv_pid = fork();
    if (recv_pid < 0) {
        perror("Failed to create receive process");
        return 1;
    } else if (recv_pid == 0) {
        receive_can_messages();
    }

    // Ensure the receiving child process is started and ready
    usleep(100000);  // 100ms

    //  Initialize sender socket
    if ((send_sock = socket(PF_CAN, SOCK_RAW, CAN_RAW)) < 0) {
        perror("Send socket creation failed");
        kill(recv_pid, SIGTERM); 
        return 1;
    }
    
    // Specify CAN interface
    strcpy(send_ifr.ifr_name, SEND_CAN);
    if (ioctl(send_sock, SIOCGIFINDEX, &send_ifr) < 0) {
        perror("IOCTL for send interface failed");
        close(send_sock);
        kill(recv_pid, SIGTERM);
        return 1;
    }
    send_addr.can_family = AF_CAN;
    send_addr.can_ifindex = send_ifr.ifr_ifindex;
    if (bind(send_sock, (struct sockaddr *)&send_addr, sizeof(send_addr)) < 0) {
        perror("Send socket bind failed");
        close(send_sock);
        kill(recv_pid, SIGTERM);
        return 1;
    }

   // Send test CAN frame (ID 0x123, data 00.11.22.33.44.55.66.77)
    uint8_t test_data[] = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77};
    if (send_can_frame(send_sock, 0x12345678, test_data, sizeof(test_data)) < 0) {
        close(send_sock);
        kill(recv_pid, SIGTERM);
        return 1;
    }

    
    sleep(1);

    // Close sender socket and terminate receiving child process
    close(send_sock);
    kill(recv_pid, SIGTERM);  
    waitpid(recv_pid, NULL, 0); 

    printf("Program exited\n");
    return 0;
}

Compile: gcc can_example.c -o can_example

Test Run: ./can_example

4.3 Python Example

can_example.py: 

import time
import threading
import subprocess
import can

# Configure constants
SEND_CAN = "can0"    # CAN interface for sending data
RECV_CAN = "can1"    # CAN interface for receiving data
BITRATE = 500000     # Baud rate 500k
SAMPLE_POINT = 0.8   # Sample point
TX_QUEUE_LEN = 4096  # Transmit queue length

class CANExample:
    def __init__(self):
        self.send_bus = None
        self.recv_bus = None
        self.receive_thread = None
        self.running = False
        
    def setup_can_interface(self, ifname):
        """Configure CAN interface"""
        print(f"Configuring CAN interface {ifname}...")
        
        try:
            # Close interface
            cmd = f"sudo ip link set {ifname} down"
            result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
            
            # Set CAN parameters
            cmd = f"sudo ip link set {ifname} type can bitrate {BITRATE} sample-point {SAMPLE_POINT}"
            result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
            if result.returncode != 0:
                print(f"Failed to configure CAN interface {ifname}: {result.stderr}")
                return -1
            
            # Set TX queue length
            queue_file = f"/sys/class/net/{ifname}/tx_queue_len"
            try:
                with open(queue_file, 'w') as fp:
                    fp.write(str(TX_QUEUE_LEN))
            except Exception as e:
                print(f"Fail to set TX queue length: {e}")
                return -1
            
            # Enable interface
            cmd = f"sudo ip link set {ifname} up"
            result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
            if result.returncode != 0:
                print(f"Failed to enable CAN interface {ifname}: {result.stderr}")
                return -1
            
            print(f"CAN interface {ifname} configured successfully:")
            print(f"  Baud rate: {BITRATE}")
            print(f"  Sample point: {SAMPLE_POINT}")
            print(f"  Transmit queue length: {TX_QUEUE_LEN}")
            
            return 0
            
        except Exception as e:
            print(f"An error occurred while configuring CAN interface {ifname}: {e}")
            return -1
    
    def setup_can_interfaces(self):
        """Configure sending and receiving CAN interfaces"""
        # Configure CAN interfaces for sender and receiver
        if self.setup_can_interface(SEND_CAN) < 0:
            return False
        if self.setup_can_interface(RECV_CAN) < 0:
            return False
        
        print("CAN interface configured successfully")
        return True
        
    def receive_can_messages(self):
        """Thread function for receiving CAN messages"""
        try:
            # Create receiving bus
            self.recv_bus = can.Bus(interface='socketcan', channel=RECV_CAN, bitrate=BITRATE)
            print(f"Receiving process started (listening on {RECV_CAN})...")
            
            while self.running:
                # Set timeout to avoid blocking
                msg = self.recv_bus.recv(timeout=1.0)
                if msg is not None:
                    print(f"[{RECV_CAN}] Received CAN frame: ID 0x{msg.arbitration_id:08X}, "
                          f"DLC {msg.dlc}, data: ", end="")
                    for i, byte in enumerate(msg.data):
                        if i < msg.dlc: print(f"{byte:02X} ", end="") 
                    print() 
         except Exception as e: 
            print(f"Receive error: {e}")
         finally: 
            if self.recv_bus:
               self.recv_bus.shutdown() 
    def send_can_frame(self, can_id, data): 
        """Send CAN frame""" 
        if not self.send_bus: 
            print("Error: sending bus not initialized") 
            return False 
        
        if len(data) > 8:
            print(f"Error: data length too long (maximum 8 bytes)")
            return False
        
        try:
            # Create CAN message
            msg = can.Message(
                arbitration_id=can_id,
                data=data,
                is_extended_id=True
            )
            
            # Send message
            self.send_bus.send(msg)
            
            print(f"[{SEND_CAN}] Send CAN frame: ID 0x{can_id:08X}, data: ", end="")
            for byte in data:
                print(f"{byte:02X} ", end="")
            print()
            
            return True
            
        except Exception as e:
            print(f"Fail to send: {e}")
            return False
    
    def run(self):
        """Main running function"""
        print("CAN bus communication test program")
        print("=" * 30)
        
        # Configure CAN interface
        if not self.setup_can_interfaces():
            print("Failed to configure CAN interface, program exiting")
            return 1
        
        # Create receiving thread
        self.running = True
        self.receive_thread = threading.Thread(target=self.receive_can_messages)
        self.receive_thread.daemon = True
        self.receive_thread.start()
        
        # Wait for receiving thread to start
        time.sleep(0.1)
        
        try:
            # Initialize sending bus
            self.send_bus = can.Bus(interface='socketcan', channel=SEND_CAN, bitrate=BITRATE)
            print(f"Sending bus initialized (interface: {SEND_CAN})")
            
            # Send test CAN frame
            test_data = bytes([0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77])
            if not self.send_can_frame(0x12345678, test_data):
                return 1
            
            # Wait for message transmission to complete
            time.sleep(1)
            
        except Exception as e:
            print(f"Program error: {e}")
            return 1
        finally:
            # Clean up resources
            self.running = False
            
            if self.receive_thread and self.receive_thread.is_alive():
                self.receive_thread.join(timeout=2.0)
            
            if self.send_bus:
                self.send_bus.shutdown()
        
        print("Program exiting")
        return 0

def main():
    """Program entry point"""
    can_example = CANExample()
    return can_example.run()

if __name__ == "__main__":
    main()

First, install the required dependencies:   pip install python-can

Test Run:  python can_example.py

5. DI

EC301 has two DI interfaces; detection range: 5–28V

Hardware Interface IO Index IO Number Chip Group
DI-1 19 GPIO0_C3 gpiochip0
DI-2 21 GPIO0_C5 gpiochip0

 

5.1  Quick Test

di_example.sh:

#!/bin/bash

# Check whether a GPIO index has been provided
if [ -z "$1" ]; then
    echo "Usage: $0 "
    exit 1
fi

GPIO=$1
GPIO_PATH="/sys/class/gpio/gpio$GPIO"
EXPORT_PATH="/sys/class/gpio/export"

# Check whether the GPIO has already been exported
if [ ! -d "$GPIO_PATH" ]; then
    echo "Exporting GPIO $GPIO..."
    echo "$GPIO" > "$EXPORT_PATH"
    if [ $? -ne 0 ]; then
        echo "Failed to export GPIO $GPIO. Check permissions or if it's in use."
        exit 1
    fi
else
    echo "GPIO $GPIO is already exported."
fi

# Set GPIO direction to input
echo "in" > "$GPIO_PATH/direction"

echo "Read GPIO $GPIO value. Press Ctrl+C to stop."
# Start reading in a loop
while true; do
    cat "$GPIO_PATH/value"
    sleep 1
done

Test Run:  bash di_example.sh 19

Test by connecting the positive terminal of a 12V power supply to DI1 and the negative terminal to COM1; when 12V is powered, it prints 0; when not powered, it prints 1. For wiring details, refer to the specifications of each EC300 series product.

5.2 C Beispiel

di_example.c:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/gpio.h>
#include <errno.h>
#include <time.h>

#define GPIO_CHIP "/dev/gpiochip0"  // GPIO controller device
#define GPIO_LINE 19                // GPIO line number to test (DI interface)
#define SAMPLE_INTERVAL_1S 1   		// Sampling interval (seconds)
#define MAX_SAMPLES 100             // Maximum number of samples

int main() {
    struct gpiohandle_request req;
    struct gpiohandle_data data;
    int fd, ret;
    int sample_count = 0;
    struct timespec start_time, current_time;

    // Open GPIO character device
    fd = open(GPIO_CHIP, O_RDWR);
    if (fd < 0) {
        perror("Unable to open GPIO device");
        return EXIT_FAILURE;
    }

    // Set GPIO request
    memset(&req, 0, sizeof(req));
    req.lineoffsets[0] = GPIO_LINE;  // GPIO line number
    req.flags = GPIOHANDLE_REQUEST_INPUT;  // Set to input mode
    strcpy(req.consumer_label, "di-monitor");  // Consumer label
    req.lines = 1;                   // Number of GPIO lines to control

    // Request GPIO control
    ret = ioctl(fd, GPIO_GET_LINEHANDLE_IOCTL, &req);
    if (ret < 0) {
        perror("Unable to acquire GPIO control");
        close(fd);
        return EXIT_FAILURE;
    }

    // Close GPIO character device (line handle already acquired)
    close(fd);

    printf("Monitoring GPIO chip0 line %d (DI interface), sampling interval %.1f seconds...\n", 
           GPIO_LINE, SAMPLE_INTERVAL_1S/1000000.0);
    printf("Press Ctrl+C to stop monitoring\n\n");

    // Get start time
    clock_gettime(CLOCK_MONOTONIC, &start_time);

    // Continuously monitor GPIO input state
    while (sample_count < MAX_SAMPLES) {
        // Read current GPIO state
        ret = ioctl(req.fd, GPIOHANDLE_GET_LINE_VALUES_IOCTL, &data);
        if (ret < 0) {
            perror("Failed to read GPIO state");
            break;
        }

        // Get current time
        clock_gettime(CLOCK_MONOTONIC, &current_time);
        double elapsed = (current_time.tv_sec - start_time.tv_sec) + 
                        (current_time.tv_nsec - start_time.tv_nsec) / 1e9;

        // Print state and timestamp
        printf("Time: %.3fs - DI state: %d\n", elapsed, data.values[0]);
        
        sample_count++;
        sleep(SAMPLE_INTERVAL_1S);
    }

    // Close GPIO line handle
    close(req.fd);

    printf("Monitoring complete, %d samples collected\n", sample_count);
    return EXIT_SUCCESS;
}

Compile:  gcc di_example.c -o di_example

Test Run: ./di_example

5.3 Python Beispiel

di_example.py:

import time
import sys
from typing import Optional

# Constant definitions
GPIO_LINE: int = 19
SAMPLE_INTERVAL: float = 1.0
MAX_SAMPLES: int = 100

def gpio_read(gpio_line: int) -> Optional[int]:
    """Read the state value of the specified GPIO line
    
    Args:
        gpio_line: GPIO line number
        
    Returns:
        GPIO state value (0 or 1), returns None on error
    """
    try:
        # Ensure GPIO has been exported and set to input
        try:
            with open('/sys/class/gpio/export', 'w') as export_file:
                export_file.write(str(gpio_line))
            time.sleep(0.1)
        except IOError:
            pass  # GPIO may have been exported
        
        # Set direction to input
        with open(f'/sys/class/gpio/gpio{gpio_line}/direction', 'w') as direction_file:
            direction_file.write('in')
        
        # Read GPIO value
        with open(f'/sys/class/gpio/gpio{gpio_line}/value', 'r') as value_file:
            return int(value_file.read().strip())
            
    except (IOError, ValueError, FileNotFoundError) as error:
        print(f"GPIO read error: {error}")
        return None

def main() -> int:
    """Main function, execute GPIO monitoring task"""
    print(f"Monitoring GPIO line {GPIO_LINE}, interval {SAMPLE_INTERVAL} seconds...")
    print("Press Ctrl+C to stop\n")
    
    start_time: float = time.monotonic()
    sample_count: int = 0
    
    try:
        while sample_count < MAX_SAMPLES:
            target_time: float = start_time + sample_count * SAMPLE_INTERVAL
            
            # Precise wait
            current_time: float = time.monotonic()
            if current_time < target_time:
                time.sleep(target_time - current_time)
            
            # Read GPIO state
            gpio_value: Optional[int] = gpio_read(GPIO_LINE)
            
            if gpio_value is None:
                print("Failed to read GPIO state")
                break
            
            elapsed: float = time.monotonic() - start_time
            print(f"Time: {elapsed:.3f}s - DI state: {gpio_value}")
            
            sample_count += 1
    
    except KeyboardInterrupt:
        print("\nUser interrupted")
    
    finally:
        # Clean up GPIO
        try:
            with open('/sys/class/gpio/unexport', 'w') as unexport_file:
                unexport_file.write(str(GPIO_LINE))
        except (IOError, FileNotFoundError):
            pass
        
        print(f"\nMonitoring complete, {sample_count} samples collected")
    
    return 0

if __name__ == "__main__":
    sys.exit(main())

Test Run: python di_example.py

6. DO

EC301 has two DO interfaces, supporting 5A/DC12~24V output

Hardware Interface IO Index IO Number Chip Group
DO-1 20 GPIO0_C4 gpiochip0
DO-2 18 GPIO0_C2 gpiochip0

6.1 Quick Test

do_example.sh: 

#!/bin/bash

# Check whether a GPIO index has been provided
if [ -z "$1" ]; then
    echo "Usage: $0 "
    exit 1
fi

GPIO=$1
GPIO_PATH="/sys/class/gpio/gpio$GPIO"
EXPORT_PATH="/sys/class/gpio/export"

# Check whether the GPIO has already been exported
if [ ! -d "$GPIO_PATH" ]; then
    echo "Exporting GPIO $GPIO..."
    echo "$GPIO" > "$EXPORT_PATH"
    if [ $? -ne 0 ]; then
        echo "Failed to export GPIO $GPIO. Check permissions or if it's in use."
        exit 1
    fi
else
    echo "GPIO $GPIO is already exported."
fi

# Set GPIO direction to input
echo "out" > "$GPIO_PATH/direction"

# Set the initial value to low level
echo 0 > "$GPIO_PATH/value"

echo "Toggling GPIO $GPIO every 1 second. Press Ctrl+C to stop."

# Start toggling in a loop
while true; do
    echo 1 > "$GPIO_PATH/value"
    sleep 1
    echo 0 > "$GPIO_PATH/value"
    sleep 1
done

Test Run: bash do_example.sh 20

Test by connecting the positive terminal of an external load (e.g., LED) to COM1 and the negative terminal to DO1; after running the test program, the LED will blink on and off at intervals. For wiring details, refer to the specifications of each EC300 series product.

6.2 C Example

do_example.c:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/gpio.h>
#include <errno.h>
#include <time.h>
#include <signal.h>
#define GPIO_CHIP "/dev/gpiochip0"  // GPIO controller device
#define GPIO_LINE 20                // GPIO line number to test (DO interface)
#define INTERVAL_SEC 1              // On/off interval time (seconds)
#define MAX_CYCLES 100              // Maximum number of loops

volatile sig_atomic_t stop_flag = 0;

void handle_signal(int sig) {
    stop_flag = 1;
}

int main() {
    struct gpiohandle_request req;
    struct gpiohandle_data data;
    int fd, ret;
    int cycle_count = 0;
    struct timespec start_time, current_time;

    // Register signal handler
    signal(SIGINT, handle_signal);
    signal(SIGTERM, handle_signal);

    // Open GPIO character device
    fd = open(GPIO_CHIP, O_RDWR);
    if (fd < 0) {
        perror("Unable to open GPIO device");
        return EXIT_FAILURE;
    }

    // Set GPIO request
    memset(&req, 0, sizeof(req));
    req.lineoffsets[0] = GPIO_LINE;  // GPIO line number
    req.flags = GPIOHANDLE_REQUEST_OUTPUT;  // Set to input mode
    req.default_values[0] = 0;       // Set the initial value to low level
    strcpy(req.consumer_label, "do-control");  // Consumer label
    req.lines = 1;                   // Number of GPIO lines to control

    // Request GPIO control
    ret = ioctl(fd, GPIO_GET_LINEHANDLE_IOCTL, &req);
    if (ret < 0) {
        perror("Unable to acquire GPIO control");
        close(fd);
        return EXIT_FAILURE;
    }

    // Close GPIO character device (line handle already acquired)
    close(fd);

    printf("Monitoring GPIO chip0 line %d (DO interface), sampling interval %.1f seconds...\n\n", 
           GPIO_LINE, INTERVAL_SEC);
    printf("Press Ctrl+C to stop monitoring\n\n");

    // Get start time
    clock_gettime(CLOCK_MONOTONIC, &start_time);

    // Loop control DO interface on/off
    while (!stop_flag && cycle_count < MAX_CYCLES) {
        // Get current time
        clock_gettime(CLOCK_MONOTONIC, &current_time);
        double elapsed = (current_time.tv_sec - start_time.tv_sec) + 
                        (current_time.tv_nsec - start_time.tv_nsec) / 1e9;

        // Toggle state
        int state = cycle_count % 2;
        data.values[0] = state;
        
        ret = ioctl(req.fd, GPIOHANDLE_SET_LINE_VALUES_IOCTL, &data);
        if (ret < 0) {
            perror("Failed to set GPIO state");
            break;
        }

        printf("Time: %.3fs - Set DO state: %s\n", 
               elapsed, state ? "High (ON)" : "Low (OFF)");
        
        cycle_count++;
        sleep(INTERVAL_SEC);
    }

    // Ensure final state is low level
    data.values[0] = 0;
    ioctl(req.fd, GPIOHANDLE_SET_LINE_VALUES_IOCTL, &data);

    // Close GPIO line handle
    close(req.fd);

    printf("\nControl completed, %d on/off cycles executed\n", cycle_count);
    return EXIT_SUCCESS;
}

Compile: gcc do_example.c -o do_example

Test Run: ./do_example

6.3 Python Example

do_example.py:

import os
import time
import signal
import sys
from typing import NoReturn, Optional

# Constant definitions
GPIO_LINE: int = 20
INTERVAL_SEC: float = 1.0
MAX_CYCLES: int = 100

stop_flag: bool = False

def handle_signal(sig: int, frame) -> None:
    """Signal handler function, set stop flag"""
    global stop_flag
    stop_flag = True

def gpio_control(gpio_line: int, value: bool) -> bool:
    """Control the output state of the specified GPIO line
    
    Args:
        gpio_line: GPIO line number
        value: Output value (True for high level, False for low level)
        
    Returns:
        Returns True on success, False on failure
    """
    try:
        # Ensure GPIO has been exported
        try:
            with open('/sys/class/gpio/export', 'w') as export_file:
                export_file.write(str(gpio_line))
            time.sleep(0.1)
        except IOError:
            pass  # GPIO may have been exported
        
        # Set direction to output
        with open(f'/sys/class/gpio/gpio{gpio_line}/direction', 'w') as direction_file:
            direction_file.write('out')
        
        # Set GPIO value
        with open(f'/sys/class/gpio/gpio{gpio_line}/value', 'w') as value_file:
            value_file.write('1' if value else '0')
        
        return True
    except (IOError, FileNotFoundError) as error:
        print(f"GPIO control error: {error}")
        return False

def main() -> int:
    """Main function, execute GPIO control task"""
    global stop_flag
    
    # Register signal handler
    signal.signal(signal.SIGINT, handle_signal)
    signal.signal(signal.SIGTERM, handle_signal)
    
    print(f"Control GPIO line {GPIO_LINE},interval {INTERVAL_SEC} seconds...")
    print("Press Ctrl+C to stop\n")
    
    start_time: float = time.monotonic()
    cycle_count: int = 0
    
    try:
        while not stop_flag and cycle_count < MAX_CYCLES:
            target_time: float = start_time + cycle_count * INTERVAL_SEC
            
            # Precise wait
            current_time: float = time.monotonic()
            if current_time < target_time:
                time.sleep(target_time - current_time)
            
            state: bool = bool(cycle_count % 2)
            
            if not gpio_control(GPIO_LINE, state):
                break
            
            elapsed: float = time.monotonic() - start_time
            status: str = "High (ON)" if state else "Low (OFF)"
            print(f"Time: {elapsed:.3f}s - DO state: {status}")
            
            cycle_count += 1
    
    except KeyboardInterrupt:
        print("\n User interrupted")
    
    finally:
        # Ensure final state is low level
        gpio_control(GPIO_LINE, False)
        print(f"\n Control completed, {cycle_count} on/off cycles executed")
    
    return 0

if __name__ == "__main__":
    sys.exit(main())

Test Run: python do_example.py

7. Buttons

EC300 Product Series – Buttons: Managed by internal programs, not user-programmable, used for restarting or resetting the device via web page configuration.

Hardware Interface IO Index IO Number Chip Group Bemerkungen
Button 0 GPIO0_A0 gpiochip0 Default system reset/restart button
  • Restart: press 1s and release
  • Reset + Restart: press >5s

8. Hardware Watchdog

All EC300 products have the hardware watchdog enabled by default, managed by internal programs.

Hardware Interface IO Index IO Number Chip Group Bemerkungen
Enable Control 113 GPIO3_C1 gpiochip3 Default system reset/restart button
Watchdog IO 8

GPIO0_B0

gpiochip0

Recommended watchdog feeding period: <30s

Note: If the internal programs are stopped, you must take over the watchdog feeding service yourself; otherwise, the device will reboot periodically.

9. Netzwerkanschlüsse

The network ports are divided into WAN + LAN, managed by internal network programs; configuration is required via the web interface. For details, see the EC Quick Start Guide.

10. Cellular Wireless

The EC300 product series supports 4G/5G communication (5G requires purchasing the 5G version; for details, please contact customer support). Module recognition and dialing are managed by internal programs, so users do not need to worry. See the EC Quick Start Guide.

11. Wi-Fi

The EC300 product series supports Wi-Fi hotspot and Wi-Fi client modes; supports 2.4GHz and 5GHz bands. Wi-Fi is managed by internal programs, so users do not need to worry. For details, see: EC Quick Start Guide.

12. Bluetooth

The EC300 product series supports Bluetooth functionality.

12.1 Quick Test

# Enter the shell Bluetooth interactive mode
bluetoothctl

# In the interactive mode, execute the following commands:
# Check the Bluetooth controller (adapter) status

# If the adapter is not enabled, turn it on
power on

# Enable discoverable mode (allow other devices to find this device)
discoverable on

# Enable pairable mode
pairable on

# Start scanning (continuous scanning, press Ctrl+C to stop)
scan on

# For pairing a Bluetooth device (e.g., headset, MAC address AA:BB:CC:DD:EE:FF) as an example:
# Execute in bluetoothctl
# Pair the device
pair AA:BB:CC:DD:EE:FF

# Trust the device (optional, to avoid repeated verification later)
trust AA:BB:CC:DD:EE:FF

# Connect the device
connect AA:BB:CC:DD:EE:FF

# If pairing requires a PIN code, enter it as prompted (usually 0000 or 1234, or the code provided in the device manual)
# After successful connection, you can view device details with info AA:BB:CC:DD:EE:FF
info AA:BB:CC:DD:EE:FF

13. Audio

The EC300 product series supports a 3.5mm headset jack.

13.1 Quick Test

Connect a 3.5mm headset or speaker device to the HP port of the device; create a script file as follows:

hp_example.sh:

#!/bin/bash

# Set the playback device to HP (HEADPHONE OUT)
amixer -c 0 cset name='Playback Path' 'HP'

# Set the playback volume to 200, range 0–255
amixer -c 0 cset name='Playback Volume' 200

# Play a sample audio file
aplay /run/media/mmcblk0p9/iotrouter.wav

Test Run: bash hp_example.sh

 

14. Display

Hardware Interface Auflösung EC300 EC300P EC301
HDMI 1920×1080 ✔️ ✔️
LVDS 1280×800 ✔️

14.1 LVDS Screen

Supports backlight brightness adjustment, adjustment range [0–255]:

echo 128 > /sys/class/backlight/backlight/brightness

echo 255 > /sys/class/backlight/backlight/brightness

Control screen display:

echo off > /sys/class/drm/card0-LVDS-1/status

echo on > /sys/class/drm/card0-LVDS-1/status

System API Interface

1. Get basic device information

  • URL: GET http://{device IP}/rpc-api/data/devinfo
  • Method: GET
  • Request parameters: None

Response data description:

Field Field Name Data Type Beschreibung
code Response Status Code int Always returns 200, indicates request success
data.model Device Model string Device model
data.sn Device Serial Number string Device serial number
data.version Device Version string Device software version

Response Example:


{
    "code": 200,
    "data": {
        "model": "EC100",
        "sn": "4310AA01C8AA2B6B",
        "version": "3802"
    }
}

2. Get Device WWAN Network Information

  • URL: GET http://{device IP}/rpc-api/data/wwaninfo
  • Method: GET
  • Request Parameters: None

Response Data Description: 

Field Field Name Data Type Beschreibung
code Response Status Code int Always returns 200, indicates request success
data.ip 4G IP string IP address obtained via 4G
data.mask Subnet Mask string Network subnet mask
data.dns DNS Server IP string DNS resolver server address
data.signal Signal Strength int 4G signal strength
data.ccid SIM Card Number string SIM card number
data.imei IMEI string Device 4G module IMEI
data.netInfo Netzwerktyp string Current network type
data.apn.addr APN Address string Access Point Name (APN) address
data.apn.username APN Username string APN authentication username
data.apn.password APN Password string APN authentication password

Response Example:


{
    "code": 200,
    "data": {
        "ip": "37.64.21.99",
        "mask": "255.255.255.255",
        "dns": "224.134.125.33",
        "signal": 90,
        "ccid": "89860480192470076526",
        "imei": "862701080346307",
        "netInfo": "LTE",
        "apn": {
            "addr": "",
            "username": "",
            "password": ""
        }
    }
}

Software Applications

1. Internal Management Program

To simplify user operation, the EC300 product series comes with a built-in management program, iotrouter, which starts automatically on boot. The management program includes, but is not limited to, the following functions:

  • Device initialization
  • Verwaltung des Netzes
  • Watchdog
  • Key/button monitoring
  • Firewall
  • Device configuration service (default port 80, listening port can be changed; file: /usr/local/src/iotrouter/web/user-config.js)
  • User file browsing function (default path /home; path can be changed; file: /usr/local/src/iotrouter/web/user-config.js)

Note: The internal program must be kept running; otherwise, the device may not operate normally on startup. If the user must disable the internal management program, they must take over the above management services themselves.

2. NeuronEX

The EC300 product series comes with NeuronEX-Lite pre-installed. The service starts automatically on boot and listens on port 8085. For more details, see: Learning Path

  • Check service status: systemctl status neuronex
  • Restart service: systemctl restart neuronex
  • Stop service: systemctl stop neuronex
  • Disable service auto-start: systemctl disable neuronex

3. Node-Red

The EC300 product series comes with Node-Red pre-installed. The service starts automatically on boot and listens on port 1880. For more details, see: Learning Path

  • Check service status: systemctl status node-red
  • Restart service: systemctl restart node-red
  • Stop service: systemctl stop node-red
  • Disable service auto-start: systemctl disable node-red

4. FUXA

The EC300 product series comes with FUXA pre-installed. The service starts automatically on boot and listens on port 1881. For more details, see: Advanced Development (to be added later).

5. Xfce4 Desktop

The default desktop environment is Xfce4; this is just an example. If users need to display other interfaces, such as a QT interface, they can develop it themselves.
The display manager for the desktop is: lightdm

  • Check service status: systemctl status lightdm
  • Restart service: systemctl restart lightdm
  • Stop service: systemctl stop lightdm
  • Disable service auto-start: systemctl disable lightdm

6. User-Developed Programs

Users are free to develop their own programs. User programs run independently, but memory and storage management must be considered to avoid system abnormalities. Software can be set to start automatically via:

  •  /etc/rc.local
  • systemd service
  • /etc/init.d system

We also provide software development and customization services. If needed, please contact our sales team.

7. Boot LOGO

Users can replace the system boot LOGO by themselves.

Logo Name Display Stage
logo.bmp Displayed during U-Boot startup
logo_kernel.bmp Displayed during kernel startup

7.1 Format Requirements

LOGO images must be in 24-bit BMP format, and the image resolution is recommended not to exceed the display resolution of HDMI or other monitors.

7.2 Replacing the LOGO

When the device powers on, copy the prepared logo.bmp und logo_kernel.bmp files to the “boot/” directory in the root filesystem of the evaluation board, replacing the existing logo.bmp und logo_kernel.bmp files in the directory.

Note: The names of the logo files must not be changed.

Kontakt