├── ftd2xx └── Static │ ├── i386 │ └── ftd2xx.lib │ └── amd64 │ └── ftd2xx.lib ├── u_strlen.h ├── .gitignore ├── u_strlen.c ├── u_mem.h ├── README-RPM.md ├── net_sock.c ├── Dockerfile ├── u_mem.c ├── net.h ├── gcfflasher.spec ├── buffer_helper.h ├── protocol.h ├── net_sock.h ├── LICENSE.txt ├── u_bstream.h ├── .github └── workflows │ └── build-linux.yml ├── toolchains ├── mingw-w64-i686.cmake └── mingw-w64-x86_64.cmake ├── posix_libftdi_reset.c ├── buffer_helper.c ├── u_sstream.h ├── net.c ├── protocol.c ├── u_bstream.c ├── README.md ├── gcf.h ├── net_udp_win32.c ├── macos_get_usb_devices.c ├── CMakeLists.txt ├── net_udp_posix.c ├── linux_libgpiod_v1_reset.c ├── main_dos.c ├── linux_libgpiod_v2_reset.c ├── linux_get_usb_devices.c ├── main_posix.c ├── u_sstream.c └── main_windows.c /ftd2xx/Static/i386/ftd2xx.lib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dresden-elektronik/gcfflasher/HEAD/ftd2xx/Static/i386/ftd2xx.lib -------------------------------------------------------------------------------- /ftd2xx/Static/amd64/ftd2xx.lib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dresden-elektronik/gcfflasher/HEAD/ftd2xx/Static/amd64/ftd2xx.lib -------------------------------------------------------------------------------- /u_strlen.h: -------------------------------------------------------------------------------- 1 | #ifndef U_STRLEN_H 2 | #define U_STRLEN_H 3 | 4 | unsigned U_strlen(const char *p); 5 | 6 | #endif /* U_STRLEN_H */ 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.obj 2 | *.GCF 3 | *.exe 4 | GCFFlasher 5 | .idea 6 | *.dSYM/ 7 | build*/ 8 | debout/ 9 | cmake-build* 10 | linux-cross.Dockerfile 11 | -------------------------------------------------------------------------------- /u_strlen.c: -------------------------------------------------------------------------------- 1 | 2 | unsigned U_strlen(const char *p) 3 | { 4 | unsigned len; 5 | 6 | for (len = 0; p[len]; len++) 7 | {} 8 | 9 | return len; 10 | } 11 | -------------------------------------------------------------------------------- /u_mem.h: -------------------------------------------------------------------------------- 1 | #ifndef U_MEM_H 2 | #define U_MEM_H 3 | 4 | void U_memset(void *mem, int c, unsigned long n); 5 | void U_bzero(void *mem, unsigned long n); 6 | 7 | void *U_memcpy(void *dst, const void *src, unsigned long n); 8 | 9 | #endif /* U_MEM_H */ 10 | -------------------------------------------------------------------------------- /README-RPM.md: -------------------------------------------------------------------------------- 1 | # Creating the RPM packages 2 | Tested with Fedora-40 and RHEL(or it's clones)-9 3 | ## Requirements 4 | - mock 5 | - rpmbuild 6 | ## Build 7 | **Without unpacking the archive!!!** 8 | 1. rpmbuild -ts gcfflasher-.tar.gz 9 | 2. mock -r 10 | -------------------------------------------------------------------------------- /net_sock.c: -------------------------------------------------------------------------------- 1 | 2 | #include "net_sock.h" 3 | 4 | int SOCK_Init(void) 5 | { 6 | return 0; 7 | } 8 | 9 | void SOCK_Free(void) 10 | { 11 | 12 | } 13 | 14 | int SOCK_GetHostAF(const char *host) 15 | { 16 | if (host) 17 | { 18 | for (;*host; host++) 19 | { 20 | if (*host == ':') 21 | return S_AF_IPV6; 22 | 23 | if (*host == '.') 24 | return S_AF_IPV4; 25 | } 26 | } 27 | 28 | return S_AF_UNKNOWN; 29 | } -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:18.04 as builder 2 | 3 | RUN apt-get update && \ 4 | apt-get install --no-install-recommends -y \ 5 | lsb-release \ 6 | ca-certificates \ 7 | build-essential \ 8 | pkg-config \ 9 | libgpiod-dev \ 10 | dpkg-dev \ 11 | fakeroot \ 12 | cmake 13 | 14 | WORKDIR /src 15 | 16 | COPY . /src/gcfflasher/ 17 | 18 | RUN mkdir /src/build && cd /src/build \ 19 | && cmake -DCMAKE_BUILD_TYPE=Release ../gcfflasher \ 20 | && cmake --build . \ 21 | && cpack -G "DEB;TGZ" . 22 | 23 | FROM scratch 24 | WORKDIR /src 25 | COPY --from=builder /src/build ./ 26 | -------------------------------------------------------------------------------- /u_mem.c: -------------------------------------------------------------------------------- 1 | 2 | 3 | void U_memset(void *mem, int c, unsigned long n) 4 | { 5 | unsigned char *m; 6 | 7 | c &= 0xFF; 8 | m = (unsigned char*)mem; 9 | 10 | for (;n; n--, m++) 11 | *m = (unsigned char)c; 12 | } 13 | 14 | void U_bzero(void *mem, unsigned long n) 15 | { 16 | U_memset(mem, 0, n); 17 | } 18 | 19 | void *U_memcpy(void *dst, const void *src, unsigned long n) 20 | { 21 | unsigned char *d; 22 | unsigned char *s; 23 | 24 | d = (unsigned char*)dst; 25 | s = (unsigned char*)src; 26 | 27 | for (;n; n--) 28 | { 29 | *d = *s; 30 | d++; 31 | s++; 32 | } 33 | 34 | return (void*)d; 35 | } 36 | -------------------------------------------------------------------------------- /net.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021-2023 dresden elektronik ingenieurtechnik gmbh. 3 | * All rights reserved. 4 | * 5 | * The software in this package is published under the terms of the BSD 6 | * style license a copy of which has been included with this distribution in 7 | * the LICENSE.txt file. 8 | * 9 | */ 10 | 11 | #ifndef NET_H 12 | #define NET_H 13 | 14 | int NET_Init(const char *interface, unsigned short port); 15 | int NET_Step(void); 16 | void NET_Exit(void); 17 | 18 | /* callback implemented in gcf.c */ 19 | void NET_Received(int client_id, const unsigned char *buf, unsigned bufsize); 20 | 21 | #endif /* NET_H */ 22 | -------------------------------------------------------------------------------- /gcfflasher.spec: -------------------------------------------------------------------------------- 1 | Name: gcfflasher 2 | Version: 4.5.2 3 | Release: 1%{?dist} 4 | Summary: Updater for the firmware of Zigbee devices 5 | License: BSD 6 | URL: https://github.com/dresden-elektronik/gcfflasher/ 7 | Suggests: %{name}-old-devices 8 | Source0: %{name}-%{version}.tar.gz 9 | BuildRequires: cmake gcc-c++ 10 | BuildRequires: pkgconfig(libgpiod) 11 | 12 | %description 13 | GCFFlasher is the tool to program the firmware of dresden elektronik 14 | Zigbee products. 15 | 16 | %package old-devices 17 | Requires: %{name}%{?_isa} = %{version}-%{release} 18 | Requires: libgpiod 19 | Summary: Add support for older devices 20 | %description old-devices 21 | Older devices are only supported using libgpiod via dlopen(). 22 | 23 | %prep 24 | %autosetup 25 | 26 | %build 27 | %cmake 28 | %cmake_build 29 | 30 | %install 31 | %cmake_install 32 | 33 | %files 34 | %doc README.md 35 | %license LICENSE.txt 36 | %{_bindir}/GCFFlasher 37 | 38 | %files old-devices 39 | -------------------------------------------------------------------------------- /buffer_helper.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021-2023 dresden elektronik ingenieurtechnik gmbh. 3 | * All rights reserved. 4 | * 5 | * The software in this package is published under the terms of the BSD 6 | * style license a copy of which has been included with this distribution in 7 | * the LICENSE.txt file. 8 | * 9 | */ 10 | 11 | #ifndef BUF_HELPER_H 12 | #define BUF_HELPER_H 13 | 14 | unsigned char *put_u8_le(unsigned char *out, const unsigned char *in); 15 | unsigned char *put_u16_le(unsigned char *out, const unsigned short *in); 16 | unsigned char *put_u32_le(unsigned char *out, const unsigned long *in); 17 | /*unsigned char *put_u64_le(unsigned char *out, const uint64_t *in);*/ 18 | const unsigned char *get_u8_le(const unsigned char *in, unsigned char *out); 19 | const unsigned char *get_u16_le(const unsigned char *in, unsigned short *out); 20 | const unsigned char *get_u32_le(const unsigned char *in, unsigned long *out); 21 | /*const unsigned char *get_u64_le(const unsigned char *in, uint64_t *out);*/ 22 | 23 | #endif /* BUF_HELPER_H */ 24 | -------------------------------------------------------------------------------- /protocol.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021-2023 dresden elektronik ingenieurtechnik gmbh. 3 | * All rights reserved. 4 | * 5 | * The software in this package is published under the terms of the BSD 6 | * style license a copy of which has been included with this distribution in 7 | * the LICENSE.txt file. 8 | * 9 | */ 10 | 11 | #ifndef PROTOCOL_H 12 | #define PROTOCOL_H 13 | 14 | typedef struct { 15 | unsigned bufpos; 16 | unsigned short crc; 17 | unsigned char escaped; 18 | unsigned char buf[256]; 19 | } PROT_RxState; 20 | 21 | /* Platform independent declarations. */ 22 | void PROT_SendFlagged(const unsigned char *data, unsigned len); 23 | void PROT_ReceiveFlagged(PROT_RxState *rx, const unsigned char *data, unsigned len); 24 | void PROT_Packet(const unsigned char *data, unsigned len); 25 | 26 | /*! Platform specific declarations. 27 | Following functions need to be implemented in the platform layer. 28 | */ 29 | int PROT_Write(const unsigned char *data, unsigned len); 30 | int PROT_Putc(unsigned char ch); 31 | int PROT_Flush(void); 32 | 33 | 34 | #endif /* PROTOCOL_H */ 35 | -------------------------------------------------------------------------------- /net_sock.h: -------------------------------------------------------------------------------- 1 | #ifndef NET_SOCK_H 2 | #define NET_SOCK_H 3 | 4 | #define S_AF_UNKNOWN 0 5 | #define S_AF_IPV4 4 6 | #define S_AF_IPV6 6 7 | #define S_UDP_MAX_PKG_SIZE 1280 8 | 9 | #ifdef PL_WIN 10 | typedef unsigned long long S_Handle; 11 | #else 12 | typedef int S_Handle; 13 | #endif 14 | 15 | typedef enum S_UdpState 16 | { 17 | S_UDP_STATE_INIT = 0, 18 | S_UDP_STATE_OPEN = 1, 19 | S_UDP_STATE_ERROR = 2 20 | } S_UdpState; 21 | 22 | typedef struct S_Addr 23 | { 24 | unsigned char data[16]; 25 | unsigned char af; 26 | } S_Addr; 27 | 28 | typedef struct S_Udp 29 | { 30 | S_Addr addr; 31 | S_Addr peer_addr; 32 | unsigned short peer_port; 33 | S_Handle handle; 34 | S_UdpState state; 35 | unsigned short port; 36 | } S_Udp; 37 | 38 | int SOCK_Init(); 39 | void SOCK_Free(); 40 | 41 | int SOCK_GetHostAF(const char *host); 42 | 43 | int SOCK_UdpInit(S_Udp *udp, int af); 44 | int SOCK_UdpSetPeer(S_Udp *udp, const char *peer, unsigned short port); 45 | int SOCK_UdpBind(S_Udp *udp, unsigned short port); 46 | int SOCK_UdpJoinMulticast(S_Udp *udp, const char *maddr); 47 | int SOCK_UdpSend(S_Udp *udp, unsigned char *buf, unsigned bufsize); 48 | int SOCK_UdpRecv(S_Udp *udp, unsigned char *buf, unsigned bufsize); 49 | void SOCK_UdpFree(S_Udp *udp); 50 | 51 | #endif /* NET_SOCK_H */ 52 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2021, dresden elektronik ingenieurtechnik gmbh 2 | 3 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 4 | 5 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 6 | 7 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 8 | 9 | 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 10 | 11 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 12 | -------------------------------------------------------------------------------- /u_bstream.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 Manuel Pietschmann. 3 | * All rights reserved. 4 | * 5 | * The software in this package is published under the terms of the BSD 6 | * style license a copy of which has been included with this distribution in 7 | * the LICENSE.txt file. 8 | * 9 | */ 10 | 11 | #ifndef U_BSTREAM_H 12 | #define U_BSTREAM_H 13 | 14 | /* byte stream */ 15 | 16 | typedef enum 17 | { 18 | U_BSTREAM_OK, 19 | U_BSTREAM_READ_PAST_END, 20 | U_BSTREAM_WRITE_PAST_END, 21 | U_BSTREAM_NOT_INITIALISED 22 | } U_BStreamStatus; 23 | 24 | typedef struct U_BStream 25 | { 26 | unsigned char *data; 27 | unsigned long pos; 28 | unsigned long size; 29 | U_BStreamStatus status; 30 | } U_BStream; 31 | 32 | void U_bstream_init(U_BStream *bs, void *data, unsigned long size); 33 | void U_bstream_put_u8(U_BStream *bs, unsigned char v); 34 | void U_bstream_put_u16_le(U_BStream *bs, unsigned short v); 35 | void U_bstream_put_u32_le(U_BStream *bs, unsigned long v); 36 | void U_bstream_put_u32_be(U_BStream *bs, unsigned long v); 37 | unsigned char U_bstream_get_u8(U_BStream *bs); 38 | unsigned short U_bstream_get_u16_le(U_BStream *bs); 39 | unsigned short U_bstream_get_u16_be(U_BStream *bs); 40 | unsigned long U_bstream_get_u32_le(U_BStream *bs); 41 | unsigned long U_bstream_get_u32_be(U_BStream *bs); 42 | 43 | /* 64-bit support */ 44 | #ifdef __STDC_VERSION__ 45 | #if __STDC_VERSION__ >= 199901L 46 | #define U_BSTREAM_HAS_LONG_LONG 47 | #endif 48 | #endif 49 | 50 | #endif /* U_BSTREAM_H */ 51 | -------------------------------------------------------------------------------- /.github/workflows/build-linux.yml: -------------------------------------------------------------------------------- 1 | name: Build Linux packages 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | tags: 7 | - '*' 8 | pull_request: 9 | branches: [ "main" ] 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | strategy: 15 | matrix: 16 | include: 17 | - platform: "linux/amd64" 18 | name: "amd64" 19 | - platform: "linux/arm64" 20 | name: "aarch64" 21 | - platform: "linux/arm/v7" 22 | name: "armhf" 23 | steps: 24 | - name: Checkout 25 | uses: actions/checkout@v3 26 | 27 | - name: Setup QEMU 28 | uses: docker/setup-qemu-action@v3 29 | 30 | - name: Setup Docker buildx 31 | uses: docker/setup-buildx-action@v3 32 | 33 | - name: Compile 34 | run: | 35 | docker buildx build \ 36 | -f Dockerfile \ 37 | --platform ${{ matrix.platform }} \ 38 | -o ${{ github.workspace }}/build \ 39 | . 40 | # - name: Upload artifact 41 | # uses: actions/upload-artifact@v3 42 | # with: 43 | # name: gcfflasher-${{ matrix.name }} 44 | # path: build/src/*.deb 45 | 46 | - name: Upload binaries to release 47 | if: startsWith(github.ref, 'refs/tags/v') 48 | uses: svenstaro/upload-release-action@v2 49 | with: 50 | repo_token: ${{ secrets.GITHUB_TOKEN }} 51 | release_name: ${{ github.ref_name }} 52 | file: build/src/*_linux_* 53 | tag: ${{ github.ref }} 54 | overwrite: true 55 | file_glob: true -------------------------------------------------------------------------------- /toolchains/mingw-w64-i686.cmake: -------------------------------------------------------------------------------- 1 | # Sample toolchain file for building for Windows from an Ubuntu Linux system. 2 | # 3 | # Typical usage: 4 | # *) install cross compiler: `sudo apt-get install mingw-w64` 5 | # *) cd build 6 | # *) cmake -DCMAKE_TOOLCHAIN_FILE=~/mingw-w64-x86_64.cmake .. 7 | # This is free and unencumbered software released into the public domain. 8 | 9 | set(CMAKE_SYSTEM_NAME Windows) 10 | set(TOOLCHAIN_PREFIX i686-w64-mingw32) 11 | 12 | # cross compilers to use for C, C++ and Fortran 13 | set(CMAKE_C_COMPILER ${TOOLCHAIN_PREFIX}-gcc) 14 | set(CMAKE_CXX_COMPILER ${TOOLCHAIN_PREFIX}-g++) 15 | set(CMAKE_Fortran_COMPILER ${TOOLCHAIN_PREFIX}-gfortran) 16 | set(CMAKE_RC_COMPILER ${TOOLCHAIN_PREFIX}-windres) 17 | 18 | # target environment on the build host system 19 | if (EXISTS /usr/${TOOLCHAIN_PREFIX}) 20 | set(CMAKE_FIND_ROOT_PATH /usr/${TOOLCHAIN_PREFIX}) 21 | elseif (EXISTS "/opt/${TOOLCHAIN_PREFIX}") 22 | set(CMAKE_FIND_ROOT_PATH /opt/${TOOLCHAIN_PREFIX}) 23 | else() 24 | message(FATAL "couldn't find root dir") 25 | endif() 26 | 27 | # modify default behavior of FIND_XXX() commands 28 | set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) 29 | set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) 30 | set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) 31 | 32 | set(CMAKE_C_FLAGS "") 33 | set(CMAKE_EXE_LINKER_FLAGS "") 34 | 35 | set(CMAKE_C_FLAGS_RELEASE_INIT "") 36 | set(CMAKE_EXE_LINKER_FLAGS_RELEASE_INIT "") 37 | 38 | set(CMAKE_C_FLAGS_RELEASE "-O2 -DNDEBUG -flto -fno-builtin -nostdlib -mconsole -Xlinker --stack=0x200000,0x200000") 39 | set(CMAKE_EXE_LINKER_FLAGS_RELEASE "-Wl,--gc-sections -s") 40 | 41 | set(CMAKE_C_FLAGS_DEBUG "--entry=mainCRTStartup -O0 -g -fno-builtin -nostdlib -mconsole -Xlinker --stack=0x200000,0x200000") 42 | set(CMAKE_EXE_LINKER_FLAGS_DEBUG "-Wl,--gc-sections") 43 | 44 | -------------------------------------------------------------------------------- /posix_libftdi_reset.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 dresden elektronik ingenieurtechnik gmbh. 3 | * All rights reserved. 4 | * 5 | * The software in this package is published under the terms of the BSD 6 | * style license a copy of which has been included with this distribution in 7 | * the LICENSE.txt file. 8 | * 9 | */ 10 | 11 | #ifdef HAS_LIBFTDI 12 | 13 | #include 14 | 15 | int plResetLibFtdi() 16 | { 17 | int ret; 18 | struct ftdi_context *ftdi; 19 | 20 | if ((ftdi = ftdi_new()) == NULL) 21 | { 22 | fprintf(stderr, "ftdi_new failed\n"); 23 | return -1; 24 | } 25 | 26 | ftdi->module_detach_mode = AUTO_DETACH_REATACH_SIO_MODULE; 27 | 28 | ret = ftdi_usb_open(ftdi, 0x0403, 0x6015); 29 | if (ret < 0 && ret != -5) 30 | { 31 | fprintf(stderr, "unable to open ftdi device: %d (%s)\n", ret, ftdi_get_error_string(ftdi)); 32 | ftdi_deinit(ftdi); 33 | return -2; 34 | } 35 | 36 | uint8_t bitmask[3] = { 0xf1, 0xf0, 0xf1 }; 37 | 38 | for (uint8_t i = 0 ; i < sizeof(bitmask); i++) 39 | { 40 | ret = ftdi_set_bitmode(ftdi, bitmask[i], BITMODE_CBUS); 41 | if (ret < 0) 42 | { 43 | fprintf(stderr, "set_bitmode failed for 0x%x, error %d (%s)\n", bitmask[i], ret, ftdi_get_error_string(ftdi)); 44 | break; 45 | } 46 | 47 | PL_MSleep(10); 48 | 49 | // read CBUS 50 | uint8_t buf; 51 | ret = ftdi_read_pins(ftdi, &buf); 52 | if (ret < 0) 53 | { 54 | fprintf(stderr, "read_pins failed, error %d (%s)\n", ret, ftdi_get_error_string(ftdi)); 55 | continue; 56 | } 57 | printf("read returned 0x%02x\n", buf); 58 | } 59 | 60 | // ftdi_set_bitmode(ftdi, 0, 0x00); // RESET 61 | 62 | 63 | printf("disabling bitbang mode\n"); 64 | ftdi_disable_bitbang(ftdi); 65 | 66 | ftdi_usb_close(ftdi); 67 | ftdi_free(ftdi); 68 | 69 | return 0; 70 | } 71 | #endif 72 | -------------------------------------------------------------------------------- /toolchains/mingw-w64-x86_64.cmake: -------------------------------------------------------------------------------- 1 | # Sample toolchain file for building for Windows from an Ubuntu Linux system. 2 | # 3 | # Typical usage: 4 | # *) install cross compiler: `sudo apt-get install mingw-w64` 5 | # *) cd build 6 | # *) cmake -DCMAKE_TOOLCHAIN_FILE=~/mingw-w64-x86_64.cmake .. 7 | # This is free and unencumbered software released into the public domain. 8 | 9 | set(CMAKE_SYSTEM_NAME Windows) 10 | set(TOOLCHAIN_PREFIX x86_64-w64-mingw32) 11 | 12 | # cross compilers to use for C, C++ and Fortran 13 | set(CMAKE_C_COMPILER ${TOOLCHAIN_PREFIX}-gcc) 14 | set(CMAKE_CXX_COMPILER ${TOOLCHAIN_PREFIX}-g++) 15 | set(CMAKE_Fortran_COMPILER ${TOOLCHAIN_PREFIX}-gfortran) 16 | set(CMAKE_RC_COMPILER ${TOOLCHAIN_PREFIX}-windres) 17 | 18 | # target environment on the build host system 19 | if (EXISTS /usr/${TOOLCHAIN_PREFIX}) 20 | set(CMAKE_FIND_ROOT_PATH /usr/${TOOLCHAIN_PREFIX}) 21 | elseif (EXISTS "/opt/${TOOLCHAIN_PREFIX}") 22 | set(CMAKE_FIND_ROOT_PATH /opt/${TOOLCHAIN_PREFIX}) 23 | elseif(EXISTS /usr/local/Cellar/mingw-w64/11.0.1/toolchain-x86_64/${TOOLCHAIN_PREFIX}) 24 | set(CMAKE_FIND_ROOT_PATH /usr/local/Cellar/mingw-w64/11.0.1/toolchain-x86_64/${TOOLCHAIN_PREFIX}) 25 | else() 26 | message(FATAL "couldn't find root dir") 27 | endif() 28 | 29 | # modify default behavior of FIND_XXX() commands 30 | set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) 31 | set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) 32 | set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) 33 | 34 | set(CMAKE_C_FLAGS "") 35 | set(CMAKE_EXE_LINKER_FLAGS "") 36 | 37 | set(CMAKE_C_FLAGS_RELEASE_INIT "") 38 | set(CMAKE_EXE_LINKER_FLAGS_RELEASE_INIT "") 39 | 40 | set(CMAKE_C_FLAGS_RELEASE "-O2 -DNDEBUG -flto -fno-builtin -nostdlib -mconsole -Xlinker --stack=0x200000,0x200000") 41 | set(CMAKE_EXE_LINKER_FLAGS_RELEASE "-Wl,--gc-sections -s") 42 | 43 | set(CMAKE_C_FLAGS_DEBUG "--entry=mainCRTStartup -O0 -g -fno-builtin -nostdlib -mconsole -Xlinker --stack=0x200000,0x200000") 44 | set(CMAKE_EXE_LINKER_FLAGS_DEBUG "-Wl,--gc-sections") 45 | 46 | -------------------------------------------------------------------------------- /buffer_helper.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021-2023 dresden elektronik ingenieurtechnik gmbh. 3 | * All rights reserved. 4 | * 5 | * The software in this package is published under the terms of the BSD 6 | * style license a copy of which has been included with this distribution in 7 | * the LICENSE.txt file. 8 | * 9 | */ 10 | 11 | #include "buffer_helper.h" 12 | 13 | unsigned char *put_u8_le(unsigned char *out, const unsigned char *in) 14 | { 15 | *out++ = *in; 16 | return out; 17 | } 18 | 19 | unsigned char *put_u16_le(unsigned char *out, const unsigned short *in) 20 | { 21 | *out++ = *in & 0x00FF; 22 | *out++ = (*in & 0xFF00) >> 8; 23 | return out; 24 | } 25 | 26 | unsigned char *put_u32_le(unsigned char *out, const unsigned long *in) 27 | { 28 | *out++ = (unsigned char)((*in & 0x000000FF) >> 0); 29 | *out++ = (unsigned char)((*in & 0x0000FF00) >> 8); 30 | *out++ = (unsigned char)((*in & 0x00FF0000) >> 16); 31 | *out++ = (unsigned char)((*in & 0xFF000000) >> 24); 32 | return out; 33 | } 34 | 35 | /* 36 | unsigned char *put_u64_le(unsigned char *out, const uint64_t *in) 37 | { 38 | *out++ = (*in & 0x00000000000000FFLLU) >> 0; 39 | *out++ = (*in & 0x000000000000FF00LLU) >> 8; 40 | *out++ = (*in & 0x0000000000FF0000LLU) >> 16; 41 | *out++ = (*in & 0x00000000FF000000LLU) >> 24; 42 | *out++ = (*in & 0x000000FF00000000LLU) >> 32; 43 | *out++ = (*in & 0x0000FF0000000000LLU) >> 40; 44 | *out++ = (*in & 0x00FF000000000000LLU) >> 48; 45 | *out++ = (*in & 0xFF00000000000000LLU) >> 56; 46 | return out; 47 | } 48 | */ 49 | 50 | const unsigned char *get_u8_le(const unsigned char *in, unsigned char *out) 51 | { 52 | *out = in[0]; 53 | return in + 1; 54 | } 55 | 56 | const unsigned char *get_u16_le(const unsigned char *in, unsigned short *out) 57 | { 58 | *out = in[0]; 59 | *out |= in[1] << 8; 60 | return in + 2; 61 | } 62 | 63 | const unsigned char *get_u32_le(const unsigned char *in, unsigned long *out) 64 | { 65 | *out = in[0] & 0xFF; 66 | *out |= ((unsigned long)in[1] << 8UL) & 0x0000FF00UL; 67 | *out |= ((unsigned long)in[2] << 16UL) & 0x00FF0000UL; 68 | *out |= ((unsigned long)in[3] << 24UL) & 0xFF000000UL; 69 | return in + 4; 70 | } 71 | 72 | /* 73 | const unsigned char *get_u64_le(const unsigned char *in, uint64_t *out) 74 | { 75 | *out = (uint64_t)in[0]; 76 | *out |= (uint64_t)in[1] << 8; 77 | *out |= (uint64_t)in[2] << 16ULL; 78 | *out |= (uint64_t)in[3] << 24ULL; 79 | *out |= (uint64_t)in[4] << 32ULL; 80 | *out |= (uint64_t)in[5] << 40ULL; 81 | *out |= (uint64_t)in[6] << 48ULL; 82 | *out |= (uint64_t)in[7] << 56ULL; 83 | return in + sizeof(*out); 84 | } 85 | */ 86 | -------------------------------------------------------------------------------- /u_sstream.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 Manuel Pietschmann. 3 | * All rights reserved. 4 | * 5 | * The software in this package is published under the terms of the BSD 6 | * style license a copy of which has been included with this distribution in 7 | * the LICENSE.txt file. 8 | * 9 | */ 10 | 11 | #ifndef U_SSTREAM_H 12 | #define U_SSTREAM_H 13 | 14 | #ifndef U_LIBAPI 15 | #ifdef _WIN32 16 | #ifdef USE_ULIB_SHARED 17 | #define U_LIBAPI __declspec(dllimport) 18 | #endif 19 | #ifdef BUILD_ULIB_SHARED 20 | #define U_LIBAPI __declspec(dllexport) 21 | #endif 22 | #endif 23 | #endif /* ! defined(U_LIBAPI) */ 24 | 25 | #ifndef U_LIBAPI 26 | #define U_LIBAPI 27 | #endif 28 | 29 | /* ANSI C module for building and parsing strings. 30 | 31 | The library is standalone without using libc. 32 | Upstream repository: https://git.sr.ht/~cryo/u_sstream 33 | */ 34 | 35 | typedef enum 36 | { 37 | U_SSTREAM_OK = 0, 38 | U_SSTREAM_ERR_RANGE = 1, 39 | U_SSTREAM_ERR_INVALID = 2, 40 | U_SSTREAM_ERR_NO_SPACE = 3, 41 | } U_sstream_status; 42 | 43 | typedef struct U_SStream 44 | { 45 | char *str; 46 | unsigned pos; 47 | unsigned len; 48 | U_sstream_status status; 49 | } U_SStream; 50 | 51 | #ifdef __cplusplus 52 | extern "C" { 53 | #endif 54 | 55 | U_LIBAPI void U_sstream_init(U_SStream *ss, void *str, unsigned size); 56 | U_LIBAPI unsigned U_sstream_pos(const U_SStream *ss); 57 | U_LIBAPI const char *U_sstream_str(const U_SStream *ss); 58 | U_LIBAPI unsigned U_sstream_remaining(const U_SStream *ss); 59 | U_LIBAPI int U_sstream_at_end(const U_SStream *ss); 60 | U_LIBAPI long U_sstream_get_long(U_SStream *ss); 61 | U_LIBAPI double U_sstream_get_double(U_SStream *ss); 62 | U_LIBAPI char U_sstream_peek_char(U_SStream *ss); 63 | U_LIBAPI void U_sstream_skip_whitespace(U_SStream *ss); 64 | U_LIBAPI int U_sstream_starts_with(U_SStream *ss, const char *str); 65 | U_LIBAPI int U_sstream_find(U_SStream *ss, const char *str); 66 | U_LIBAPI void U_sstream_seek(U_SStream *ss, unsigned pos); 67 | U_LIBAPI void U_sstream_put_str(U_SStream *ss, const char *str); 68 | 69 | /** Limited JSON friendly double to string conversion. 70 | * 71 | * Important: Only the values in range -2^53-1 to 2^53-1 are supported! 72 | * E.g. [+-]9007199254740991. Values out of this range result in 73 | * U_SSTREAM_ERR_RANGE status. For full range support use snprintf() instead. 74 | * 75 | * Special values: 76 | * 77 | * -0 --> 0 78 | * NaN --> null 79 | * -Inf --> -1e99999 80 | * +Inf --> 1e99999 81 | * 82 | * \param ss the stringt stream context. 83 | * \param num a double value. 84 | * \param precision of fractional part (1..18). 85 | */ 86 | U_LIBAPI void U_sstream_put_double(U_SStream *ss, double num, int precision); 87 | U_LIBAPI void U_sstream_put_long(U_SStream *ss, long num); 88 | U_LIBAPI void U_sstream_put_longlong(U_SStream *ss, long long num); 89 | U_LIBAPI void U_sstream_put_ulonglong(U_SStream *ss, unsigned long long num); 90 | U_LIBAPI void U_sstream_put_hex(U_SStream *ss, const void *data, unsigned size); 91 | 92 | U_LIBAPI long U_strtol(const char *s, unsigned len, const char **endp, int *err); 93 | U_LIBAPI double U_strtod(const char *str, unsigned len, const char **endp, int *err); 94 | 95 | #ifdef __cplusplus 96 | } 97 | #endif 98 | 99 | #endif /* U_SSTREAM_H */ 100 | -------------------------------------------------------------------------------- /net.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021-2023 dresden elektronik ingenieurtechnik gmbh. 3 | * All rights reserved. 4 | * 5 | * The software in this package is published under the terms of the BSD 6 | * style license a copy of which has been included with this distribution in 7 | * the LICENSE.txt file. 8 | * 9 | */ 10 | 11 | #define MAX_NET_CLIENTS 4 12 | 13 | #include 14 | 15 | #include "u_mem.h" 16 | #include "net.h" 17 | 18 | #ifdef USE_NET 19 | #include "net_sock.h" 20 | 21 | typedef struct NET_Client 22 | { 23 | S_Addr addr; 24 | unsigned short port; 25 | } NET_Client; 26 | 27 | typedef struct NET_State 28 | { 29 | S_Udp udp_main; 30 | unsigned char rx_buf[1280 + 1]; 31 | 32 | unsigned n_clients; 33 | NET_Client clients[MAX_NET_CLIENTS]; 34 | 35 | } NET_State; 36 | 37 | static NET_State net_state; 38 | 39 | int NET_Init(const char *interface, unsigned short port) 40 | { 41 | S_Udp *sock; 42 | SOCK_Init(); 43 | 44 | net_state.n_clients = 0; 45 | 46 | (void)interface; /* TODO */ 47 | sock = &net_state.udp_main; 48 | 49 | if (SOCK_UdpInit(sock, S_AF_IPV4) != 1) 50 | goto err1; 51 | 52 | if (SOCK_UdpBind(sock, port) != 1) 53 | goto err1; 54 | 55 | return 1; 56 | 57 | err1: 58 | return 0; 59 | } 60 | 61 | static int netCheckNewClient(void) 62 | { 63 | unsigned i; 64 | unsigned j; 65 | unsigned addr_len; 66 | S_Udp *sock; 67 | NET_Client *client; 68 | 69 | sock = &net_state.udp_main; 70 | client = &net_state.clients[0]; 71 | 72 | for (i = 0; i < net_state.n_clients; i++, client++) 73 | { 74 | if (sock->peer_port == client->port && sock->peer_addr.af == client->addr.af) 75 | { 76 | addr_len = (client->addr.af == S_AF_IPV4) ? 4 : 16; 77 | for (j = 0; j < addr_len; j++) 78 | { 79 | if (sock->peer_addr.data[j] != client->addr.data[j]) 80 | break; 81 | } 82 | 83 | if (j == addr_len) 84 | { 85 | // already known TODO refresh ttl 86 | return (int)i; 87 | } 88 | } 89 | } 90 | 91 | if (net_state.n_clients < MAX_NET_CLIENTS) 92 | { 93 | client = &net_state.clients[net_state.n_clients]; 94 | client->port = sock->peer_port; 95 | U_memcpy(&client->addr, &sock->peer_addr, sizeof(sock->peer_addr)); 96 | net_state.n_clients++; 97 | return (int)net_state.n_clients - 1; 98 | } 99 | else 100 | { 101 | /* clients exhausted (send error to last client?) */ 102 | } 103 | 104 | return -1; 105 | } 106 | 107 | int NET_Step(void) 108 | { 109 | /* 110 | echo -n "hello" >/dev/udp/127.0.0.1/19817 111 | */ 112 | 113 | int n; 114 | int client_id; 115 | 116 | n = SOCK_UdpRecv(&net_state.udp_main, net_state.rx_buf, sizeof(net_state.rx_buf) - 1); 117 | if (n > 0) 118 | { 119 | client_id = netCheckNewClient(); 120 | net_state.rx_buf[n] = '\0'; 121 | NET_Received(client_id, net_state.rx_buf, (unsigned)n); 122 | } 123 | 124 | return 1; 125 | } 126 | 127 | void NET_Exit(void) 128 | { 129 | net_state.n_clients = 0; 130 | SOCK_UdpFree(&net_state.udp_main); 131 | } 132 | 133 | #else 134 | int NET_Init(const char *interface, unsigned short port) 135 | { 136 | (void)interface; 137 | (void)port; 138 | return 0; 139 | } 140 | 141 | int NET_Step(void) 142 | { 143 | return 0; 144 | } 145 | 146 | void NET_Exit(void) 147 | { 148 | } 149 | #endif 150 | -------------------------------------------------------------------------------- /protocol.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021-2023 dresden elektronik ingenieurtechnik gmbh. 3 | * All rights reserved. 4 | * 5 | * The software in this package is published under the terms of the BSD 6 | * style license a copy of which has been included with this distribution in 7 | * the LICENSE.txt file. 8 | * 9 | */ 10 | 11 | #include "protocol.h" 12 | #include "gcf.h" 13 | 14 | #define FR_END (unsigned char)0xC0 15 | #define FR_ESC (unsigned char)0xDB 16 | #define T_FR_END (unsigned char)0xDC 17 | #define T_FR_ESC (unsigned char)0xDD 18 | #define ASC_FLAG 0x01 19 | 20 | void PROT_SendFlagged(const unsigned char *data, unsigned len) 21 | { 22 | unsigned char c = 0; 23 | unsigned short i = 0; 24 | unsigned short crc = 0; 25 | 26 | /* put an end before the packet */ 27 | PROT_Putc(FR_END); 28 | 29 | while (i < len) 30 | { 31 | c = data[i++]; 32 | crc += c; 33 | 34 | switch (c) 35 | { 36 | case FR_ESC: 37 | PROT_Putc(FR_ESC); 38 | PROT_Putc(T_FR_ESC); 39 | break; 40 | case FR_END: 41 | PROT_Putc(FR_ESC); 42 | PROT_Putc(T_FR_END); 43 | break; 44 | default: 45 | PROT_Putc(c); 46 | break; 47 | } 48 | } 49 | 50 | c = (~crc + 1) & 0xFF; 51 | if (c == FR_ESC) 52 | { 53 | PROT_Putc(FR_ESC); 54 | PROT_Putc(T_FR_ESC); 55 | } 56 | else if (c == FR_END) 57 | { 58 | PROT_Putc(FR_ESC); 59 | PROT_Putc(T_FR_END); 60 | } 61 | else 62 | { 63 | PROT_Putc(c); 64 | } 65 | 66 | c = ( (~crc + 1) >> 8) & 0xFF; 67 | if (c == FR_ESC) 68 | { 69 | PROT_Putc(FR_ESC); 70 | PROT_Putc(T_FR_ESC); 71 | } 72 | else if (c == FR_END) 73 | { 74 | PROT_Putc(FR_ESC); 75 | PROT_Putc(T_FR_END); 76 | } 77 | else 78 | { 79 | PROT_Putc(c); 80 | } 81 | 82 | /* tie off the packet */ 83 | PROT_Putc(FR_END); 84 | 85 | PROT_Flush(); 86 | } 87 | 88 | void PROT_ReceiveFlagged(PROT_RxState *rx, const unsigned char *data, unsigned len) 89 | { 90 | unsigned char c; 91 | unsigned short pos = 0; 92 | 93 | if (len == 0) 94 | { 95 | return; 96 | } 97 | 98 | nextTurn: 99 | while(pos < len) 100 | { 101 | c = data[pos]; 102 | pos++; 103 | 104 | switch (c) 105 | { 106 | case FR_END: 107 | if (rx->escaped) 108 | { 109 | /* invalid */ 110 | rx->bufpos = 0; 111 | rx->crc = 0; 112 | } 113 | else 114 | { 115 | if (rx->bufpos >= 2) 116 | { 117 | unsigned char crcvalid = 0; 118 | /* Checksum bytes are added to the checksum rx->crc - substract them here */ 119 | rx->crc -= rx->buf[rx->bufpos-1]; 120 | rx->crc -= rx->buf[rx->bufpos-2]; 121 | /* TODO clean this messy condition up */ 122 | if ((((~(rx->crc)+1 ) & 0xFF) == rx->buf[rx->bufpos - 2]) && 123 | ((((~(rx->crc)+1 ) >> 8) & 0xFF) == rx->buf[rx->bufpos - 1])) 124 | { 125 | crcvalid = 1; 126 | } 127 | 128 | if (crcvalid) 129 | { 130 | PROT_Packet(&rx->buf[0], rx->bufpos - 2); 131 | } 132 | else 133 | { 134 | PL_Printf(DBG_DEBUG, "invalid CRC\n"); 135 | } 136 | } 137 | rx->bufpos = 0; 138 | rx->crc = 0; 139 | } 140 | rx->escaped &= ~ASC_FLAG; 141 | goto nextTurn; 142 | 143 | case FR_ESC: 144 | rx->escaped |= ASC_FLAG; 145 | goto nextTurn; 146 | } 147 | 148 | if (rx->escaped & ASC_FLAG) 149 | { 150 | /* translate the 2 byte escape sequence back to original char */ 151 | rx->escaped &= ~ASC_FLAG; 152 | 153 | switch (c) 154 | { 155 | case T_FR_ESC: c = FR_ESC; break; 156 | case T_FR_END: c = FR_END; break; 157 | default: goto nextTurn; 158 | } 159 | } 160 | 161 | /* we reach here with every byte for the buffer 162 | legacy BUG: checksum bytes are added but should not be */ 163 | if (rx->bufpos < sizeof(rx->buf)) 164 | { 165 | rx->buf[rx->bufpos++] = c; 166 | rx->crc += c; 167 | } 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /u_bstream.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 Manuel Pietschmann. 3 | * All rights reserved. 4 | * 5 | * The software in this package is published under the terms of the BSD 6 | * style license a copy of which has been included with this distribution in 7 | * the LICENSE.txt file. 8 | * 9 | */ 10 | 11 | #include "u_bstream.h" 12 | 13 | void U_bstream_init(U_BStream *bs, void *data, unsigned long size) 14 | { 15 | bs->data = (unsigned char*)data; 16 | bs->pos = 0; 17 | bs->size = size; 18 | bs->status = U_BSTREAM_OK; 19 | } 20 | 21 | static int U_bstream_verify_write(U_BStream *bs, unsigned long size) 22 | { 23 | if (bs->status != U_BSTREAM_OK) 24 | return 0; 25 | 26 | if (!bs->data) 27 | { 28 | bs->status = U_BSTREAM_NOT_INITIALISED; 29 | return 0; 30 | } 31 | 32 | if ((bs->pos + size) > bs->size) 33 | { 34 | bs->status = U_BSTREAM_WRITE_PAST_END; 35 | return 0; 36 | } 37 | 38 | return 1; 39 | } 40 | 41 | static int U_bstream_verify_read(U_BStream *bs, unsigned long size) 42 | { 43 | if (bs->status != U_BSTREAM_OK) 44 | return 0; 45 | 46 | if (!bs->data) 47 | { 48 | bs->status = U_BSTREAM_NOT_INITIALISED; 49 | return 0; 50 | } 51 | 52 | if ((bs->pos + size) > bs->size) 53 | { 54 | bs->status = U_BSTREAM_READ_PAST_END; 55 | return 0; 56 | } 57 | 58 | return 1; 59 | } 60 | 61 | void U_bstream_put_u8(U_BStream *bs, unsigned char v) 62 | { 63 | if (U_bstream_verify_write(bs, 1)) 64 | { 65 | bs->data[bs->pos++] = v; 66 | } 67 | } 68 | 69 | void U_bstream_put_u16_le(U_BStream *bs, unsigned short v) 70 | { 71 | if (U_bstream_verify_write(bs, 2)) 72 | { 73 | bs->data[bs->pos++] = (v >> 0) & 0xFF; 74 | bs->data[bs->pos++] = (v >> 8) & 0xFF; 75 | } 76 | } 77 | 78 | void U_bstream_put_u32_le(U_BStream *bs, unsigned long v) 79 | { 80 | if (U_bstream_verify_write(bs, 4)) 81 | { 82 | bs->data[bs->pos++] = (v >> 0) & 0xFF; 83 | bs->data[bs->pos++] = (v >> 8) & 0xFF; 84 | bs->data[bs->pos++] = (v >> 16) & 0xFF; 85 | bs->data[bs->pos++] = (v >> 24) & 0xFF; 86 | } 87 | } 88 | 89 | void U_bstream_put_u32_be(U_BStream *bs, unsigned long v) 90 | { 91 | if (U_bstream_verify_write(bs, 4)) 92 | { 93 | bs->data[bs->pos++] = (v >> 24) & 0xFF; 94 | bs->data[bs->pos++] = (v >> 16) & 0xFF; 95 | bs->data[bs->pos++] = (v >> 8) & 0xFF; 96 | bs->data[bs->pos++] = (v >> 0) & 0xFF; 97 | } 98 | } 99 | 100 | unsigned char U_bstream_get_u8(U_BStream *bs) 101 | { 102 | unsigned char result; 103 | result = 0; 104 | 105 | if (U_bstream_verify_read(bs, 1)) 106 | { 107 | result = bs->data[bs->pos]; 108 | bs->pos++; 109 | } 110 | 111 | return result; 112 | } 113 | 114 | unsigned short U_bstream_get_u16_le(U_BStream *bs) 115 | { 116 | unsigned short result; 117 | result = 0; 118 | 119 | if (U_bstream_verify_read(bs, 2)) 120 | { 121 | result = bs->data[bs->pos + 1]; 122 | result <<= 8; 123 | result |= bs->data[bs->pos]; 124 | bs->pos += 2; 125 | } 126 | 127 | return result; 128 | } 129 | 130 | unsigned short U_bstream_get_u16_be(U_BStream *bs) 131 | { 132 | unsigned short result; 133 | result = 0; 134 | 135 | if (U_bstream_verify_read(bs, 2)) 136 | { 137 | result = bs->data[bs->pos]; 138 | result <<= 8; 139 | result |= bs->data[bs->pos + 1]; 140 | bs->pos += 2; 141 | } 142 | 143 | return result; 144 | } 145 | 146 | unsigned long U_bstream_get_u32_le(U_BStream *bs) 147 | { 148 | unsigned long result; 149 | result = 0; 150 | 151 | if (U_bstream_verify_read(bs, 4)) 152 | { 153 | result = bs->data[bs->pos + 3]; 154 | result <<= 8; 155 | result |= bs->data[bs->pos + 2]; 156 | result <<= 8; 157 | result |= bs->data[bs->pos + 1]; 158 | result <<= 8; 159 | result |= bs->data[bs->pos + 0]; 160 | bs->pos += 4; 161 | } 162 | 163 | return result; 164 | } 165 | 166 | unsigned long U_bstream_get_u32_be(U_BStream *bs) 167 | { 168 | unsigned long result; 169 | result = 0; 170 | 171 | if (U_bstream_verify_read(bs, 4)) 172 | { 173 | result = bs->data[bs->pos]; 174 | result <<= 8; 175 | result |= bs->data[bs->pos + 1]; 176 | result <<= 8; 177 | result |= bs->data[bs->pos + 2]; 178 | result <<= 8; 179 | result |= bs->data[bs->pos + 3]; 180 | bs->pos += 4; 181 | } 182 | 183 | return result; 184 | } 185 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GCFFlasher 4 2 | 3 | GCFFlasher is the tool to program the firmware of dresden elektronik Zigbee products. 4 | 5 | ## Supported Hardware 6 | 7 | * ConBee I 8 | * ConBee II 9 | * ConBee III 10 | * RaspBee I 11 | * RaspBee II 12 | * Hive 13 | * FLS-M 14 | 15 | ## Supported platforms 16 | 17 | The sources are POSIX compliant with a small platform specific layer, to make porting to different platforms easy. 18 | 19 | * GNU/Linux (arm, aarch64 and amd64; mips and risc should work too but aren't tested) 20 | * FreeBSD 21 | * Windows 22 | * macOS 23 | 24 | ## Notes 25 | 26 | * To use the sniffer mode on ConBee I and ConBee II the ZShark sniffer firmware needs to be installed. It can be downloaded at https://deconz.dresden-elektronik.de/deconz-firmware 27 | * On macOS the `-d` parameter is `/dev/cu.usbmodemDE...` where ... is the serialnumber. 28 | 29 | ## Building on Linux 30 | 31 | ### Dependencies 32 | 33 | The executable can be compiled without any dependencies, but it is recommended to install `libgpiod` to support RaspBee I, RaspBee II and ConBee I. 34 | 35 | * A C99 compiler like GCC or Clang 36 | * Linux kernel version 4.8 37 | * CMake 38 | * pkg-config 39 | * libgpiod 40 | 41 | The executable doesn't link directly to libgpiod and will check at runtime if it is available via `dlopen()`. 42 | 43 | On Debian based distributions the build dependencies are installed by: 44 | 45 | ``` 46 | apt install pkg-config build-essential libgpiod-dev cmake make 47 | ``` 48 | 49 | ### Build 50 | 51 | 1. Checkout this repository 52 | 53 | 2. Navigate to the source directory, e.g. `cd gcfflasher` 54 | 55 | 3. Compile the executable with CMake 56 | 57 | ``` 58 | cmake -B build . 59 | cmake --build build 60 | ``` 61 | 62 | The executable is `build/GCFFlasher4` 63 | 64 | 4. (optional) create a .deb package 65 | 66 | ``` 67 | cd build 68 | cpack -G DEB . 69 | ``` 70 | 71 | ## Building on Windows 72 | 73 | ### Dependencies 74 | 75 | Visual Studio with MSVC C++ compiler needs to be installed. Tested with VS 2022 but older versions should work fine as well. The executable has no external dependencies. 76 | 77 | ### Build 78 | 79 | 1. Checkout this repository 80 | 81 | 2. Open "x86 Native Tools Command Promt for VS 2022" via Windows Start Menu 82 | 83 | 3. Navigate to the source directory, e.g. `cd C:\gcfflasher` 84 | 85 | 3. Compile the executable with CMake 86 | 87 | ``` 88 | cmake -B build . 89 | cmake --build build --config Release 90 | ``` 91 | 92 | The executable is `build\Release\GCFFlasher4.exe`. 93 | 94 | ## Building on macOS 95 | 96 | ### Dependencies 97 | 98 | The executable can be compiled without any dependencies for ConBee II. To support ConBee I the library `libftdi` needs to be installed. 99 | 100 | * A C99 compiler like GCC or Clang 101 | * CMake 102 | * (optional) the `libftdi` development package for ConBee I can be installed via `brew install libftdi` using [Homebrew](https://brew.sh). 103 | 104 | ### Build 105 | 106 | 1. Checkout this repository 107 | 108 | 2. Navigate to the source directory, e.g. `cd gcfflasher` 109 | 110 | 3. Compile the executable with CMake 111 | 112 | ``` 113 | cmake -B build . 114 | cmake --build build 115 | ``` 116 | 117 | The executable is `build/GCFFlasher4` 118 | 119 | ## Run 120 | 121 | ``` 122 | $ ./GCFFlasher4 123 | GCFFlasher 4.8.0 copyright dresden elektronik ingenieurtechnik gmbh 124 | usage: GCFFlasher 125 | options: 126 | -r force device reboot without programming 127 | -f flash firmware file 128 | -d device number or path to use, e.g. 0, /dev/ttyUSB0 or RaspBee 129 | -s enable sniffer on Zigbee channel (requires sniffer firmware) 130 | the Wireshark sniffer traffic is send to UDP port 17754 131 | -H send sniffer traffic to Wireshark running on host 132 | default is 172.0.0.1 (localhost) 133 | -c connect and debug serial protocol 134 | -t retry until timeout (seconds) is reached 135 | -l list devices 136 | -x debug log level 0, 1, 3 137 | -i interactive mode for debugging 138 | -h -? print this help 139 | ``` 140 | 141 | ## Building on FreeBSD 142 | 143 | ### Build 144 | 145 | 1. Checkout this repository 146 | 147 | 2. Navigate to the source directory, e.g. `cd gcfflasher` 148 | 149 | 3. Compile the executable with the build script (with Clang) 150 | 151 | ``` 152 | cmake -B build . 153 | cmake --build build 154 | ``` 155 | 156 | The executable is `build/GCFFlasher4` 157 | 158 | **Note:** The serial USB device for a ConBee II is `/dev/cuaU0`. 159 | 160 | 161 | ## Differences to previous GCFFlasher version 3.17 162 | 163 | * Open sourced under BSD-3-Clause License 164 | * Doesn't require root privileges on Raspberry Pi 165 | * Rewritten in C instead C++ 166 | * Smaller binary, with 25 Kb vs. previously 250 Kb + Qt libraries on Raspberry Pi 167 | * No Qt, libWiringPi and libft2xx (FTDI) dependencies 168 | * Easier to port to different platforms 169 | * Suitable for headless systems and standalone setup which don't use deCONZ 170 | -------------------------------------------------------------------------------- /gcf.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021-2023 dresden elektronik ingenieurtechnik gmbh. 3 | * All rights reserved. 4 | * 5 | * The software in this package is published under the terms of the BSD 6 | * style license a copy of which has been included with this distribution in 7 | * the LICENSE.txt file. 8 | * 9 | */ 10 | 11 | #ifndef GCF_H 12 | #define GCF_H 13 | 14 | typedef enum 15 | { 16 | EV_ACTION = 0, 17 | EV_RESET_SUCCESS = 10, 18 | EV_RESET_FAILED = 20, 19 | EV_UART_RESET_SUCCESS = 11, 20 | EV_UART_RESET_FAILED = 21, 21 | EV_FTDI_RESET_SUCCESS = 12, 22 | EV_FTDI_RESET_FAILED = 22, 23 | EV_RASPBEE_RESET_SUCCESS = 13, 24 | EV_RASPBEE_RESET_FAILED = 23, 25 | EV_PKG_UART_RESET = 33, 26 | EV_PL_STARTED = 100, 27 | EV_PL_LOOP = 101, 28 | EV_RX_ASCII = 50, 29 | EV_RX_BTL_PKG_DATA = 40, 30 | EV_RX_PKG_DATA = 41, 31 | EV_CONNECTED = 200, 32 | EV_DISCONNECTED = 203, 33 | EV_TIMEOUT = 333 34 | } Event; 35 | 36 | typedef enum 37 | { 38 | GCF_SUCCESS, 39 | GCF_FAILED 40 | } GCF_Status; 41 | 42 | typedef enum 43 | { 44 | PL_BAUDRATE_UNKNOWN = 0, 45 | PL_BAUDRATE_38400 = 38400, 46 | PL_BAUDRATE_115200 = 115200 47 | } PL_Baudrate; 48 | 49 | #define PL_KEY_BACKSPACE 0x07FFFF00 50 | #define PL_KEY_DELETE 0x07FFFF01 51 | #define PL_KEY_TAB 0x09 52 | #define PL_KEY_ENTER 0x0A 53 | #define PL_KEY_UP 0x07FFFF04 54 | #define PL_KEY_DOWN 0x07FFFF05 55 | #define PL_KEY_LEFT 0x07FFFF06 56 | #define PL_KEY_RIGHT 0x07FFFF07 57 | #define PL_KEY_ESC 0x1B 58 | #define PL_KEY_POS1 0x07FFFF09 59 | #define PL_KEY_END 0x07FFFF0A 60 | 61 | typedef struct GCF_t GCF; 62 | typedef struct GCF_File_t GCF_File; 63 | 64 | /* TODO detect old 32-bit only compilers */ 65 | typedef unsigned long long PL_time_t; 66 | 67 | 68 | #ifdef NDEBUG 69 | #define Assert(c) ((void)0) 70 | #else 71 | #if _MSC_VER 72 | #define Assert(c) if (!(c)) __debugbreak() 73 | #elif __GNUC__ 74 | #define Assert(c) if (!(c)) __builtin_trap() 75 | #else 76 | #define Assert(c) ((void)0) 77 | #endif 78 | #endif /* NDEBUG */ 79 | 80 | GCF *GCF_Init(int argc, char *argv[]); 81 | void GCF_Exit(GCF *gcf); 82 | 83 | /*! Called from platform layer when \p data has been received, \p len must be > 0. */ 84 | void GCF_Received(GCF *gcf, const unsigned char *data, int len); 85 | /*! Called from platform layer for keyboard input. */ 86 | void GCF_KeyboardInput(GCF *gcf, unsigned long codepoint); 87 | void GCF_HandleEvent(GCF *gcf, Event event); 88 | 89 | int GCF_ParseFile(GCF_File *file); 90 | void gcfDebugHex(GCF *gcf, const char *msg, const unsigned char *data, unsigned size); 91 | void put_hex(unsigned char ch, char *buf); 92 | 93 | /* Platform specific declarations. 94 | 95 | The functions prefixed with PL_ need to be implemented for a target platform. 96 | */ 97 | 98 | /*! Returns a monotonic time in milliseconds. */ 99 | PL_time_t PL_Time(void); 100 | 101 | /*! Lets the programm sleep for \p ms milliseconds. */ 102 | void PL_MSleep(unsigned long ms); 103 | 104 | 105 | /*! Sets a timeout \p ms in milliseconds, after which a \c EV_TIMOUT event is generated. */ 106 | void PL_SetTimeout(unsigned long ms); 107 | 108 | /*! Clears an active timeout. */ 109 | void PL_ClearTimeout(void); 110 | 111 | #define MAX_DEV_NAME_LENGTH 32 112 | #define MAX_DEV_SERIALNR_LENGTH 18 113 | #define MAX_DEV_PATH_LENGTH 255 114 | #define MAX_GCF_FILE_SIZE (1 << 22) /* 4 MB */ 115 | 116 | typedef struct 117 | { 118 | PL_Baudrate baudrate; 119 | char name[MAX_DEV_NAME_LENGTH]; 120 | char path[MAX_DEV_PATH_LENGTH]; 121 | char serial[MAX_DEV_SERIALNR_LENGTH]; 122 | char stablepath[MAX_DEV_PATH_LENGTH]; 123 | } Device; 124 | 125 | /* Fills up to \p max devices in the \p devs array. 126 | 127 | The output is used in list operation (-l). 128 | */ 129 | int PL_GetDevices(Device *devs, unsigned max); 130 | 131 | /*! Opens the serial port connection for device. 132 | 133 | \param path - The path like /dev/ttyACM0 or COM7. 134 | \returns GCF_SUCCESS or GCF_FAILED 135 | */ 136 | GCF_Status PL_Connect(const char *path, PL_Baudrate baudrate); 137 | 138 | /*! Closed the serial port connection. */ 139 | void PL_Disconnect(void); 140 | 141 | /*! Shuts down platform layer (ends main loop). */ 142 | void PL_ShutDown(void); 143 | 144 | /*! Executes a MCU reset for ConBee I via FTDI CBUS0 reset. */ 145 | int PL_ResetFTDI(int num, const char *serialnum); 146 | 147 | /*! Executes a MCU reset for RaspBee I / II via GPIO17 reset pin. */ 148 | int PL_ResetRaspBee(void); 149 | 150 | int PL_ReadFile(const char *path, unsigned char *buf, unsigned long buflen); 151 | 152 | 153 | /* Terminal printing and logging */ 154 | 155 | typedef enum 156 | { 157 | DBG_INFO = 0x0001, 158 | DBG_DEBUG = 0x0002, 159 | DBG_RAW = 0x0004 160 | } DebugLevel; 161 | 162 | /* ASCII escape codes 163 | 164 | https://gist.github.com/fnky/458719343aabd01cfb17a3a4f7296797 165 | */ 166 | #ifdef PL_NO_ESCASCII 167 | #define FMT_ESC "" 168 | #define FMT_GREEN FMT_ESC "" 169 | #define FMT_RESET FMT_ESC "" 170 | #else 171 | #define FMT_ESC "\x1b" 172 | #define FMT_GREEN FMT_ESC "[32m" 173 | #define FMT_RESET FMT_ESC "[0m" 174 | #endif 175 | 176 | void PL_Print(const char *line); 177 | 178 | void PL_Printf(DebugLevel level, const char *format, ...); 179 | 180 | void UI_GetWinSize(unsigned *w, unsigned *h); 181 | void UI_SetCursor(unsigned x, unsigned y); 182 | 183 | #endif /* GCF_H */ 184 | -------------------------------------------------------------------------------- /net_udp_win32.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "u_mem.h" 4 | #include "net_sock.h" 5 | 6 | static int wsa_init = 0; 7 | static WSADATA wsa_data; 8 | 9 | int SOCK_UdpInit(S_Udp *udp, int af) 10 | { 11 | if (wsa_init == 0) 12 | { 13 | if (WSAStartup(MAKEWORD(2,2), &wsa_data) != 0) 14 | return -1; 15 | 16 | wsa_init = 1; 17 | } 18 | 19 | U_bzero(udp, sizeof(*udp)); 20 | udp->state = S_UDP_STATE_INIT; 21 | udp->addr.af = af; 22 | 23 | if (af == S_AF_IPV4) af = AF_INET; 24 | else if (af == S_AF_IPV6) af = AF_INET6; 25 | else 26 | { 27 | udp->addr.af = S_AF_UNKNOWN; 28 | return 0; 29 | } 30 | 31 | udp->handle = socket(af, SOCK_DGRAM, 0); 32 | 33 | if (udp->handle == INVALID_SOCKET) 34 | return 0; 35 | 36 | udp->state = S_UDP_STATE_OPEN; 37 | 38 | return 1; 39 | } 40 | 41 | int SOCK_UdpSetPeer(S_Udp *udp, const char *peer, unsigned short port) 42 | { 43 | struct in_addr addr; 44 | struct in6_addr addr6; 45 | 46 | if (inet_pton(AF_INET, peer, &addr) == 1) 47 | { 48 | udp->peer_addr.af = S_AF_IPV4; 49 | udp->peer_port = port; 50 | U_memcpy(udp->peer_addr.data, &addr.s_addr, 4); 51 | return 0; 52 | } 53 | else if (inet_pton(AF_INET6, peer, &addr6) == 1) 54 | { 55 | udp->peer_addr.af = S_AF_IPV6; 56 | udp->peer_port = port; 57 | U_memcpy(udp->peer_addr.data, &addr6, 16); 58 | return 0; 59 | } 60 | 61 | return -1; 62 | } 63 | 64 | int SOCK_UdpBind(S_Udp *udp, unsigned short port) 65 | { 66 | #if 0 67 | int ret; 68 | struct sockaddr_in addr; 69 | 70 | if (udp->state != S_UDP_STATE_OPEN) 71 | return -1; 72 | 73 | if (udp->addr.af == S_AF_IPV4) 74 | { 75 | // allow multiple sockets to use the same PORT number 76 | int yes = 1; 77 | if (setsockopt(udp->handle, SOL_SOCKET, SO_REUSEADDR, (char*) &yes, sizeof(yes) ) < 0 ) 78 | { 79 | goto err; 80 | } 81 | 82 | U_bzero(&addr, sizeof(addr)); 83 | addr.sin_family = AF_INET; 84 | addr.sin_addr.s_addr = htonl(INADDR_ANY); 85 | addr.sin_port = htons(port); 86 | ret = bind(udp->handle, (struct sockaddr*) &addr, sizeof(addr)); 87 | 88 | if (ret == -1) 89 | { 90 | goto err; 91 | } 92 | 93 | udp->port = port; 94 | return 0; 95 | } 96 | 97 | err: 98 | udp->state = S_UDP_STATE_ERROR; 99 | #endif 100 | return -1; 101 | } 102 | 103 | int SOCK_UdpJoinMulticast(S_Udp *udp, const char *maddr) 104 | { 105 | (void)maddr; 106 | if (udp->state != S_UDP_STATE_OPEN) 107 | return -1; 108 | #if 0 109 | if (udp->addr.af == S_AF_IPV4) 110 | { 111 | struct ip_mreq mreq; 112 | mreq.imr_multiaddr.s_addr = inet_addr(maddr); 113 | mreq.imr_interface.s_addr = htonl(INADDR_ANY); 114 | 115 | if (setsockopt(udp->handle, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) < 0) 116 | { 117 | goto err; 118 | } 119 | 120 | return 0; 121 | } 122 | 123 | err: 124 | udp->state = S_UDP_STATE_ERROR; 125 | #endif 126 | return -1; 127 | } 128 | 129 | int SOCK_UdpRecv(S_Udp *udp, unsigned char *buf, unsigned bufsize) 130 | { 131 | #if 0 132 | ssize_t n; 133 | fd_set readfds; 134 | struct timeval tv; 135 | socklen_t addr_len; 136 | struct sockaddr_in addr; 137 | 138 | if (udp->state != S_UDP_STATE_OPEN) 139 | return; 140 | 141 | tv.tv_sec = 0; 142 | tv.tv_usec = 5 * 1000; // 5 msec 143 | FD_ZERO(&readfds); 144 | FD_SET(udp->handle, &readfds); 145 | // don't care about writefds and exceptfds: 146 | select(udp->handle+1, &readfds, NULL, NULL, &tv); 147 | if (FD_ISSET(udp->handle, &readfds)) 148 | { 149 | addr_len = sizeof(addr); 150 | n = recvfrom(udp->handle, &udp->rx_buf[0], sizeof(udp->rx_buf), 0, (struct sockaddr*)&addr, &addr_len); 151 | 152 | if (n > 0 && n < S_UDP_MAX_PKG_SIZE) 153 | { 154 | udp->rx_buf[n] = '\0'; 155 | if (udp->rx) 156 | udp->rx(udp->user, udp, &udp->rx_buf[0], (unsigned)n); 157 | } 158 | } 159 | #endif 160 | return -1; 161 | } 162 | 163 | int SOCK_UdpSend(S_Udp *udp, unsigned char *buf, unsigned bufsize) 164 | { 165 | int n; 166 | struct sockaddr_in dest_addr; 167 | struct sockaddr_in6 dest_addr6; 168 | 169 | if (udp->peer_addr.af == S_AF_IPV4) 170 | { 171 | dest_addr.sin_family = AF_INET; 172 | U_memcpy ( (char *) &dest_addr.sin_addr.s_addr, udp->peer_addr.data, 4); 173 | dest_addr.sin_port = htons(udp->peer_port); 174 | 175 | n = sendto(udp->handle, (char*)buf, (int)bufsize, 0 /* flags */, (struct sockaddr*)&dest_addr, sizeof(dest_addr)); 176 | } 177 | else if (udp->peer_addr.af == S_AF_IPV6) 178 | { 179 | dest_addr6.sin6_family = AF_INET6; 180 | U_memcpy ( (char *) &dest_addr6.sin6_addr, udp->peer_addr.data, 16); 181 | dest_addr6.sin6_port = htons (udp->peer_port); 182 | 183 | n = sendto(udp->handle, (char*)buf, (int)bufsize, 0 /* flags */, (struct sockaddr*)&dest_addr6, sizeof(dest_addr6)); 184 | } 185 | else 186 | { 187 | return -1; 188 | } 189 | 190 | if (n < 0) 191 | { 192 | return -1; 193 | } 194 | 195 | return n; 196 | } 197 | 198 | void SOCK_UdpFree(S_Udp *udp) 199 | { 200 | if (udp->handle) 201 | closesocket(udp->handle); 202 | 203 | U_bzero(udp, sizeof(*udp)); 204 | udp->state = S_UDP_STATE_INIT; 205 | } 206 | -------------------------------------------------------------------------------- /macos_get_usb_devices.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "gcf.h" 4 | #include "u_sstream.h" 5 | #include "u_strlen.h" 6 | #include "u_mem.h" 7 | 8 | /* 9 | On macOS the following commandline tools can be used: 10 | 11 | system_profiler -json SPUSBDataType 12 | ioreg -Src IOUSBDevice 13 | */ 14 | 15 | char buf[4096 * 64]; 16 | 17 | static int next_line(U_SStream *line, U_SStream *ss) 18 | { 19 | if (ss->status != U_SSTREAM_OK) 20 | return 0; 21 | 22 | line->status = U_SSTREAM_OK; 23 | line->str = &ss->str[ss->pos]; 24 | line->pos = 0; 25 | line->len = 0; 26 | 27 | for (;ss->pos < ss->len;) 28 | { 29 | line->len++; 30 | ss->pos++; 31 | if (line->str[line->len - 1] == '\n') 32 | break; 33 | } 34 | 35 | if (line->len) 36 | return 1; 37 | 38 | return 0; 39 | } 40 | 41 | enum query_state 42 | { 43 | SP_STATE_INIT, 44 | SP_STATE_DEVICE 45 | }; 46 | 47 | /* 48 | * Lookup device path under /dev/ 49 | * The respective entry always has the serial number in the name. 50 | */ 51 | static int getDevicePath(Device *dev) 52 | { 53 | DIR *dir; 54 | struct dirent *entry; 55 | U_SStream ss; 56 | 57 | dir = opendir("/dev"); 58 | 59 | if (!dir) 60 | return 0; 61 | 62 | dev->path[0] = '\0'; 63 | 64 | while ((entry = readdir(dir)) != NULL) 65 | { 66 | U_sstream_init(&ss, entry->d_name, U_strlen(entry->d_name)); 67 | 68 | if (U_sstream_starts_with(&ss, "cu.") == 0) 69 | continue; 70 | 71 | if (U_sstream_find(&ss, dev->serial)) 72 | { 73 | U_sstream_init(&ss, dev->path, sizeof(dev->path)); 74 | U_sstream_put_str(&ss, "/dev/"); 75 | U_sstream_put_str(&ss, entry->d_name); 76 | 77 | U_memcpy(&dev->stablepath[0], &dev->path[0], sizeof(dev->path)); 78 | break; 79 | } 80 | } 81 | 82 | closedir(dir); 83 | 84 | if (dev->path[0] != '\0') 85 | return 1; 86 | 87 | return 0; 88 | } 89 | 90 | /* 91 | * Parse the output of: system_profiler -detailLevel mini SPUSBDataType 2>/dev/null 92 | * It's a bit messy but should also work on older macOS versions before Catalina. 93 | */ 94 | static int queryFromSystemProfiler(Device *dev, Device *end) 95 | { 96 | FILE *f; 97 | size_t n; 98 | U_SStream ss; 99 | U_SStream line; 100 | unsigned i; 101 | enum query_state state; 102 | int result; 103 | 104 | result = 0; 105 | ss.str = 0; 106 | state = SP_STATE_INIT; 107 | 108 | /* check if udevadm is available */ 109 | f = popen("system_profiler -detailLevel mini SPUSBDataType 2>/dev/null", "r"); 110 | if (f) 111 | { 112 | n = fread(&buf[0], 1, sizeof(buf) - 1, f); 113 | if (n > 0) 114 | { 115 | buf[n] = '\0'; 116 | U_sstream_init(&ss, &buf[0], (unsigned)n); 117 | } 118 | pclose(f); 119 | } 120 | 121 | if (!ss.str) 122 | return result; 123 | 124 | for (;;) 125 | { 126 | if (dev >= end) 127 | break; 128 | 129 | if (next_line(&line, &ss) == 0) 130 | break; 131 | 132 | U_sstream_skip_whitespace(&line); 133 | 134 | if (state == SP_STATE_INIT) 135 | { 136 | if (U_sstream_starts_with(&line, "ConBee II:")) 137 | { 138 | state = SP_STATE_DEVICE; 139 | dev->baudrate = PL_BAUDRATE_115200; 140 | } 141 | else if (U_sstream_starts_with(&line, "ConBee III:")) 142 | { 143 | state = SP_STATE_DEVICE; 144 | dev->baudrate = PL_BAUDRATE_115200; 145 | } 146 | else if (U_sstream_starts_with(&line, "FT230X Basic UART")) /* ConBee I */ 147 | { 148 | state = SP_STATE_DEVICE; 149 | dev->baudrate = PL_BAUDRATE_38400; 150 | } 151 | 152 | if (state == SP_STATE_DEVICE) 153 | { 154 | if (line.len - line.pos > sizeof(dev->name)) 155 | { 156 | // sanity should not happen 157 | state = SP_STATE_INIT; 158 | continue; 159 | } 160 | 161 | for (i = 0; line.str[line.pos + i] != ':'; i++) 162 | { 163 | dev->name[i] = line.str[line.pos + i]; 164 | } 165 | 166 | dev->name[i] = '\0'; 167 | } 168 | } 169 | else if (state == SP_STATE_DEVICE) 170 | { 171 | if (U_sstream_starts_with(&line, "Serial Number:")) 172 | { 173 | state = SP_STATE_INIT; 174 | 175 | for (;line.pos < line.len; line.pos++) 176 | { 177 | if (line.str[line.pos - 1] == ':') 178 | break; 179 | } 180 | 181 | U_sstream_skip_whitespace(&line); 182 | 183 | if (line.len - line.pos > sizeof(dev->serial)) 184 | { 185 | // sanity should not happen 186 | state = SP_STATE_INIT; 187 | continue; 188 | } 189 | 190 | for (i = 0; line.pos + i < line.len; i++) 191 | { 192 | if (line.str[line.pos + i] == '\n') 193 | break; 194 | dev->serial[i] = line.str[line.pos + i]; 195 | } 196 | 197 | dev->serial[i] = '\0'; 198 | 199 | if (getDevicePath(dev)) 200 | { 201 | //printf("'%s' - '%s' (%s)\n", dev->name, dev->serial, dev->path); 202 | dev++; 203 | result++; 204 | } 205 | } 206 | } 207 | } 208 | 209 | return result; 210 | } 211 | 212 | /*! Fills the \p dev array with ConBee I, II and III devices. 213 | 214 | \returns The number of devices placed in the array. 215 | */ 216 | int plGetMacOSUSBDevices(Device *dev, Device *end) 217 | { 218 | return queryFromSystemProfiler(dev, end); 219 | } 220 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 3.10) 2 | project (GCFFlasher VERSION 4.11.0) 3 | 4 | option(USE_NET "Support connection via network sockets" OFF) 5 | option(USE_SNIFF "Support sniffer firmware" ON) 6 | 7 | set(COMMON_SRCS 8 | gcf.c 9 | buffer_helper.c 10 | protocol.c 11 | u_bstream.c 12 | u_sstream.c 13 | u_strlen.c 14 | u_mem.c 15 | net.c 16 | ) 17 | 18 | add_executable(${PROJECT_NAME} ${COMMON_SRCS}) 19 | 20 | if (USE_SNIFF) 21 | target_compile_definitions(${PROJECT_NAME} PRIVATE USE_SNIFF) 22 | endif() 23 | 24 | if (USE_NET) 25 | target_compile_definitions(${PROJECT_NAME} PRIVATE USE_NET) 26 | endif () 27 | 28 | if (USE_NET OR USE_SNIFF) 29 | set(NET_SRCS net_sock.c) 30 | 31 | if (UNIX) 32 | list(APPEND NET_SRCS net_udp_posix.c) 33 | endif () 34 | if (WIN32) 35 | list(APPEND NET_SRCS net_udp_win32.c) 36 | target_link_libraries(${PROJECT_NAME} ws2_32.lib) 37 | endif () 38 | 39 | target_sources(${PROJECT_NAME} 40 | PRIVATE 41 | ${NET_SRCS}) 42 | endif() 43 | 44 | target_compile_definitions(${PROJECT_NAME} 45 | PUBLIC 46 | APP_VERSION="\"\"${PROJECT_VERSION}\"\"") 47 | 48 | if (UNIX) 49 | target_sources(${PROJECT_NAME} PRIVATE main_posix.c) 50 | 51 | if (CMAKE_BUILD_TYPE MATCHES "Debug") 52 | # -Wpedantic -Wconversion 53 | target_compile_options(${PROJECT_NAME} PRIVATE -Wall -Wextra -Wdeprecated) 54 | target_compile_options(${PROJECT_NAME} PRIVATE -fsanitize=undefined -fsanitize=address) 55 | target_link_options(${PROJECT_NAME} BEFORE PUBLIC -fsanitize=undefined PUBLIC -fsanitize=address) 56 | endif() 57 | 58 | #---------------------------------------------------------------------- 59 | if (${CMAKE_HOST_SYSTEM_NAME} MATCHES "Linux") 60 | target_compile_definitions(${PROJECT_NAME} PRIVATE PL_LINUX=1) 61 | target_sources(${PROJECT_NAME} 62 | PRIVATE 63 | linux_get_usb_devices.c) 64 | 65 | find_package(PkgConfig) 66 | pkg_check_modules(GPIOD libgpiod) 67 | if (${GPIOD_FOUND}) 68 | if (${GPIOD_VERSION} VERSION_LESS 2.0) 69 | target_compile_definitions(${PROJECT_NAME} PRIVATE HAS_LIBGPIOD_V1) 70 | target_sources(${PROJECT_NAME} PRIVATE linux_libgpiod_v1_reset.c) 71 | elseif (${GPIOD_VERSION} VERSION_LESS 3.0) 72 | target_compile_definitions(${PROJECT_NAME} PRIVATE HAS_LIBGPIOD_V2) 73 | target_sources(${PROJECT_NAME} PRIVATE linux_libgpiod_v2_reset.c) 74 | endif() 75 | message("gpiod version: ${GPIOD_VERSION}") 76 | target_compile_definitions(${PROJECT_NAME} PRIVATE HAS_LIBGPIOD) 77 | target_link_libraries(${PROJECT_NAME} dl) 78 | 79 | endif() 80 | endif() 81 | 82 | #---------------------------------------------------------------------- 83 | if (APPLE) 84 | target_compile_definitions(${PROJECT_NAME} PRIVATE PL_MAC=1) 85 | target_sources(${PROJECT_NAME} 86 | PRIVATE 87 | macos_get_usb_devices.c) 88 | endif() 89 | endif() 90 | 91 | #---------------------------------------------------------------------- 92 | # https://github.com/open-watcom/open-watcom-v2/wiki/OW-tools-usage-with-CMake 93 | if (DOS) 94 | # if("${CMAKE_C_COMPILER_ID}" MATCHES "OpenWatcom") 95 | 96 | target_compile_definitions(${PROJECT_NAME} PRIVATE 97 | PL_DOS=1 98 | PL_NO_ESCASCII=1 99 | PL_NO_UTF8=1 100 | ) 101 | 102 | set(CMAKE_C_FLAGS "-za99 -w1") 103 | set(CMAKE_C_STANDARD 99) 104 | # add_compile_options(-D_WIN32) 105 | 106 | target_sources(${PROJECT_NAME} PRIVATE ${COMMON_SRCS} main_dos.c) 107 | endif() 108 | 109 | if (WIN32) 110 | option(USE_FTD2XX "Use FTDI ftd2xx library on Windows" OFF) 111 | target_sources(${PROJECT_NAME} PRIVATE main_windows.c) 112 | 113 | if(MINGW) 114 | set(CMAKE_VERBOSE_MAKEFILE ON) 115 | endif() 116 | 117 | if (MSVC) 118 | set(CMAKE_C_FLAGS_DEBUG_INIT "") 119 | set(CMAKE_C_FLAGS_DEBUG "/Od /Zi") 120 | 121 | # set(CMAKE_C_FLAGS_MINSIZEREL_INIT "") 122 | # set(CMAKE_C_FLAGS_MINSIZEREL "/O1 /MT") 123 | 124 | #target_link_options(${PROJECT_NAME} PUBLIC "/NODEFAULTLIB:libcmt") 125 | target_link_options(${PROJECT_NAME} PUBLIC "/NODEFAULTLIB" "/STACK:0x100000,0x100000") 126 | 127 | add_compile_options( 128 | $<$:/MT> #---------| 129 | $<$:/MTd> #---|-- Statically link the runtime libraries 130 | $<$:/MT> #--| 131 | ) 132 | 133 | target_compile_options(${PROJECT_NAME} BEFORE PRIVATE 134 | "/std:c11" 135 | "/GR-" 136 | "/EHa-" 137 | "/GS-" 138 | "/Gs999999999" 139 | ) 140 | 141 | if (MSVC_VERSION GREATER_EQUAL 1700) 142 | set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /utf-8") 143 | endif() 144 | 145 | if (USE_FTD2XX) 146 | if ("${CMAKE_SIZEOF_VOID_P}" STREQUAL "4") 147 | target_link_libraries(${PROJECT_NAME} ${CMAKE_CURRENT_SOURCE_DIR}/ftd2xx/Static/i386/ftd2xx.lib) 148 | else() 149 | target_link_libraries(${PROJECT_NAME} ${CMAKE_CURRENT_SOURCE_DIR}/ftd2xx/Static/amd64/ftd2xx.lib) 150 | endif() 151 | target_compile_definitions(${PROJECT_NAME} PRIVATE 152 | FTD2XX_STATIC 153 | USE_FTD2XX 154 | ) 155 | endif() 156 | endif() 157 | 158 | target_compile_definitions(${PROJECT_NAME} PRIVATE 159 | PL_WIN=1 160 | PL_NO_ESCASCII=1 161 | PL_NO_UTF8=1 162 | ) 163 | 164 | target_link_libraries(${PROJECT_NAME} setupapi shlwapi advapi32) 165 | endif() 166 | 167 | include(GNUInstallDirs) 168 | install(TARGETS ${PROJECT_NAME} 169 | RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} 170 | BUNDLE DESTINATION ${CMAKE_INSTALL_BINDIR}) 171 | 172 | # Debian .deb specifics 173 | set(CPACK_DEBIAN_PACKAGE_MAINTAINER "Manuel Pietschmann ") 174 | set(CPACK_DEBIAN_PACKAGE_SECTION "non-free / misc") 175 | 176 | # keep arch names as used in the past 177 | set(PKG_ARCH ${CMAKE_SYSTEM_PROCESSOR}) 178 | if (${PKG_ARCH} MATCHES "aarch64") 179 | set(PKG_ARCH "arm64") 180 | endif() 181 | if (${PKG_ARCH} MATCHES "armv7l") 182 | set(PKG_ARCH "armhf") 183 | endif() 184 | if (${PKG_ARCH} MATCHES "x86_64") 185 | set(PKG_ARCH "amd64") 186 | endif() 187 | 188 | string(TOLOWER "${CMAKE_SYSTEM_NAME}" LOWERCASE_SYSTEM_NAME) 189 | set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE.txt") 190 | set(CPACK_PACKAGE_HOMEPAGE_URL "https://github.com/dresden-elektronik/gcfflasher") 191 | set(CPACK_PACKAGE_DESCRIPTION "Tool to flash firmware of RaspBee and ConBee.") 192 | set(CPACK_PACKAGE_FILE_NAME "gcfflasher_${PROJECT_VERSION}_${LOWERCASE_SYSTEM_NAME}_${PKG_ARCH}") 193 | 194 | include(CPack) 195 | -------------------------------------------------------------------------------- /net_udp_posix.c: -------------------------------------------------------------------------------- 1 | #ifndef _GNU_SOURCE 2 | #define _GNU_SOURCE 3 | #endif 4 | 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include "u_mem.h" 19 | #include "net_sock.h" 20 | 21 | int SOCK_UdpInit(S_Udp *udp, int af) 22 | { 23 | U_bzero(udp, sizeof(*udp)); 24 | udp->state = S_UDP_STATE_INIT; 25 | udp->addr.af = af; 26 | 27 | if (af == S_AF_IPV4) af = AF_INET; 28 | else if (af == S_AF_IPV6) af = AF_INET6; 29 | else 30 | { 31 | udp->addr.af = S_AF_UNKNOWN; 32 | return 0; 33 | } 34 | 35 | udp->handle = socket(af, SOCK_DGRAM, 0); 36 | 37 | if (udp->handle == -1) 38 | return 0; 39 | 40 | udp->state = S_UDP_STATE_OPEN; 41 | 42 | return 1; 43 | } 44 | 45 | int SOCK_UdpSetPeer(S_Udp *udp, const char *peer, unsigned short port) 46 | { 47 | struct in_addr addr; 48 | struct in6_addr addr6; 49 | 50 | if (inet_pton(AF_INET, peer, &addr) == 1) 51 | { 52 | udp->peer_addr.af = S_AF_IPV4; 53 | udp->peer_port = port; 54 | U_memcpy(udp->peer_addr.data, &addr.s_addr, 4); 55 | return 0; 56 | } 57 | else if (inet_pton(AF_INET6, peer, &addr6) == 1) 58 | { 59 | udp->peer_addr.af = S_AF_IPV6; 60 | udp->peer_port = port; 61 | U_memcpy(udp->peer_addr.data, &addr6, 16); 62 | return 0; 63 | } 64 | 65 | return -1; 66 | } 67 | 68 | int SOCK_UdpBind(S_Udp *udp, unsigned short port) 69 | { 70 | int ret; 71 | int yes = 1; 72 | struct sockaddr_in addr; 73 | struct sockaddr_in6 addr6; 74 | 75 | if (udp->state != S_UDP_STATE_OPEN) 76 | return 0; 77 | 78 | errno = 0; 79 | 80 | /* allow multiple sockets to use the same PORT number */ 81 | if (setsockopt(udp->handle, SOL_SOCKET, SO_REUSEADDR, (char*) &yes, sizeof(yes) ) < 0 ) 82 | goto err; 83 | 84 | if (udp->addr.af == S_AF_IPV4) 85 | { 86 | U_bzero(&addr, sizeof(addr)); 87 | addr.sin_family = AF_INET; 88 | addr.sin_addr.s_addr = htonl(INADDR_ANY); 89 | addr.sin_port = htons(port); 90 | ret = bind(udp->handle, (struct sockaddr*) &addr, sizeof(addr)); 91 | 92 | if (ret == -1) 93 | goto err; 94 | 95 | udp->port = port; 96 | return 1; 97 | } 98 | else if (udp->addr.af == S_AF_IPV6) 99 | { 100 | U_bzero(&addr6, sizeof(addr6)); 101 | addr6.sin6_family = AF_INET6; 102 | addr6.sin6_addr = in6addr_any; 103 | addr6.sin6_port = htons(port); 104 | ret = bind(udp->handle, (struct sockaddr*) &addr6, sizeof(addr6)); 105 | 106 | if (ret == -1) 107 | goto err; 108 | 109 | udp->port = port; 110 | return 1; 111 | } 112 | 113 | err: 114 | udp->state = S_UDP_STATE_ERROR; 115 | if (errno) 116 | { 117 | fprintf(stderr, "udp bind port: %u failed: %s\n", (unsigned)port, strerror(errno)); 118 | } 119 | return 0; 120 | } 121 | 122 | int SOCK_UdpJoinMulticast(S_Udp *udp, const char *maddr) 123 | { 124 | struct ip_mreq mreq; 125 | struct ipv6_mreq mreq6; 126 | 127 | if (udp->state != S_UDP_STATE_OPEN) 128 | return -1; 129 | 130 | if (udp->addr.af == S_AF_IPV4) 131 | { 132 | mreq.imr_multiaddr.s_addr = inet_addr(maddr); 133 | mreq.imr_interface.s_addr = htonl(INADDR_ANY); 134 | 135 | if (setsockopt(udp->handle, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) < 0) 136 | goto err; 137 | 138 | return 0; 139 | } 140 | else if (udp->addr.af == S_AF_IPV6) 141 | { 142 | mreq6.ipv6mr_interface = 0; 143 | inet_pton(AF_INET6, maddr, &mreq6.ipv6mr_multiaddr); 144 | #ifdef PL_MAC 145 | if (setsockopt(udp->handle, IPPROTO_IPV6, IPV6_JOIN_GROUP, &mreq6, sizeof(mreq6)) < 0) 146 | #else 147 | if (setsockopt(udp->handle, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, &mreq6, sizeof(mreq6)) < 0) 148 | #endif 149 | goto err; 150 | 151 | return 0; 152 | } 153 | 154 | err: 155 | udp->state = S_UDP_STATE_ERROR; 156 | return -1; 157 | } 158 | 159 | int SOCK_UdpSend(S_Udp *udp, unsigned char *buf, unsigned bufsize) 160 | { 161 | ssize_t n; 162 | struct sockaddr_in dest_addr; 163 | struct sockaddr_in6 dest_addr6; 164 | 165 | if (udp->peer_addr.af == S_AF_IPV4) 166 | { 167 | dest_addr.sin_family = AF_INET; 168 | U_memcpy ( (char *) &dest_addr.sin_addr.s_addr, udp->peer_addr.data, 4); 169 | dest_addr.sin_port = htons (udp->peer_port); 170 | 171 | n = sendto(udp->handle, buf, (size_t)bufsize, 0 /* flags */, (struct sockaddr*)&dest_addr, sizeof(dest_addr)); 172 | } 173 | else if (udp->peer_addr.af == S_AF_IPV6) 174 | { 175 | dest_addr6.sin6_family = AF_INET6; 176 | U_memcpy ( (char *) &dest_addr6.sin6_addr, udp->peer_addr.data, 16); 177 | dest_addr6.sin6_port = htons (udp->peer_port); 178 | 179 | n = sendto(udp->handle, buf, (size_t)bufsize, 0 /* flags */, (struct sockaddr*)&dest_addr6, sizeof(dest_addr6)); 180 | } 181 | else 182 | { 183 | return -1; 184 | } 185 | 186 | if (n < 0) 187 | { 188 | 189 | return -1; 190 | } 191 | 192 | return (int)n; 193 | } 194 | 195 | int SOCK_UdpRecv(S_Udp *udp, unsigned char *buf, unsigned bufsize) 196 | { 197 | ssize_t n; 198 | fd_set readfds; 199 | struct timeval tv; 200 | socklen_t addr_len; 201 | struct sockaddr_in *sa4; 202 | struct sockaddr_in6 *sa6; 203 | struct sockaddr_storage addr; 204 | char abuf[INET6_ADDRSTRLEN]; 205 | 206 | if (udp->state != S_UDP_STATE_OPEN) 207 | return -1; 208 | 209 | /* TODO non blocking */ 210 | tv.tv_sec = 0; 211 | tv.tv_usec = 5 * 1000; // 5 msec 212 | FD_ZERO(&readfds); 213 | FD_SET(udp->handle, &readfds); 214 | // don't care about writefds and exceptfds: 215 | select(udp->handle+1, &readfds, NULL, NULL, &tv); 216 | if (FD_ISSET(udp->handle, &readfds)) 217 | { 218 | addr_len = sizeof(addr); 219 | n = recvfrom(udp->handle, buf, bufsize, 0, (struct sockaddr*)&addr, &addr_len); 220 | 221 | if (addr.ss_family == AF_INET6) 222 | { 223 | sa6 = (struct sockaddr_in6*)&addr; 224 | udp->peer_addr.af = S_AF_IPV6; 225 | udp->peer_port = sa6->sin6_port; 226 | U_memcpy(&udp->peer_addr.data[0], &sa6->sin6_addr.s6_addr[0], 16); 227 | } 228 | else if (addr.ss_family == AF_INET) 229 | { 230 | sa4 = (struct sockaddr_in*)&addr; 231 | udp->peer_addr.af = S_AF_IPV4; 232 | udp->peer_port = sa4->sin_port; 233 | U_memcpy(&udp->peer_addr.data[0], &sa4->sin_addr.s_addr, 4); 234 | } 235 | else 236 | { 237 | return -1; 238 | } 239 | 240 | if (inet_ntop(addr.ss_family, (void*)&udp->peer_addr.data[0], &abuf[0], sizeof(abuf))) 241 | { 242 | fprintf(stderr, "UDP peer %s port: %u\n", &abuf[0], ntohs(udp->peer_port)); 243 | } 244 | 245 | if (n > 0 && n < (ssize_t)bufsize) 246 | { 247 | return (int)n; 248 | } 249 | else if (n < 0) 250 | { 251 | fprintf(stderr, "UDP error %s\n", strerror(errno)); 252 | return -1; 253 | } 254 | } 255 | 256 | return 0; 257 | } 258 | 259 | void SOCK_UdpFree(S_Udp *udp) 260 | { 261 | if (udp->handle) 262 | close(udp->handle); 263 | 264 | U_bzero(udp, sizeof(*udp)); 265 | udp->state = S_UDP_STATE_INIT; 266 | } 267 | -------------------------------------------------------------------------------- /linux_libgpiod_v1_reset.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021-2025 dresden elektronik ingenieurtechnik gmbh. 3 | * All rights reserved. 4 | * 5 | * The software in this package is published under the terms of the BSD 6 | * style license a copy of which has been included with this distribution in 7 | * the LICENSE.txt file. 8 | * 9 | */ 10 | 11 | #ifdef HAS_LIBGPIOD 12 | #include 13 | #include 14 | #include 15 | #include "gcf.h" 16 | 17 | /* Implementation for libgpiod version < 2.x */ 18 | /* 19 | /sys/bus/usb/drivers/cdc_acm 20 | /sys/class/tty/ttyACM0/device 21 | /sys/devices/pci0000:00/0000:00:14.0/usb1/1-4/1-4.4/serial 22 | */ 23 | 24 | typedef struct gpiod_chip_iter *(*pl_gpiod_chip_iter_new)(void); 25 | typedef struct gpiod_chip *(*pl_gpiod_chip_iter_next)(struct gpiod_chip_iter *iter); 26 | typedef const char *(*pl_gpiod_chip_name)(struct gpiod_chip *chip); 27 | typedef const char *(*pl_gpiod_chip_label)(struct gpiod_chip *chip); 28 | typedef struct gpiod_line *(*pl_gpiod_chip_get_line)(struct gpiod_chip *chip, unsigned int offset); 29 | typedef int (*pl_gpiod_line_request_output)(struct gpiod_line *line, const char *consumer, int default_val); 30 | typedef int (*pl_gpiod_line_request_input)(struct gpiod_line *line, const char *consumer); 31 | typedef int (*pl_gpiod_line_set_value)(struct gpiod_line *line, int value); 32 | typedef void (*pl_gpiod_line_release)(struct gpiod_line *line); 33 | typedef void (*pl_gpiod_chip_iter_free)(struct gpiod_chip_iter *iter); 34 | 35 | static void* lib_gpiod_handle; 36 | static pl_gpiod_chip_iter_new fn_gpiod_chip_iter_new; 37 | static pl_gpiod_chip_iter_next fn_gpiod_chip_iter_next; 38 | static pl_gpiod_chip_iter_free fn_gpiod_chip_iter_free; 39 | static pl_gpiod_chip_name fn_gpiod_chip_name; 40 | static pl_gpiod_chip_label fn_gpiod_chip_label; 41 | static pl_gpiod_chip_get_line fn_gpiod_chip_get_line; 42 | static pl_gpiod_line_request_output fn_gpiod_line_request_output; 43 | static pl_gpiod_line_request_input fn_gpiod_line_request_input; 44 | static pl_gpiod_line_set_value fn_gpiod_line_set_value; 45 | static pl_gpiod_line_release fn_gpiod_line_release; 46 | 47 | static int plLoadLibGpiod(void); 48 | static int plUnloadLibGpiod(void); 49 | 50 | static int plLoadLibGpiod(void) 51 | { 52 | Assert(lib_gpiod_handle == NULL); 53 | 54 | lib_gpiod_handle = dlopen("libgpiod.so", RTLD_LAZY); 55 | if (!lib_gpiod_handle) 56 | { 57 | PL_Printf(DBG_DEBUG, "failed to open libgpiod.so\n"); 58 | return -1; 59 | } 60 | 61 | fn_gpiod_chip_iter_new = (pl_gpiod_chip_iter_new)dlsym(lib_gpiod_handle, "gpiod_chip_iter_new"); 62 | fn_gpiod_chip_iter_next = (pl_gpiod_chip_iter_next)dlsym(lib_gpiod_handle, "gpiod_chip_iter_next"); 63 | fn_gpiod_chip_iter_free = (pl_gpiod_chip_iter_free)dlsym(lib_gpiod_handle, "gpiod_chip_iter_free"); 64 | fn_gpiod_chip_name = (pl_gpiod_chip_name)dlsym(lib_gpiod_handle, "gpiod_chip_name"); 65 | fn_gpiod_chip_label = (pl_gpiod_chip_label)dlsym(lib_gpiod_handle, "gpiod_chip_label"); 66 | fn_gpiod_chip_get_line = (pl_gpiod_chip_get_line)dlsym(lib_gpiod_handle, "gpiod_chip_get_line"); 67 | fn_gpiod_line_request_output = (pl_gpiod_line_request_output)dlsym(lib_gpiod_handle, "gpiod_line_request_output"); 68 | fn_gpiod_line_request_input = (pl_gpiod_line_request_input)dlsym(lib_gpiod_handle, "gpiod_line_request_input"); 69 | fn_gpiod_line_set_value = (pl_gpiod_line_set_value)dlsym(lib_gpiod_handle, "gpiod_line_set_value"); 70 | fn_gpiod_line_release = (pl_gpiod_line_release)dlsym(lib_gpiod_handle, "gpiod_line_release"); 71 | 72 | if (!fn_gpiod_chip_iter_new || 73 | !fn_gpiod_chip_iter_next || 74 | !fn_gpiod_chip_iter_free || 75 | !fn_gpiod_chip_name || 76 | !fn_gpiod_chip_label || 77 | !fn_gpiod_chip_get_line || 78 | !fn_gpiod_line_request_output || 79 | !fn_gpiod_line_request_input || 80 | !fn_gpiod_line_set_value || 81 | !fn_gpiod_line_release) 82 | { 83 | plUnloadLibGpiod(); 84 | return -1; 85 | } 86 | 87 | return 0; 88 | } 89 | 90 | static int plUnloadLibGpiod(void) 91 | { 92 | Assert(lib_gpiod_handle != NULL); 93 | 94 | if (lib_gpiod_handle) 95 | { 96 | dlclose(lib_gpiod_handle); 97 | lib_gpiod_handle = NULL; 98 | fn_gpiod_chip_iter_new = NULL; 99 | fn_gpiod_chip_iter_next = NULL; 100 | fn_gpiod_chip_iter_free = NULL; 101 | fn_gpiod_chip_name = NULL; 102 | fn_gpiod_chip_label = NULL; 103 | fn_gpiod_chip_get_line = NULL; 104 | fn_gpiod_line_request_output = NULL; 105 | fn_gpiod_line_request_input = NULL; 106 | fn_gpiod_line_set_value = NULL; 107 | fn_gpiod_line_release = NULL; 108 | return 0; 109 | } 110 | 111 | return -1; 112 | } 113 | 114 | int plResetRaspBeeLibGpiod(void) 115 | { 116 | int ret = -1; 117 | struct gpiod_chip *chip; 118 | struct gpiod_chip_iter *iter; 119 | struct gpiod_line *line; 120 | const char *name; 121 | const char *label; 122 | 123 | if (plLoadLibGpiod() != 0) 124 | { 125 | return -1; 126 | } 127 | 128 | iter = fn_gpiod_chip_iter_new(); 129 | 130 | if (!iter) 131 | { 132 | plUnloadLibGpiod(); 133 | return -2; 134 | } 135 | 136 | while ((chip = fn_gpiod_chip_iter_next(iter)) != NULL) 137 | { 138 | name = fn_gpiod_chip_name(chip); 139 | label = fn_gpiod_chip_label(chip); 140 | 141 | if (!label || strncmp(label, "pinctrl-", 8) != 0) 142 | { 143 | continue; 144 | } 145 | 146 | /* https://pinout.xyz/pinout/raspbee 147 | RaspBee reset pin on gpio17 148 | */ 149 | line = fn_gpiod_chip_get_line(chip, 17); 150 | 151 | if (!line) 152 | { 153 | continue; 154 | } 155 | 156 | PL_Printf(DBG_DEBUG, "gpiod chip: name: %s, label: %s\n", name, label); 157 | 158 | ret = fn_gpiod_line_request_output(line, "gcf", 1); 159 | Assert(ret != -1); 160 | 161 | ret = fn_gpiod_line_set_value(line, 0); 162 | Assert(ret != -1); 163 | 164 | PL_MSleep(200); 165 | 166 | ret = fn_gpiod_line_set_value(line, 1); 167 | Assert(ret != -1); 168 | 169 | fn_gpiod_line_release(line); 170 | 171 | ret = fn_gpiod_line_request_input(line, "gcf"); 172 | Assert(ret != -1); 173 | 174 | fn_gpiod_line_release(line); 175 | 176 | ret = 0; 177 | break; 178 | } 179 | 180 | fn_gpiod_chip_iter_free(iter); 181 | 182 | plUnloadLibGpiod(); 183 | 184 | return ret; 185 | } 186 | 187 | int plResetFtdiLibGpiod(void) 188 | { 189 | int ret = -1; 190 | struct gpiod_chip *chip; 191 | struct gpiod_chip_iter *iter; 192 | struct gpiod_line *line; 193 | const char *name; 194 | const char *label; 195 | 196 | if (plLoadLibGpiod() != 0) 197 | { 198 | return -1; 199 | } 200 | 201 | iter = fn_gpiod_chip_iter_new(); 202 | 203 | if (!iter) 204 | { 205 | plUnloadLibGpiod(); 206 | return -2; 207 | } 208 | 209 | while ((chip = fn_gpiod_chip_iter_next(iter)) != NULL) 210 | { 211 | name = fn_gpiod_chip_name(chip); 212 | label = fn_gpiod_chip_label(chip); 213 | 214 | if (!label || strcmp(label, "ftdi-cbus") != 0) 215 | { 216 | continue; 217 | } 218 | 219 | line = fn_gpiod_chip_get_line(chip, 0); /* CBUS0 */ 220 | 221 | if (!line) 222 | { 223 | continue; 224 | } 225 | 226 | PL_Printf(DBG_DEBUG, "gpiod chip: name: %s, label: %s\n", name, label); 227 | 228 | /* toggle CBUS0 which is connected to MCU reset */ 229 | 230 | ret = fn_gpiod_line_request_output(line, "gcf", 0); 231 | Assert(ret != -1); 232 | 233 | ret = fn_gpiod_line_set_value(line, 1); 234 | Assert(ret != -1); 235 | 236 | ret = fn_gpiod_line_set_value(line, 0); 237 | Assert(ret != -1); 238 | 239 | ret = fn_gpiod_line_set_value(line, 1); 240 | Assert(ret != -1); 241 | 242 | fn_gpiod_line_release(line); 243 | 244 | ret = 0; 245 | break; 246 | } 247 | 248 | fn_gpiod_chip_iter_free(iter); 249 | 250 | plUnloadLibGpiod(); 251 | 252 | return ret; 253 | } 254 | #endif 255 | -------------------------------------------------------------------------------- /main_dos.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021-2023 dresden elektronik ingenieurtechnik gmbh. 3 | * All rights reserved. 4 | * 5 | * The software in this package is published under the terms of the BSD 6 | * style license a copy of which has been included with this distribution in 7 | * the LICENSE.txt file. 8 | * 9 | */ 10 | 11 | /* DOS port out of curiosity 12 | * 13 | * cmake -G "Watcom WMake" -D CMAKE_SYSTEM_NAME=DOS -D CMAKE_SYSTEM_PROCESSOR=x86 -B build . 14 | * 15 | */ 16 | 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | #include "gcf.h" 26 | #include "u_sstream.h" 27 | #include "u_strlen.h" 28 | 29 | typedef struct 30 | { 31 | PL_time_t timer; 32 | volatile unsigned long time; 33 | 34 | uint8_t running; 35 | uint8_t rx_buf[1024]; 36 | uint8_t txbuf[2048]; 37 | size_t txpos; 38 | 39 | volatile int rx_head; 40 | volatile int rx_tail; 41 | 42 | unsigned short com_port; 43 | unsigned short com_int; 44 | unsigned short com_int_vect; 45 | 46 | GCF *gcf; 47 | } PL_Internal; 48 | 49 | static PL_Internal platform; 50 | 51 | /*! Returns a monotonic time in milliseconds. */ 52 | PL_time_t PL_Time(void) 53 | { 54 | return platform.time; 55 | } 56 | 57 | /*! Lets the programm sleep for \p ms milliseconds. */ 58 | void PL_MSleep(unsigned long ms) 59 | { 60 | delay((int)ms); 61 | } 62 | 63 | 64 | /*! Sets a timeout \p ms in milliseconds, after which a \c EV_TIMOUT event is generated. */ 65 | void PL_SetTimeout(unsigned long ms) 66 | { 67 | platform.timer = PL_Time() + ms; 68 | } 69 | 70 | /*! Clears an active timeout. */ 71 | void PL_ClearTimeout(void) 72 | { 73 | platform.timer = 0; 74 | } 75 | 76 | /* Fills up to \p max devices in the \p devs array. 77 | 78 | The output is used in list operation (-l). 79 | */ 80 | int PL_GetDevices(Device *devs, unsigned max) 81 | { 82 | int result; 83 | 84 | (void)max; 85 | 86 | result = 1; 87 | 88 | devs->name[0] = 'C'; 89 | devs->name[1] = 'O'; 90 | devs->name[2] = 'M'; 91 | devs->name[3] = '1'; 92 | devs->name[4] = '\0'; 93 | 94 | devs->serial[0] = '1'; 95 | devs->serial[1] = '\0'; 96 | 97 | devs->baudrate = 115200; 98 | 99 | devs->path[0] = 'C'; 100 | devs->path[1] = 'O'; 101 | devs->path[2] = 'M'; 102 | devs->path[3] = '1'; 103 | devs->path[4] = '\0'; 104 | 105 | return result; 106 | } 107 | 108 | #define COM1_BASE_ADDR 0x3F8 109 | #define COM2_BASE_ADDR 0x2F8 110 | #define COM3_BASE_ADDR 0x3E8 111 | #define COM4_BASE_ADDR 0x2E8 112 | 113 | #define COM1_INT 4 114 | #define COM2_INT 3 115 | #define COM3_INT 4 116 | #define COM4_INT 3 117 | 118 | /* COM1 - 0x0C, COM2 - 0x0B, COM3 - 0x0C, COM4 - 0x0B */ 119 | #define COM1_INT_VECT 0x0C 120 | #define COM2_INT_VECT 0x0B 121 | #define COM3_INT_VECT 0x0C 122 | #define COM4_INT_VECT 0x0B 123 | 124 | void interrupt (*oldportisr)(); 125 | 126 | static int rx_count; 127 | void __interrupt __far port_isr() 128 | { 129 | int c; 130 | 131 | if (platform.rx_tail == platform.rx_head) 132 | { 133 | platform.rx_head = 0; 134 | platform.rx_tail = 0; 135 | } 136 | 137 | do { 138 | /* get the content of the LSR (line status register) 139 | if Bit 0 of LSR is set, than one or more data bytes are available */ 140 | c = inp(platform.com_port + 5); 141 | if (c & 1) { /* check Bit 0 */ 142 | platform.rx_buf[platform.rx_tail] = (unsigned char)inp(platform.com_port); /* get the character and store it in the buffer */ 143 | platform.rx_tail = (platform.rx_tail + 1) & (sizeof(platform.rx_buf) - 1); 144 | rx_count++; 145 | } 146 | } while (c & 1); /* while data ready */ 147 | 148 | outp(0x20,0x20); /* clear the interrupt */ 149 | } 150 | 151 | /*! Opens the serial port connection for device. 152 | 153 | https://zeiner.at/informatik/c/serialport.html 154 | 155 | \param path - The path like /dev/ttyACM0 or COM7. 156 | \returns GCF_SUCCESS or GCF_FAILED 157 | */ 158 | GCF_Status PL_Connect(const char *path, PL_Baudrate baudrate) 159 | { 160 | PL_Printf(DBG_INFO, "PL_Connect: %s\n", path); 161 | 162 | unsigned short port; 163 | 164 | platform.com_port = COM4_BASE_ADDR; 165 | platform.com_int = COM4_INT; 166 | platform.com_int_vect = COM4_INT_VECT; 167 | 168 | port = platform.com_port; 169 | 170 | outp(port + 1 , 0); /* Turn off interrupts */ 171 | oldportisr = _dos_getvect(platform.com_int_vect); /* Save old Interrupt Vector of later recovery */ 172 | 173 | /* Set Interrupt Vector Entry */ 174 | /* COM1 - 0x0C, COM2 - 0x0B, COM3 - 0x0C, COM4 - 0x0B */ 175 | _dos_setvect(platform.com_int_vect, port_isr); 176 | 177 | /* PORT 1 - Communication Settings */ 178 | 179 | outp(port + 3 , 0x80); /* SET DLAB ON */ 180 | outp(port + 0 , 0x01); /* Set Baud rate - Divisor Latch Low Byte */ 181 | /* 0x01 = 115,200 BPS */ 182 | /* 0x03 = 38,400 BPS */ 183 | outp(port + 1 , 0x00); /* Set Baud rate - Divisor Latch High Byte */ 184 | outp(port + 3 , 0x03); /* 8 Bits, No Parity, 1 Stop Bit */ 185 | outp(port + 2 , 0xC7); /* FIFO Control Register */ 186 | // outp(port + 2 , 0x07); /* clear FIFO */ 187 | // outp(port + 2 , 0x00); /* disable FIFO */ 188 | 189 | outp(port + 4 , 0x0B); /* Turn on DTR, RTS, and OUT2 */ 190 | /* Set Programmable Interrupt Controller */ 191 | /* COM1, COM3 (IRQ4) - 0xEF */ 192 | /* COM2, COM4 (IRQ3) - 0xF7 */ 193 | 194 | if (port == COM1_BASE_ADDR || port == COM3_BASE_ADDR) 195 | outp(0x21,(inp(0x21) & 0xEF)); 196 | else 197 | outp(0x21,(inp(0x21) & 0xF7)); 198 | 199 | outp(port + 1 , 0x01); /* Interrupt when data received */ 200 | 201 | return GCF_SUCCESS; 202 | } 203 | 204 | /*! Closed the serial port connection. */ 205 | void PL_Disconnect(void) 206 | { 207 | PL_Printf(DBG_DEBUG, "PL_Disconnect\n"); 208 | platform.txpos = 0; 209 | 210 | if (platform.com_port) 211 | { 212 | outp(platform.com_port + 1 , 0); /* Turn off interrupts */ 213 | /* COM1 und COM3 (IRQ4) - 0x10 */ 214 | /* COM2 unf COM4 (IRQ3) - 0x08 */ 215 | if (platform.com_port == COM1_BASE_ADDR || platform.com_port == COM3_BASE_ADDR) 216 | outp(0x21, (inp(0x21) | 0x10)); /* MASK IRQ using PIC */ 217 | else 218 | outp(0x21, (inp(0x21) | 0x08)); /* MASK IRQ using PIC */ 219 | 220 | platform.com_port = 0; 221 | platform.com_int_vect = 0; 222 | platform.com_int = 0; 223 | } 224 | 225 | GCF_HandleEvent(platform.gcf, EV_DISCONNECTED); 226 | } 227 | 228 | /*! Shuts down platform layer (ends main loop). */ 229 | void PL_ShutDown(void) 230 | { 231 | platform.running = 0; 232 | } 233 | 234 | /*! Executes a MCU reset for ConBee I via FTDI CBUS0 reset. */ 235 | int PL_ResetFTDI(int num, const char *serialnum) 236 | { 237 | return -1; 238 | } 239 | 240 | /*! Executes a MCU reset for RaspBee I / II via GPIO17 reset pin. */ 241 | int PL_ResetRaspBee(void) 242 | { 243 | return -1; 244 | } 245 | 246 | int PL_ReadFile(const char *path, unsigned char *buf, unsigned long buflen) 247 | { 248 | int result = -1; 249 | 250 | return result; 251 | } 252 | 253 | 254 | void PL_Print(const char *line) 255 | { 256 | printf("%s", line); 257 | } 258 | 259 | void PL_Printf(DebugLevel level, const char *format, ...) 260 | { 261 | #ifdef NDEBUG 262 | if (level == DBG_DEBUG) 263 | { 264 | // return; 265 | } 266 | #else 267 | (void)level; 268 | #endif 269 | va_list args; 270 | va_start (args, format); 271 | vprintf(format, args); 272 | va_end (args); 273 | } 274 | 275 | void UI_GetWinSize(unsigned *w, unsigned *h) 276 | { 277 | // TODO 278 | *w = 80; 279 | *h = 60; 280 | } 281 | 282 | void UI_SetCursor(unsigned x, unsigned y) 283 | { 284 | (void)x; 285 | (void)y; 286 | } 287 | 288 | 289 | int PROT_Write(const unsigned char *data, unsigned len) 290 | { 291 | int n; 292 | n = 0; 293 | 294 | if (platform.com_port == 0) 295 | return 0; 296 | 297 | gcfDebugHex(platform.gcf, "send", data, len); 298 | 299 | for (; len != 0; len--) 300 | { 301 | outp(platform.com_port, (int)*data); 302 | data++; 303 | n++; 304 | } 305 | 306 | return n; 307 | } 308 | 309 | int PROT_Putc(unsigned char ch) 310 | { 311 | Assert(platform.txpos + 1 < sizeof(platform.txbuf)); 312 | if (platform.txpos + 1 < sizeof(platform.txbuf)) 313 | { 314 | platform.txbuf[platform.txpos] = ch; 315 | platform.txpos += 1; 316 | return 1; 317 | } 318 | return 0; 319 | } 320 | 321 | int PROT_Flush(void) 322 | { 323 | int result = 0; 324 | 325 | if (platform.txpos != 0 && platform.txpos < sizeof(platform.txbuf)) 326 | { 327 | result = PROT_Write(&platform.txbuf[0], (unsigned)platform.txpos); 328 | Assert(result == (int)platform.txpos); /* support/handle partial writes? */ 329 | platform.txpos = 0; 330 | } 331 | 332 | return result; 333 | } 334 | 335 | void (__interrupt __far *prev_int_1c)(); 336 | 337 | void __interrupt __far timer_rtn() 338 | { 339 | platform.time += (1000/18); /* 18.2 per second pulses */ 340 | _chain_intr( prev_int_1c ); 341 | } 342 | 343 | static void PL_Loop(GCF *gcf) 344 | { 345 | unsigned pos; 346 | unsigned char buf[128]; 347 | platform.gcf = gcf; 348 | platform.running = 1; 349 | 350 | /* hook up timer */ 351 | prev_int_1c = _dos_getvect( 0x1c ); 352 | _dos_setvect( 0x1c, timer_rtn ); 353 | 354 | GCF_HandleEvent(gcf, EV_PL_STARTED); 355 | 356 | while (platform.running) 357 | { 358 | GCF_HandleEvent(gcf, EV_PL_LOOP); 359 | 360 | outp(platform.com_port + 1 , 0); /* Turn off interrupts */ 361 | if (rx_count) 362 | { 363 | PL_Printf(DBG_INFO, "rx count: %d\n", rx_count); 364 | rx_count = 0; 365 | } 366 | for (pos = 0; platform.rx_head != platform.rx_tail && pos < sizeof(buf); pos++) 367 | { 368 | buf[pos] = platform.rx_buf[platform.rx_head]; 369 | platform.rx_head = (platform.rx_head + 1) & sizeof(platform.rx_buf) - 1; 370 | } 371 | outp(platform.com_port + 1 , 1); /* Turn on interrupts */ 372 | 373 | if (pos) 374 | { 375 | GCF_Received(gcf, buf, pos); 376 | } 377 | 378 | if (platform.timer != 0) 379 | { 380 | if (platform.timer < PL_Time()) 381 | { 382 | platform.timer = 0; 383 | GCF_HandleEvent(gcf, EV_TIMEOUT); 384 | } 385 | } 386 | 387 | delay(5); 388 | 389 | if (platform.time > 30000) 390 | platform.running = 0; 391 | } 392 | 393 | PL_Disconnect(); 394 | 395 | _dos_setvect( 0x1c, prev_int_1c ); 396 | } 397 | 398 | int main(int argc, char **argv) 399 | { 400 | GCF *gcf = GCF_Init(argc, argv); 401 | if (gcf == NULL) 402 | return 2; 403 | 404 | PL_Loop(gcf); 405 | 406 | GCF_Exit(gcf); 407 | 408 | return 0; 409 | } 410 | -------------------------------------------------------------------------------- /linux_libgpiod_v2_reset.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021-2025 dresden elektronik ingenieurtechnik gmbh. 3 | * All rights reserved. 4 | * 5 | * The software in this package is published under the terms of the BSD 6 | * style license a copy of which has been included with this distribution in 7 | * the LICENSE.txt file. 8 | * 9 | */ 10 | 11 | #ifdef HAS_LIBGPIOD 12 | 13 | #include 14 | #include 15 | #include 16 | #include "u_sstream.h" 17 | #include "gcf.h" 18 | 19 | /* Implementation for libgpiod version >= 2.x */ 20 | 21 | /* 22 | /sys/bus/usb/drivers/cdc_acm 23 | /sys/class/tty/ttyACM0/device 24 | /sys/devices/pci0000:00/0000:00:14.0/usb1/1-4/1-4.4/serial 25 | */ 26 | 27 | typedef const char *(*pl_gpiod_api_version)(void); 28 | typedef struct gpiod_chip *(*pl_gpiod_chip_open)(const char *path); 29 | typedef void (*pl_gpiod_chip_close)(struct gpiod_chip *chip); 30 | typedef struct gpiod_chip_info *(*pl_gpiod_chip_get_info)(struct gpiod_chip *chip); 31 | typedef void (*pl_gpiod_chip_info_free)(struct gpiod_chip_info *info); 32 | typedef const char *(*pl_gpiod_chip_info_get_label)(struct gpiod_chip_info *info); 33 | typedef struct gpiod_line_request *(*pl_gpiod_chip_request_lines)(struct gpiod_chip *chip, struct gpiod_request_config *req_cfg, struct gpiod_line_config *line_cfg); 34 | typedef struct gpiod_line_config *(*pl_gpiod_line_config_new)(void); 35 | typedef void (*pl_gpiod_line_config_free)(struct gpiod_line_config *config); 36 | typedef void (*pl_gpiod_line_request_release)(struct gpiod_line_request *request); 37 | typedef struct gpiod_line_settings *(*pl_gpiod_line_settings_new)(void); 38 | typedef void (*pl_gpiod_line_settings_free)(struct gpiod_line_settings *settings); 39 | typedef int (*pl_gpiod_line_settings_set_direction)(struct gpiod_line_settings *settings, enum gpiod_line_direction direction); 40 | typedef int (*pl_gpiod_line_settings_set_output_value)(struct gpiod_line_settings *settings, enum gpiod_line_value value); 41 | typedef int (*pl_gpiod_line_config_add_line_settings)(struct gpiod_line_config *config, const unsigned int *offsets, size_t num_offsets, struct gpiod_line_settings *settings); 42 | typedef struct gpiod_request_config *(*pl_gpiod_request_config_new)(void); 43 | typedef void (*pl_gpiod_request_config_free)(struct gpiod_request_config *config); 44 | typedef void (*pl_gpiod_request_config_set_consumer)(struct gpiod_request_config *config, const char *consumer); 45 | typedef int (*pl_gpiod_line_request_set_value)(struct gpiod_line_request *request, unsigned int offset, enum gpiod_line_value value); 46 | 47 | static void* lib_gpiod_handle; 48 | static pl_gpiod_api_version fn_gpiod_api_version; 49 | static pl_gpiod_chip_open fn_gpiod_chip_open; 50 | static pl_gpiod_chip_close fn_gpiod_chip_close; 51 | static pl_gpiod_chip_get_info fn_gpiod_chip_get_info; 52 | static pl_gpiod_chip_info_free fn_gpiod_chip_info_free; 53 | static pl_gpiod_chip_info_get_label fn_gpiod_chip_info_get_label; 54 | static pl_gpiod_chip_request_lines fn_gpiod_chip_request_lines; 55 | static pl_gpiod_line_config_new fn_gpiod_line_config_new; 56 | static pl_gpiod_line_config_free fn_gpiod_line_config_free; 57 | static pl_gpiod_line_request_release fn_gpiod_line_request_release; 58 | static pl_gpiod_line_settings_new fn_gpiod_line_settings_new; 59 | static pl_gpiod_line_settings_free fn_gpiod_line_settings_free; 60 | static pl_gpiod_line_settings_set_direction fn_gpiod_line_settings_set_direction; 61 | static pl_gpiod_line_settings_set_output_value fn_gpiod_line_settings_set_output_value; 62 | static pl_gpiod_line_config_add_line_settings fn_gpiod_line_config_add_line_settings; 63 | 64 | static pl_gpiod_request_config_new fn_gpiod_request_config_new; 65 | static pl_gpiod_request_config_free fn_gpiod_request_config_free; 66 | static pl_gpiod_request_config_set_consumer fn_gpiod_request_config_set_consumer; 67 | static pl_gpiod_line_request_set_value fn_gpiod_line_request_set_value; 68 | 69 | static int plLoadLibGpiod(void); 70 | static int plUnloadLibGpiod(void); 71 | 72 | static int plLoadLibGpiod(void) 73 | { 74 | Assert(lib_gpiod_handle == NULL); 75 | 76 | lib_gpiod_handle = dlopen("libgpiod.so", RTLD_LAZY); 77 | if (!lib_gpiod_handle) 78 | { 79 | PL_Printf(DBG_DEBUG, "failed to open libgpiod.so\n"); 80 | return -1; 81 | } 82 | 83 | fn_gpiod_api_version = (pl_gpiod_api_version)dlsym(lib_gpiod_handle, "gpiod_api_version"); 84 | 85 | if (!fn_gpiod_api_version) 86 | { 87 | return -1; 88 | } 89 | 90 | PL_Printf(DBG_DEBUG, "gpiod version %s\n", fn_gpiod_api_version()); 91 | 92 | fn_gpiod_chip_open = (pl_gpiod_chip_open)dlsym(lib_gpiod_handle, "gpiod_chip_open"); 93 | fn_gpiod_chip_close = (pl_gpiod_chip_close)dlsym(lib_gpiod_handle, "gpiod_chip_close"); 94 | fn_gpiod_chip_get_info = (pl_gpiod_chip_get_info)dlsym(lib_gpiod_handle, "gpiod_chip_get_info"); 95 | fn_gpiod_chip_info_free = (pl_gpiod_chip_info_free)dlsym(lib_gpiod_handle, "gpiod_chip_info_free"); 96 | fn_gpiod_chip_info_get_label = (pl_gpiod_chip_info_get_label)dlsym(lib_gpiod_handle, "gpiod_chip_info_get_label"); 97 | fn_gpiod_chip_request_lines = (pl_gpiod_chip_request_lines)dlsym(lib_gpiod_handle, "gpiod_chip_request_lines"); 98 | fn_gpiod_line_config_new = (pl_gpiod_line_config_new)dlsym(lib_gpiod_handle, "gpiod_line_config_new"); 99 | fn_gpiod_line_config_free = (pl_gpiod_line_config_free)dlsym(lib_gpiod_handle, "gpiod_line_config_free"); 100 | fn_gpiod_line_request_release = (pl_gpiod_line_request_release)dlsym(lib_gpiod_handle, "gpiod_line_request_release"); 101 | 102 | fn_gpiod_line_settings_new = (pl_gpiod_line_settings_new)dlsym(lib_gpiod_handle, "gpiod_line_settings_new"); 103 | fn_gpiod_line_settings_free = (pl_gpiod_line_settings_free)dlsym(lib_gpiod_handle, "gpiod_line_settings_free"); 104 | fn_gpiod_line_settings_set_direction = (pl_gpiod_line_settings_set_direction)dlsym(lib_gpiod_handle, "gpiod_line_settings_set_direction"); 105 | fn_gpiod_line_settings_set_output_value = (pl_gpiod_line_settings_set_output_value)dlsym(lib_gpiod_handle, "gpiod_line_settings_set_output_value"); 106 | 107 | fn_gpiod_line_config_add_line_settings = (pl_gpiod_line_config_add_line_settings)dlsym(lib_gpiod_handle, "gpiod_line_config_add_line_settings"); 108 | 109 | fn_gpiod_request_config_new = (pl_gpiod_request_config_new)dlsym(lib_gpiod_handle, "gpiod_request_config_new"); 110 | fn_gpiod_request_config_free = (pl_gpiod_request_config_free)dlsym(lib_gpiod_handle, "gpiod_request_config_free"); 111 | fn_gpiod_request_config_set_consumer = (pl_gpiod_request_config_set_consumer)dlsym(lib_gpiod_handle, "gpiod_request_config_set_consumer"); 112 | fn_gpiod_line_request_set_value = (pl_gpiod_line_request_set_value)dlsym(lib_gpiod_handle, "gpiod_line_request_set_value"); 113 | 114 | if (!fn_gpiod_chip_open || 115 | !fn_gpiod_chip_close || 116 | !fn_gpiod_chip_get_info || 117 | !fn_gpiod_chip_info_free || 118 | !fn_gpiod_chip_info_get_label || 119 | !fn_gpiod_chip_request_lines || 120 | !fn_gpiod_line_config_new || 121 | !fn_gpiod_line_config_free || 122 | !fn_gpiod_line_request_release || 123 | !fn_gpiod_line_settings_new || 124 | !fn_gpiod_line_settings_free || 125 | !fn_gpiod_line_settings_set_direction || 126 | !fn_gpiod_line_settings_set_output_value || 127 | !fn_gpiod_line_config_add_line_settings || 128 | !fn_gpiod_request_config_new || 129 | !fn_gpiod_request_config_free || 130 | !fn_gpiod_request_config_set_consumer || 131 | !fn_gpiod_line_request_set_value 132 | ) 133 | { 134 | plUnloadLibGpiod(); 135 | return -1; 136 | } 137 | 138 | return 0; 139 | } 140 | 141 | static int plUnloadLibGpiod(void) 142 | { 143 | Assert(lib_gpiod_handle != NULL); 144 | 145 | if (lib_gpiod_handle) 146 | { 147 | dlclose(lib_gpiod_handle); 148 | lib_gpiod_handle = NULL; 149 | fn_gpiod_chip_open = NULL; 150 | fn_gpiod_chip_close = NULL; 151 | fn_gpiod_chip_get_info = NULL; 152 | fn_gpiod_chip_info_free = NULL; 153 | fn_gpiod_chip_info_get_label = NULL; 154 | fn_gpiod_chip_request_lines = NULL; 155 | fn_gpiod_line_config_new = NULL; 156 | fn_gpiod_line_config_free = NULL; 157 | fn_gpiod_line_request_release = NULL; 158 | 159 | fn_gpiod_line_settings_new = NULL; 160 | fn_gpiod_line_settings_free = NULL; 161 | fn_gpiod_line_settings_set_direction = NULL; 162 | fn_gpiod_line_settings_set_output_value = NULL; 163 | 164 | fn_gpiod_line_config_add_line_settings = NULL; 165 | fn_gpiod_request_config_new = NULL; 166 | fn_gpiod_request_config_free = NULL; 167 | fn_gpiod_request_config_set_consumer = NULL; 168 | fn_gpiod_line_request_set_value = NULL; 169 | 170 | return 0; 171 | } 172 | 173 | return -1; 174 | } 175 | 176 | static struct gpiod_chip *plGetGpiodChip(const char *prefix) 177 | { 178 | int i; 179 | struct gpiod_chip *chip; 180 | struct gpiod_chip_info *info; 181 | const char *label; 182 | char path[64]; 183 | U_SStream ss; 184 | 185 | for (i = 0; i < 20; i++) 186 | { 187 | U_sstream_init(&ss, path, sizeof(path)); 188 | U_sstream_put_str(&ss, "/dev/gpiochip"); 189 | U_sstream_put_long(&ss, i); 190 | chip = fn_gpiod_chip_open(path); 191 | if (!chip) 192 | continue; 193 | 194 | info = fn_gpiod_chip_get_info(chip); 195 | if (!info) 196 | { 197 | fn_gpiod_chip_close(chip); 198 | continue; 199 | } 200 | 201 | label = fn_gpiod_chip_info_get_label(info); 202 | 203 | if (label) 204 | { 205 | U_sstream_init(&ss, (char*)label, strlen(label)); 206 | if (U_sstream_find(&ss, prefix)) 207 | { 208 | fn_gpiod_chip_info_free(info); 209 | return chip; 210 | } 211 | } 212 | 213 | fn_gpiod_chip_info_free(info); 214 | fn_gpiod_chip_close(chip); 215 | } 216 | 217 | return 0; 218 | } 219 | 220 | static struct gpiod_line_request * plRequestOutputLine(struct gpiod_chip *chip, unsigned int offset, enum gpiod_line_value value) 221 | { 222 | int ret; 223 | const char *consumer = "gcf"; 224 | struct gpiod_line_request *req = 0; 225 | struct gpiod_request_config *req_cfg = 0; 226 | struct gpiod_line_settings *settings; 227 | struct gpiod_line_config *line_cfg; 228 | 229 | settings = fn_gpiod_line_settings_new(); 230 | 231 | if (settings) 232 | { 233 | fn_gpiod_line_settings_set_direction(settings, GPIOD_LINE_DIRECTION_OUTPUT); 234 | fn_gpiod_line_settings_set_output_value(settings, value); 235 | 236 | line_cfg = fn_gpiod_line_config_new(); 237 | 238 | if (line_cfg) 239 | { 240 | ret = fn_gpiod_line_config_add_line_settings(line_cfg, &offset, 1, settings); 241 | 242 | if (ret == 0) 243 | { 244 | if (consumer) 245 | { 246 | req_cfg = fn_gpiod_request_config_new(); 247 | if (req_cfg) 248 | fn_gpiod_request_config_set_consumer(req_cfg, consumer); 249 | } 250 | 251 | req = fn_gpiod_chip_request_lines(chip, req_cfg, line_cfg); 252 | 253 | if (req_cfg) 254 | fn_gpiod_request_config_free(req_cfg); 255 | } 256 | 257 | fn_gpiod_line_config_free(line_cfg); 258 | } 259 | 260 | fn_gpiod_line_settings_free(settings); 261 | } 262 | 263 | return req; 264 | } 265 | 266 | int plResetRaspBeeLibGpiod(void) 267 | { 268 | int ret = -1; 269 | struct gpiod_chip *chip; 270 | struct gpiod_line_request *line_req; 271 | 272 | if (plLoadLibGpiod() != 0) 273 | { 274 | return -1; 275 | } 276 | 277 | chip = plGetGpiodChip("pinctrl-"); 278 | 279 | if (!chip) 280 | { 281 | plUnloadLibGpiod(); 282 | return -2; 283 | } 284 | 285 | /* https://pinout.xyz/pinout/raspbee 286 | RaspBee reset pin on gpio17 287 | */ 288 | line_req = plRequestOutputLine(chip, 17, GPIOD_LINE_VALUE_ACTIVE); /* CBUS0 */ 289 | 290 | if (line_req) 291 | { 292 | /* TODO not tested yet due lack of libgpoid version 2 for Raspberry Pi */ 293 | ret = fn_gpiod_line_request_set_value(line_req, 0, GPIOD_LINE_VALUE_INACTIVE); 294 | Assert(ret == 0); 295 | ret = fn_gpiod_line_request_set_value(line_req, 0, GPIOD_LINE_VALUE_ACTIVE); 296 | Assert(ret == 0); 297 | fn_gpiod_line_request_release(line_req); 298 | ret = 0; 299 | } 300 | 301 | fn_gpiod_chip_close(chip); 302 | plUnloadLibGpiod(); 303 | 304 | return ret; 305 | } 306 | 307 | /* https://web.git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git/tree/examples/toggle_line_value.c */ 308 | int plResetFtdiLibGpiod(void) 309 | { 310 | int ret = -1; 311 | struct gpiod_chip *chip; 312 | struct gpiod_line_request *line_req; 313 | 314 | if (plLoadLibGpiod() != 0) 315 | { 316 | return -1; 317 | } 318 | 319 | chip = plGetGpiodChip("ftdi-cbus"); 320 | if (!chip) 321 | { 322 | plUnloadLibGpiod(); 323 | return -2; 324 | } 325 | 326 | line_req = plRequestOutputLine(chip, 0, GPIOD_LINE_VALUE_INACTIVE); /* CBUS0 */ 327 | 328 | if (line_req) 329 | { 330 | ret = fn_gpiod_line_request_set_value(line_req, 0, GPIOD_LINE_VALUE_ACTIVE); 331 | Assert(ret == 0); 332 | ret = fn_gpiod_line_request_set_value(line_req, 0, GPIOD_LINE_VALUE_INACTIVE); 333 | Assert(ret == 0); 334 | ret = fn_gpiod_line_request_set_value(line_req, 0, GPIOD_LINE_VALUE_ACTIVE); 335 | Assert(ret == 0); 336 | fn_gpiod_line_request_release(line_req); 337 | ret = 0; 338 | } 339 | 340 | fn_gpiod_chip_close(chip); 341 | 342 | plUnloadLibGpiod(); 343 | 344 | return ret; 345 | } 346 | 347 | #endif /* HAS_LIBGPIOD */ 348 | -------------------------------------------------------------------------------- /linux_get_usb_devices.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021-2023 dresden elektronik ingenieurtechnik gmbh. 3 | * All rights reserved. 4 | * 5 | * The software in this package is published under the terms of the BSD 6 | * style license a copy of which has been included with this distribution in 7 | * the LICENSE.txt file. 8 | * 9 | */ 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include "gcf.h" 19 | #include "u_sstream.h" 20 | #include "u_mem.h" 21 | 22 | 23 | /* Query USB info via udevadm 24 | This works also when /dev/by-id .. is symlinks aren't available 25 | 26 | udevadm info --name=/dev/ttyACM0 27 | */ 28 | static int query_udevadm(Device *dev, Device *end) 29 | { 30 | FILE *f; 31 | size_t n; 32 | U_SStream ss; 33 | char ch; 34 | char buf[4096 * 4]; 35 | long udevadm_version; 36 | unsigned i; 37 | unsigned dev_major; 38 | unsigned usb_vendor; 39 | 40 | Device *dev_cur; 41 | DIR *dir; 42 | struct dirent *entry; 43 | struct stat statbuf; 44 | 45 | dev_cur = dev; 46 | udevadm_version = 0; 47 | /* check if udevadm is available */ 48 | f = popen("udevadm --version", "r"); 49 | if (f) 50 | { 51 | n = fread(&buf[0], 1, sizeof(buf) - 1, f); 52 | if (n > 0 && n < 10) 53 | { 54 | buf[n] = '\0'; 55 | U_sstream_init(&ss, &buf[0], (unsigned)n); 56 | udevadm_version = U_sstream_get_long(&ss); 57 | if (ss.status != U_SSTREAM_OK) 58 | udevadm_version = 0; 59 | } 60 | pclose(f); 61 | } 62 | 63 | if (udevadm_version == 0) 64 | return 0; 65 | 66 | dir = opendir("/dev"); 67 | 68 | if (!dir) 69 | return 0; 70 | 71 | while ((entry = readdir(dir)) != NULL) 72 | { 73 | if (entry->d_type != DT_CHR) 74 | continue; 75 | 76 | if (dev_cur == end) 77 | break; 78 | 79 | U_bzero(dev_cur, sizeof(*dev_cur)); 80 | 81 | U_sstream_init(&ss, &dev_cur->path[0], sizeof(dev_cur->path)); 82 | U_sstream_put_str(&ss, "/dev/"); 83 | U_sstream_put_str(&ss, &entry->d_name[0]); 84 | 85 | if (stat(ss.str, &statbuf) != 0) 86 | continue; 87 | 88 | dev_major = statbuf.st_rdev >> 8; 89 | if (dev_major != 166 && dev_major != 188) 90 | continue; 91 | 92 | U_sstream_init(&ss, &buf[0], sizeof(buf)); 93 | U_sstream_put_str(&ss, "udevadm info --name="); 94 | U_sstream_put_str(&ss, "/dev/"); 95 | U_sstream_put_str(&ss, &entry->d_name[0]); 96 | 97 | f = popen(ss.str, "r"); 98 | if (f) 99 | { 100 | usb_vendor = 0; 101 | dev_cur->serial[0] = '\0'; 102 | dev_cur->name[0] = '\0'; 103 | 104 | n = fread(&buf[0], 1, sizeof(buf) - 1, f); 105 | pclose(f); 106 | 107 | if (n > 0 && n < sizeof(buf) - 1) 108 | { 109 | buf[n] = '\0'; 110 | U_sstream_init(&ss, &buf[0], (unsigned)n); 111 | while (U_sstream_find(&ss, "E: ") && U_sstream_remaining(&ss) > 8) 112 | { 113 | ss.pos += 3; 114 | if (U_sstream_starts_with(&ss, "ID_USB_VENDOR_ID=") && U_sstream_find(&ss, "=")) 115 | { 116 | ss.pos += 1; 117 | if (U_sstream_starts_with(&ss, "1cf1")) { usb_vendor = 0x1cf1; } 118 | else if (U_sstream_starts_with(&ss, "0403")) { usb_vendor = 0x0403; } 119 | else if (U_sstream_starts_with(&ss, "1a86")) { usb_vendor = 0x1a86; } 120 | else if (U_sstream_starts_with(&ss, "303a")) { usb_vendor = 0x303a; } 121 | } 122 | else if (U_sstream_starts_with(&ss, "ID_USB_SERIAL_SHORT=") && U_sstream_find(&ss, "=")) 123 | { 124 | i = 0; 125 | ss.pos += 1; 126 | dev_cur->serial[0] = '\0'; 127 | 128 | for (;ss.pos < ss.len && i + 1 < sizeof(dev_cur->serial); ss.pos++) 129 | { 130 | ch = U_sstream_peek_char(&ss); 131 | if ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9')) 132 | { 133 | dev_cur->serial[i] = ch; 134 | dev_cur->serial[i + 1] = '\0'; 135 | i++; 136 | } 137 | else if (ch == ':') 138 | { 139 | /* 40:4C:CA:42:8B:50 (ignore ':' in hex string) */ 140 | } 141 | else 142 | { 143 | break; 144 | } 145 | } 146 | } 147 | else if (U_sstream_starts_with(&ss, "ID_USB_MODEL=") && U_sstream_find(&ss, "=")) 148 | { 149 | i = 0; 150 | ss.pos += 1; 151 | dev_cur->name[0] = '\0'; 152 | 153 | for (;ss.pos < ss.len && i + 1 < sizeof(dev_cur->name); ss.pos++, i++) 154 | { 155 | ch = U_sstream_peek_char(&ss); 156 | if (ch == ' ' || ch == '_' || (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9')) 157 | { 158 | dev_cur->name[i] = ch; 159 | dev_cur->name[i + 1] = '\0'; 160 | } 161 | else 162 | { 163 | break; 164 | } 165 | } 166 | 167 | { 168 | U_SStream s2; 169 | U_sstream_init(&s2, dev_cur->name, i); 170 | 171 | if (U_sstream_starts_with(&s2, "ConBee_III")) 172 | { 173 | dev_cur->baudrate = PL_BAUDRATE_115200; 174 | } 175 | else if (U_sstream_starts_with(&s2, "ConBee_II")) 176 | { 177 | dev_cur->baudrate = PL_BAUDRATE_115200; 178 | } 179 | else if (U_sstream_starts_with(&s2, "USB_JTAG_serial_debug_unit")) 180 | { 181 | dev_cur->baudrate = PL_BAUDRATE_115200; /* expressif (FLS-M) */ 182 | } 183 | } 184 | } 185 | } 186 | } 187 | 188 | if (usb_vendor == 0x1a86) 189 | { 190 | dev_cur->baudrate = PL_BAUDRATE_115200; 191 | if (dev_cur->serial[0] == '\0') 192 | { 193 | /* the CH340 chips don't have a serial? */ 194 | dev_cur->serial[0] = '1'; 195 | dev_cur->serial[1] = '\0'; 196 | } 197 | } 198 | else if (usb_vendor == 0x303a) 199 | { 200 | U_SStream s2; 201 | U_sstream_init(&s2, dev_cur->name, sizeof(dev_cur->name)); 202 | U_sstream_put_str(&s2, "Espressif"); 203 | } 204 | 205 | if (usb_vendor && dev_cur->serial[0] && dev_cur->name[0]) 206 | { 207 | U_memcpy(&dev_cur->stablepath[0], &dev_cur->path[0], sizeof(dev_cur->path)); 208 | dev_cur++; 209 | } 210 | } 211 | 212 | } 213 | 214 | closedir(dir); 215 | 216 | return (int)(dev_cur - dev); 217 | } 218 | 219 | /*! Fills the \p dev array with ConBee I and II devices. 220 | 221 | The array is filled based on the Linux /dev/serial/by-id/ symlinks 222 | which give a clue about devices without requiring extra libraries. 223 | 224 | \returns The number of devices placed in the array. 225 | */ 226 | int plGetLinuxUSBDevices(Device *dev, Device *end) 227 | { 228 | Assert(dev < end); 229 | 230 | DIR *dir; 231 | struct dirent *entry; 232 | 233 | int result = 0; 234 | char buf[MAX_DEV_PATH_LENGTH]; 235 | 236 | result = query_udevadm(dev, end); 237 | if (result > 0) 238 | return result; 239 | 240 | Assert(sizeof(dev->stablepath) == sizeof(buf)); 241 | 242 | const char *basedir = "/dev/serial/by-id"; 243 | 244 | dir = opendir(basedir); 245 | 246 | if (!dir) 247 | { 248 | return result; 249 | } 250 | 251 | const char *devConBeeII = "ConBee_II"; /* usb-dresden_elektronik_ingenieurtechnik_GmbH_ConBee_II_DE1948474-if00 */ 252 | const char *devConBeeIII = "ConBee_III"; /* usb-dresden_elektronik_ConBee_III_DEDEADAFFE-if00-port0 */ 253 | const char *devConBeeIFTDI = "FT230X_Basic_UART"; /* usb-FTDI_FT230X_Basic_UART_DJ00QBWE-if00-port0 */ 254 | const char *devConBeeI = "ConBee"; 255 | const char *devEspressif = "Espressif_USB_JTAG_serial_debug_unit"; /* usb-Espressif_USB_JTAG_serial_debug_unit_40:4C:CA:42:8B:50-if00 */ 256 | 257 | while ((entry = readdir(dir)) != NULL) 258 | { 259 | if (dev == end) 260 | { 261 | break; 262 | } 263 | 264 | if (entry->d_name[0] == '.') /* skip . and .. */ 265 | { 266 | continue; 267 | } 268 | 269 | const char *name = NULL; 270 | const char *serial = NULL; 271 | 272 | dev->baudrate = PL_BAUDRATE_UNKNOWN; 273 | 274 | if (strstr(entry->d_name, devConBeeII)) 275 | { 276 | name = devConBeeII; 277 | /* usb-dresden_elektronik_ingenieurtechnik_GmbH_ConBee_II_DE1948474-if00 278 | ^ 279 | */ 280 | serial = strstr(entry->d_name, devConBeeII) + strlen(devConBeeII) + 1; 281 | dev->baudrate = PL_BAUDRATE_115200; 282 | 283 | } 284 | else if (strstr(entry->d_name, devConBeeIII)) 285 | { 286 | name = devConBeeIII; 287 | /* usb-dresden_elektronik_ConBee_III_DEDEADAFFE-if00-port0 288 | ^ 289 | */ 290 | serial = strstr(entry->d_name, devConBeeIII) + strlen(devConBeeIII) + 1; 291 | dev->baudrate = PL_BAUDRATE_115200; 292 | 293 | } 294 | else if (strstr(entry->d_name, devConBeeIFTDI)) 295 | { 296 | name = devConBeeI; 297 | /* usb-FTDI_FT230X_Basic_UART_DJ00QBWE-if00-port0 298 | ^ 299 | */ 300 | serial = strstr(entry->d_name, devConBeeIFTDI) + strlen(devConBeeIFTDI) + 1; 301 | dev->baudrate = PL_BAUDRATE_38400; 302 | } 303 | else if (strstr(entry->d_name, devEspressif)) 304 | { 305 | name = "Espressif"; 306 | /* usb-Espressif_USB_JTAG_serial_debug_unit_40:4C:CA:42:8B:50-if00 307 | ^ 308 | */ 309 | serial = strstr(entry->d_name, devEspressif) + strlen(devEspressif) + 1; 310 | dev->baudrate = PL_BAUDRATE_115200; 311 | } 312 | 313 | if (!name) 314 | { 315 | continue; 316 | } 317 | 318 | /* copy name */ 319 | size_t namelen = strlen(name); 320 | if (namelen >= sizeof(dev->name)) 321 | { 322 | Assert(!"Device->name buffer too small"); 323 | continue; 324 | } 325 | 326 | memcpy(dev->name, name, namelen + 1); 327 | 328 | /* copy serial number */ 329 | char *p = NULL; 330 | 331 | dev->serial[0] = '\0'; 332 | if (serial) 333 | { 334 | p = strchr(serial, '-'); 335 | Assert(!p || p > serial); 336 | size_t len = p ? p - serial : 0; 337 | if (len > 0 && len < sizeof(dev->serial)) 338 | { 339 | memcpy(dev->serial, serial, len); 340 | dev->serial[len] = '\0'; 341 | } 342 | } 343 | 344 | /* copy stable device path */ 345 | if (snprintf(buf, sizeof(buf), "%s/%s", basedir, entry->d_name) >= (int)sizeof(buf)) 346 | { 347 | Assert(!"Device->stablepath is too small"); 348 | continue; 349 | } 350 | 351 | memcpy(dev->stablepath, buf, strlen(buf) + 1); 352 | 353 | /* copy path (/dev/ttyACM0 ...) */ 354 | /* realpath writes up to PATH_MAX bytes, and might crash even if the resulting string is smaller */ 355 | char rbuf[PATH_MAX]; 356 | p = realpath(buf, rbuf); 357 | 358 | if (!p) 359 | { 360 | Assert(!"failed to get symbolic link target"); 361 | continue; 362 | } 363 | 364 | if (strlen(p) + 1 < sizeof(dev->path)) 365 | { 366 | memcpy(dev->path, p, strlen(p) + 1); 367 | } 368 | 369 | dev++; 370 | result++; 371 | } 372 | 373 | closedir(dir); 374 | 375 | return result; 376 | } 377 | 378 | int plGetLinuxSerialDevices(Device *dev, Device *end) 379 | { 380 | int sz; 381 | U_SStream ss; 382 | struct stat statbuf; 383 | char lnkpath[MAX_DEV_PATH_LENGTH]; 384 | const char *ser0 = "/dev/serial0"; 385 | 386 | if (dev >= end) 387 | return 0; 388 | 389 | if (lstat(ser0, &statbuf) == 0) 390 | { 391 | if (S_ISLNK(statbuf.st_mode)) 392 | { 393 | sz = readlink(ser0, lnkpath, sizeof(lnkpath) - 1); 394 | if (sz > 0 && sz < MAX_DEV_PATH_LENGTH - 1) 395 | { 396 | lnkpath[sz] = '\0'; 397 | dev->serial[0] = 0; 398 | U_memcpy(dev->name, "RaspBee", strlen("RaspBee") + 1); 399 | dev->baudrate = PL_BAUDRATE_38400; 400 | 401 | U_sstream_init(&ss, dev->path, sizeof(dev->path)); 402 | if (lnkpath[0] != '/') 403 | { 404 | U_sstream_put_str(&ss, "/dev/"); 405 | } 406 | U_sstream_put_str(&ss, lnkpath); 407 | U_memcpy(dev->stablepath, ser0, strlen(ser0) + 1); 408 | return 1; 409 | } 410 | } 411 | } 412 | 413 | return 0; 414 | } 415 | -------------------------------------------------------------------------------- /main_posix.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021-2023 dresden elektronik ingenieurtechnik gmbh. 3 | * All rights reserved. 4 | * 5 | * The software in this package is published under the terms of the BSD 6 | * style license a copy of which has been included with this distribution in 7 | * the LICENSE.txt file. 8 | * 9 | */ 10 | 11 | #include 12 | #include 13 | #include /* va_list, ... */ 14 | #include 15 | #include /* open() */ 16 | #include /* close() */ 17 | //#include 18 | #include 19 | #include 20 | #include /* memset() */ 21 | #include 22 | #include 23 | #include /* POSIX terminal control definitions */ 24 | 25 | #include "gcf.h" 26 | #include "protocol.h" 27 | #include "u_sstream.h" 28 | #include "u_mem.h" 29 | 30 | #define RX_BUF_SIZE 1024 31 | #define TX_BUF_SIZE 2048 32 | 33 | typedef struct 34 | { 35 | PL_time_t timer; 36 | int fd; 37 | unsigned char running; 38 | unsigned char rxbuf[RX_BUF_SIZE]; 39 | unsigned char txbuf[TX_BUF_SIZE]; 40 | unsigned tx_rp; 41 | unsigned tx_wp; 42 | GCF *gcf; 43 | } PL_Internal; 44 | 45 | static PL_Internal platform; 46 | static struct termios restore_attr; 47 | 48 | #ifdef PL_LINUX 49 | int plGetLinuxUSBDevices(Device *dev, Device *end); 50 | int plGetLinuxSerialDevices(Device *dev, Device *end); 51 | 52 | #ifdef HAS_LIBGPIOD 53 | int plResetRaspBeeLibGpiod(void); 54 | int plResetFtdiLibGpiod(void); 55 | #endif 56 | 57 | #endif 58 | 59 | #ifdef PL_MAC 60 | #include "posix_libftdi_reset.c" 61 | int plGetMacOSUSBDevices(Device *dev, Device *end); 62 | #endif 63 | 64 | static int plSetupPort(int fd, int baudrate) 65 | { 66 | struct termios options; 67 | 68 | fcntl(fd, F_SETFL, O_RDWR | /*O_NONBLOCK |*/ O_NOCTTY); 69 | 70 | tcgetattr(fd, &options); 71 | 72 | cfsetispeed(&options, (speed_t)baudrate); 73 | cfsetospeed(&options, (speed_t)baudrate); 74 | 75 | cfmakeraw(&options); 76 | /* Enable the receiver and set local mode... */ 77 | options.c_cflag |= (CLOCAL | CREAD); 78 | 79 | // No parity 8N1 80 | options.c_cflag &= (tcflag_t)~PARENB; 81 | options.c_cflag &= (tcflag_t)~CSTOPB; 82 | options.c_cflag &= (tcflag_t)~CSIZE; 83 | options.c_cflag |= (tcflag_t)CS8; 84 | 85 | /* disable hardware control flow */ 86 | options.c_cflag &= (tcflag_t)~CRTSCTS; 87 | 88 | tcsetattr(fd, TCSANOW, &options); 89 | 90 | return 0; 91 | } 92 | 93 | /* Returns a monotonic timestamps in milliseconds */ 94 | PL_time_t PL_Time(void) 95 | { 96 | PL_time_t res; 97 | struct timespec ts; 98 | 99 | res = 0; 100 | if (clock_gettime(CLOCK_MONOTONIC, &ts) == 0) 101 | { 102 | res = ts.tv_sec * 1000; 103 | res += ts.tv_nsec / 1000000; 104 | } 105 | 106 | return res; 107 | } 108 | 109 | void PL_MSleep(unsigned long ms) 110 | { 111 | while (ms > 0) 112 | { 113 | usleep(1000); 114 | ms--; 115 | } 116 | } 117 | 118 | int PL_ResetFTDI(int num, const char *serialnum) 119 | { 120 | (void)num; 121 | (void)serialnum; 122 | #ifdef HAS_LIBGPIOD 123 | return plResetFtdiLibGpiod(); 124 | #endif 125 | 126 | #ifdef HAS_LIBFTDI 127 | return plResetLibFtdi(); 128 | #endif 129 | 130 | return -1; 131 | } 132 | 133 | int PL_ResetRaspBee(void) 134 | { 135 | #ifdef HAS_LIBGPIOD 136 | return plResetRaspBeeLibGpiod(); 137 | #endif 138 | return -1; 139 | } 140 | 141 | void PL_Print(const char *line) 142 | { 143 | ssize_t n = write(STDOUT_FILENO, line, strlen(line)); 144 | (void)n; 145 | } 146 | 147 | void PL_Printf(DebugLevel level, const char *format, ...) 148 | { 149 | FILE *fp; 150 | 151 | fp = stdout; 152 | #ifdef NDEBUG 153 | if (level == DBG_DEBUG) 154 | return; 155 | #endif 156 | if (level == DBG_DEBUG) 157 | fp = stderr; 158 | 159 | va_list args; 160 | va_start (args, format); 161 | vfprintf(fp, format, args); 162 | va_end (args); 163 | } 164 | 165 | GCF_Status PL_Connect(const char *path, PL_Baudrate baudrate) 166 | { 167 | PL_Printf(DBG_DEBUG, "PL_Connect\n"); 168 | 169 | int baudrate1 = 0; 170 | 171 | if (platform.fd != 0) 172 | { 173 | PL_Printf(DBG_DEBUG, "device already connected %s\n", path); 174 | return GCF_SUCCESS; 175 | } 176 | 177 | platform.fd = open(path, O_CLOEXEC | O_RDWR /*| O_NONBLOCK*/); 178 | platform.tx_rp = 0; 179 | platform.tx_wp = 0; 180 | 181 | if (platform.fd < 0) 182 | { 183 | PL_Printf(DBG_DEBUG, "failed to open device %s\n", path); 184 | platform.fd = 0; 185 | return GCF_FAILED; 186 | } 187 | 188 | if (baudrate == PL_BAUDRATE_38400) 189 | { 190 | baudrate1 = B38400; 191 | } 192 | else if (baudrate == PL_BAUDRATE_115200) 193 | { 194 | baudrate1 = B115200; 195 | } 196 | 197 | if (baudrate1 == 0) 198 | { 199 | baudrate1 = B38400; 200 | #if 0 201 | if (strstr(path, "ACM")) /* ConBee II Linux */ 202 | { 203 | baudrate1 = B115200; 204 | } 205 | else if (strstr(path, "cu.usbmodemDE")) /* ConBee II macOS */ 206 | { 207 | baudrate1 = B115200; 208 | } 209 | #endif 210 | } 211 | 212 | plSetupPort(platform.fd, baudrate1); 213 | 214 | PL_Printf(DBG_DEBUG, "connected to %s, baudrate: %d\n", path, baudrate); 215 | 216 | return GCF_SUCCESS; 217 | } 218 | 219 | void PL_Disconnect(void) 220 | { 221 | PL_Printf(DBG_DEBUG, "PL_Disconnect\n"); 222 | if (platform.fd != 0) 223 | { 224 | close(platform.fd); 225 | platform.fd = 0; 226 | } 227 | platform.tx_rp = 0; 228 | platform.tx_wp = 0; 229 | GCF_HandleEvent(platform.gcf, EV_DISCONNECTED); 230 | } 231 | 232 | void PL_ShutDown(void) 233 | { 234 | PL_Printf(DBG_DEBUG, "PL_Shutdown\n"); 235 | platform.running = 0; 236 | } 237 | 238 | int PL_ReadFile(const char *path, unsigned char *buf, unsigned long buflen) 239 | { 240 | int fd; 241 | int ret; 242 | 243 | Assert(path && buf && buflen >= MAX_GCF_FILE_SIZE); 244 | 245 | ret = -1; 246 | fd = open(path, O_RDONLY); 247 | 248 | if (fd == -1) 249 | { 250 | PL_Printf(DBG_DEBUG, "failed to open %s, err: %s\n", path, strerror(errno)); 251 | return ret; 252 | } 253 | 254 | ret = (int)read(fd, buf, buflen); 255 | if (ret == -1) 256 | { 257 | PL_Printf(DBG_DEBUG, "failed to read %s, err: %s\n", path, strerror(errno)); 258 | } 259 | 260 | if (close(fd) == -1) 261 | { 262 | PL_Printf(DBG_DEBUG, "failed to close %s, err: %s\n", path, strerror(errno)); 263 | } 264 | 265 | return ret; 266 | } 267 | 268 | void PL_SetTimeout(unsigned long ms) 269 | { 270 | platform.timer = PL_Time() + ms; 271 | } 272 | 273 | void PL_ClearTimeout(void) 274 | { 275 | platform.timer = 0; 276 | } 277 | 278 | int PL_GetDevices(Device *devs, unsigned max) 279 | { 280 | int result = 0; 281 | 282 | U_bzero(devs, sizeof(*devs) * max); 283 | 284 | #ifdef PL_LINUX 285 | result = plGetLinuxUSBDevices(devs, devs + max); 286 | if (result == 0) // only include RaspBee if no USB devices where found 287 | { 288 | result += plGetLinuxSerialDevices(devs + result, devs + max); 289 | } 290 | #endif 291 | 292 | #ifdef PL_MAC 293 | result = plGetMacOSUSBDevices(devs, devs + max); 294 | #endif 295 | 296 | return result; 297 | } 298 | 299 | int PROT_Write(const unsigned char *data, unsigned len) 300 | { 301 | int result; 302 | unsigned i; 303 | 304 | result = 0; 305 | for (i = 0; i < len; i++) 306 | result += PROT_Putc(data[i]); 307 | 308 | PROT_Flush(); 309 | 310 | return result; 311 | } 312 | 313 | int PROT_Putc(unsigned char ch) 314 | { 315 | if (platform.fd == 0) 316 | return 0; 317 | 318 | platform.txbuf[platform.tx_wp % TX_BUF_SIZE] = ch; 319 | platform.tx_wp++; 320 | 321 | if ((platform.tx_wp % TX_BUF_SIZE) == (platform.tx_rp % TX_BUF_SIZE)) 322 | platform.tx_rp++; /* overwrite oldest */ 323 | 324 | return 1; 325 | } 326 | 327 | int PROT_Flush(void) 328 | { 329 | int n; 330 | unsigned pos; 331 | unsigned len; 332 | unsigned char buf[512]; 333 | 334 | if (platform.fd == 0) 335 | { 336 | platform.tx_wp = 0; 337 | platform.tx_rp = 0; 338 | GCF_HandleEvent(platform.gcf, EV_DISCONNECTED); 339 | return -1; 340 | } 341 | 342 | for (len = 0; len < sizeof(buf); len++) 343 | { 344 | if ((platform.tx_wp % TX_BUF_SIZE) == ((platform.tx_rp + len) % TX_BUF_SIZE)) 345 | break; 346 | buf[len] = platform.txbuf[(platform.tx_rp + len) % TX_BUF_SIZE]; 347 | } 348 | 349 | gcfDebugHex(platform.gcf, "send", &buf[0], len); 350 | 351 | for (pos = 0; pos < len;) 352 | { 353 | n = (int)write(platform.fd, &buf[pos], len - pos); 354 | if (n == -1) 355 | { 356 | if (errno == EINTR) 357 | continue; 358 | PL_Printf(DBG_DEBUG, "write() failed: %s\n", strerror(errno)); 359 | break; 360 | } 361 | else if (n > 0 && n <= (int)(len - pos)) 362 | { 363 | pos += (unsigned)n; 364 | } 365 | else 366 | { 367 | break; /* should never happen */ 368 | } 369 | } 370 | 371 | platform.tx_rp += pos; 372 | 373 | return (int)pos; 374 | } 375 | 376 | void UI_GetWinSize(unsigned *w, unsigned *h) 377 | { 378 | struct winsize size; 379 | ioctl(STDOUT_FILENO, TIOCGWINSZ, &size); 380 | 381 | *w = size.ws_col; 382 | *h = size.ws_row; 383 | } 384 | 385 | /* Unicode box drawing chars 386 | https://en.wikipedia.org/wiki/Box-drawing_character 387 | */ 388 | void UI_SetCursor(unsigned x, unsigned y) 389 | { 390 | U_SStream ss; 391 | char buf[24]; 392 | U_sstream_init(&ss, buf, sizeof(buf)); 393 | U_sstream_put_str(&ss, FMT_ESC "["); 394 | U_sstream_put_long(&ss, (long)y); 395 | U_sstream_put_str(&ss, ";"); 396 | U_sstream_put_long(&ss, (long)x); 397 | U_sstream_put_str(&ss, "H"); 398 | PL_Print(buf); 399 | } 400 | 401 | static void PL_InitKeyboard(void) 402 | { 403 | struct termios attr; 404 | tcgetattr(STDIN_FILENO, &attr); 405 | tcgetattr(STDIN_FILENO, &restore_attr); 406 | attr.c_lflag &= ~(ICANON | ECHO); /* turn off canonical mode */ 407 | tcsetattr(STDIN_FILENO, TCSANOW, &attr); 408 | } 409 | 410 | static void PL_AtExit(void) 411 | { 412 | restore_attr.c_lflag |= (ICANON | ECHO); /* turn on canonical mode */ 413 | tcsetattr(STDIN_FILENO, TCSANOW, &restore_attr); 414 | } 415 | 416 | static int PL_Loop(GCF *gcf) 417 | { 418 | int nfds; 419 | int ret; 420 | int nread; 421 | struct pollfd fds[2]; 422 | unsigned codepoint; 423 | 424 | PL_InitKeyboard(); 425 | 426 | memset(&platform, 0, sizeof(platform)); 427 | platform.gcf = gcf; 428 | 429 | platform.running = 1; 430 | 431 | fds[0].events = POLLIN; 432 | fds[1].events = POLLIN; 433 | 434 | GCF_HandleEvent(gcf, EV_PL_STARTED); 435 | 436 | while (platform.running) 437 | { 438 | GCF_HandleEvent(gcf, EV_PL_LOOP); 439 | 440 | nfds = 0; 441 | 442 | /* always poll STDIN at fds[0], to get poll() timeout and keyboard input */ 443 | fds[nfds++].fd = STDIN_FILENO; 444 | 445 | /* when device is connected fds[1] */ 446 | if (platform.fd != 0) 447 | fds[nfds++].fd = platform.fd; 448 | 449 | ret = poll(&fds[0], nfds, 5); 450 | 451 | if (ret < 0) 452 | { 453 | PL_Printf(DBG_DEBUG, "poll error: %s\n", strerror(errno)); 454 | break; 455 | } 456 | 457 | if (ret == 0) 458 | { 459 | if (platform.timer != 0) 460 | { 461 | if (platform.timer < PL_Time()) 462 | { 463 | platform.timer = 0; 464 | GCF_HandleEvent(gcf, EV_TIMEOUT); 465 | } 466 | } 467 | 468 | continue; 469 | } 470 | 471 | if (nfds == 2) /* device connected */ 472 | { 473 | if (fds[1].revents & (POLLHUP | POLLERR | POLLNVAL)) 474 | { 475 | PL_Disconnect(); 476 | continue; 477 | } 478 | 479 | if (fds[1].revents & POLLIN) 480 | { 481 | nread = (int) read(fds[1].fd, platform.rxbuf, sizeof(platform.rxbuf)); 482 | 483 | if (nread > 0) 484 | { 485 | GCF_Received(gcf, platform.rxbuf, nread); 486 | } 487 | } 488 | 489 | if (platform.tx_rp != platform.tx_wp) 490 | { 491 | PROT_Flush(); 492 | } 493 | } 494 | 495 | /* keyboard input */ 496 | if (fds[0].revents & POLLIN) 497 | { 498 | codepoint = 0; 499 | nread = (int) read(fds[0].fd, platform.rxbuf, sizeof(platform.rxbuf)); 500 | 501 | /* simplified input for ASCII and navigation keys */ 502 | 503 | if (nread == 1) 504 | { 505 | codepoint = platform.rxbuf[0]; 506 | if (codepoint >= 32 && codepoint <= 126) 507 | { } /* ASCII */ 508 | else if (codepoint == 0x09) { codepoint = PL_KEY_TAB; } 509 | else if (codepoint == 0x0A) { codepoint = PL_KEY_ENTER; } 510 | else if (codepoint == 0x1B) { codepoint = PL_KEY_ESC; } 511 | else if (codepoint == 0x7F) { codepoint = PL_KEY_BACKSPACE; } 512 | else { codepoint = 0; } 513 | } 514 | else if (nread >= 3 && platform.rxbuf[0] == 0x1B && platform.rxbuf[1] == 0x5B) 515 | { 516 | switch (platform.rxbuf[2]) 517 | { 518 | case 0x33: codepoint = PL_KEY_DELETE; break; 519 | case 0x41: codepoint = PL_KEY_UP; break; 520 | case 0x42: codepoint = PL_KEY_DOWN; break; 521 | case 0x43: codepoint = PL_KEY_RIGHT; break; 522 | case 0x44: codepoint = PL_KEY_LEFT; break; 523 | case 0x48: codepoint = PL_KEY_POS1; break; 524 | case 0x46: codepoint = PL_KEY_END; break; 525 | } 526 | } 527 | 528 | if (codepoint) 529 | GCF_KeyboardInput(gcf, codepoint); 530 | 531 | #ifndef NDEBUG 532 | if (codepoint == 0) 533 | { 534 | for (int b = 0; b < nread; b++) 535 | { 536 | PL_Printf(DBG_INFO, "IN: [%d] = 0x%02X (%c) \n", b, platform.rxbuf[b] & 0xFF, 537 | platform.rxbuf[b] & 0xFF); 538 | } 539 | } 540 | #endif 541 | } 542 | } 543 | 544 | PL_Disconnect(); 545 | 546 | return 1; 547 | } 548 | 549 | int main(int argc, char *argv[]) 550 | { 551 | GCF *gcf; 552 | 553 | atexit(PL_AtExit); 554 | 555 | gcf = GCF_Init(argc, argv); 556 | if (gcf == NULL) 557 | return 2; 558 | 559 | PL_Loop(gcf); 560 | 561 | GCF_Exit(gcf); 562 | 563 | return 0; 564 | } 565 | -------------------------------------------------------------------------------- /u_sstream.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 Manuel Pietschmann. 3 | * All rights reserved. 4 | * 5 | * The software in this package is published under the terms of the BSD 6 | * style license a copy of which has been included with this distribution in 7 | * the LICENSE.txt file. 8 | * 9 | */ 10 | 11 | #include "u_sstream.h" 12 | 13 | #ifndef U_ASSERT 14 | #define U_ASSERT(c) ((void)0) 15 | #endif 16 | 17 | void U_sstream_init(U_SStream *ss, void *str, unsigned size) 18 | { 19 | ss->str = (char*)str; 20 | ss->pos = 0; 21 | ss->len = size; 22 | 23 | if (str && size) 24 | { 25 | ss->status = U_SSTREAM_OK; 26 | } 27 | else 28 | { 29 | ss->status = U_SSTREAM_ERR_INVALID; 30 | } 31 | } 32 | 33 | const char *U_sstream_str(const U_SStream *ss) 34 | { 35 | U_ASSERT(ss->pos < ss->len); 36 | return &ss->str[ss->pos]; 37 | } 38 | 39 | 40 | unsigned U_sstream_pos(const U_SStream *ss) 41 | { 42 | return ss->pos; 43 | } 44 | 45 | unsigned U_sstream_remaining(const U_SStream *ss) 46 | { 47 | U_ASSERT(ss->pos <= ss->len); 48 | if (ss->pos <= ss->len) 49 | return ss->len - ss->pos; 50 | 51 | return 0; 52 | } 53 | 54 | int U_sstream_at_end(const U_SStream *ss) 55 | { 56 | U_ASSERT(ss->pos <= ss->len); 57 | if (ss->pos <= ss->len) 58 | return (ss->len - ss->pos) == 0; 59 | 60 | return 1; 61 | } 62 | 63 | long U_sstream_get_long(U_SStream *ss) 64 | { 65 | int err; 66 | long r; 67 | char *nptr; 68 | unsigned out_len; 69 | const char *endptr; 70 | 71 | r = 0; 72 | 73 | if (ss->pos < ss->len) 74 | { 75 | nptr = &ss->str[ss->pos]; 76 | endptr = 0; 77 | 78 | r = U_strtol(nptr, ss->len - ss->pos, &endptr, &err); 79 | if (err) 80 | { 81 | if (err & 0x1) { ss->status = U_SSTREAM_ERR_INVALID; } 82 | else if (err & (0x2 | 0x4)) { ss->status = U_SSTREAM_ERR_RANGE; } 83 | r = 0; 84 | } 85 | 86 | if (endptr) 87 | { 88 | out_len = (unsigned)(endptr - nptr); 89 | if (ss->pos + out_len <= ss->len) 90 | ss->pos += out_len; 91 | } 92 | } 93 | 94 | return r; 95 | } 96 | 97 | double U_sstream_get_double(U_SStream *ss) 98 | { 99 | int err; 100 | double r; 101 | char *nptr; 102 | unsigned out_len; 103 | const char *endptr; 104 | 105 | r = 0.0; 106 | 107 | if (ss->pos < ss->len) 108 | { 109 | nptr = &ss->str[ss->pos]; 110 | endptr = 0; 111 | 112 | r = U_strtod(nptr, ss->len - ss->pos, &endptr, &err); 113 | if (err) 114 | { 115 | ss->status = U_SSTREAM_ERR_INVALID; 116 | r = 0.0; 117 | } 118 | 119 | if (endptr) 120 | { 121 | out_len = (unsigned)(endptr - nptr); 122 | if (ss->pos + out_len <= ss->len) 123 | ss->pos += out_len; 124 | } 125 | } 126 | 127 | return r; 128 | } 129 | 130 | char U_sstream_peek_char(U_SStream *ss) 131 | { 132 | if (ss->pos < ss->len) 133 | return ss->str[ss->pos]; 134 | return '\0'; 135 | } 136 | 137 | void U_sstream_skip_whitespace(U_SStream *ss) 138 | { 139 | char ch; 140 | while (ss->pos < ss->len) 141 | { 142 | ch = ss->str[ss->pos]; 143 | switch (ch) 144 | { 145 | case ' ': 146 | case '\t': 147 | case '\n': 148 | case '\r': 149 | ss->pos++; 150 | break; 151 | default: 152 | return; 153 | } 154 | } 155 | } 156 | 157 | int U_sstream_starts_with(U_SStream *ss, const char *str) 158 | { 159 | unsigned i; 160 | unsigned len; 161 | 162 | for (len = 0; str[len]; len++) 163 | ; 164 | 165 | if ((ss->len - ss->pos) >= len) 166 | { 167 | for (i = 0; i < len; i++) 168 | { 169 | if (ss->str[ss->pos + i] != str[i]) 170 | return 0; 171 | } 172 | 173 | return 1; 174 | } 175 | 176 | return 0; 177 | } 178 | 179 | int U_sstream_find(U_SStream *ss, const char *str) 180 | { 181 | unsigned i; 182 | unsigned len; 183 | unsigned pos; 184 | unsigned match; 185 | 186 | for (len = 0; str[len]; len++) 187 | ; 188 | 189 | pos = ss->pos; 190 | 191 | for (; pos < ss->len && (ss->len - pos) >= len; ) 192 | { 193 | match = 0; 194 | for (i = 0; i < len; i++) 195 | { 196 | if (ss->str[pos + i] != str[i]) 197 | break; 198 | 199 | match++; 200 | } 201 | 202 | if (match == len) 203 | { 204 | ss->pos = pos; 205 | return 1; 206 | } 207 | 208 | pos++; 209 | } 210 | 211 | return 0; 212 | } 213 | 214 | void U_sstream_put_str(U_SStream *ss, const char *str) 215 | { 216 | unsigned len; 217 | 218 | if (ss->status != U_SSTREAM_OK) 219 | return; 220 | 221 | for (len = 0; str[len]; len++) 222 | ; 223 | 224 | if (ss->pos + len + 1 < ss->len) 225 | { 226 | for (; *str; str++, ss->pos++) 227 | ss->str[ss->pos] = *str; 228 | 229 | ss->str[ss->pos] = '\0'; 230 | } 231 | else 232 | { 233 | ss->status = U_SSTREAM_ERR_NO_SPACE; 234 | } 235 | } 236 | 237 | /* Outputs the signed 32/64-bit integer 'num' as ASCII string. 238 | 239 | The range is different on 32-bit systems and Windows 240 | and 64-bit systems. 241 | 242 | -2147483648 .. 2147483647 243 | -9223372036854775807 .. 9223372036854775807 244 | 245 | \param num signed number 246 | */ 247 | void U_sstream_put_long(U_SStream *ss, long num) 248 | { 249 | int i; 250 | int pos; 251 | long remainder; 252 | long n; 253 | unsigned char buf[24]; 254 | 255 | if (ss->status != U_SSTREAM_OK) 256 | return; 257 | 258 | /* sign + max digits + NUL := 21 bytes on 64-bit */ 259 | 260 | n = num; 261 | if (n < 0) 262 | ss->str[ss->pos++] = '-'; 263 | 264 | pos = 0; 265 | do 266 | { 267 | remainder = n % 10; 268 | remainder = remainder < 0 ? -remainder : remainder; 269 | n = n / 10; 270 | buf[pos++] = '0' + (unsigned char)remainder; 271 | } 272 | while (n); 273 | 274 | if (ss->len - ss->pos < (unsigned)pos + 1) /* not enough space */ 275 | { 276 | ss->status = U_SSTREAM_ERR_NO_SPACE; 277 | return; 278 | } 279 | 280 | for (i = pos; i > 0; i--) /* reverse copy */ 281 | { 282 | ss->str[ss->pos++] = (char)buf[i - 1]; 283 | } 284 | 285 | ss->str[ss->pos] = '\0'; 286 | } 287 | 288 | /* Outputs the signed 64-bit integer 'num' as ASCII string. 289 | 290 | -9223372036854775807 .. 9223372036854775807 291 | 292 | \param num signed number 293 | */ 294 | void U_sstream_put_longlong(U_SStream *ss, long long num) 295 | { 296 | int i; 297 | int pos; 298 | long remainder; 299 | long long n; 300 | char buf[24]; 301 | 302 | if (ss->status != U_SSTREAM_OK) 303 | return; 304 | 305 | /* sign + max digits + NUL := 21 bytes on 64-bit */ 306 | 307 | n = num; 308 | if (n < 0) 309 | ss->str[ss->pos++] = '-'; 310 | 311 | pos = 0; 312 | do 313 | { 314 | remainder = n % 10; 315 | remainder = remainder < 0 ? -remainder : remainder; 316 | n = n / 10; 317 | buf[pos++] = (char)('0' + remainder); 318 | } 319 | while (n); 320 | 321 | if (ss->len - ss->pos < (unsigned)pos + 1) /* not enough space */ 322 | { 323 | ss->status = U_SSTREAM_ERR_NO_SPACE; 324 | return; 325 | } 326 | 327 | for (i = pos; i > 0; i--) /* reverse copy */ 328 | { 329 | ss->str[ss->pos++] = buf[i - 1]; 330 | } 331 | 332 | ss->str[ss->pos] = '\0'; 333 | } 334 | 335 | /* Outputs the unsigned 64-bit integer 'num' as ASCII string. 336 | 337 | 0 .. 18446744073709551615 338 | 339 | \param num unsigned number 340 | */ 341 | void U_sstream_put_ulonglong(U_SStream *ss, unsigned long long num) 342 | { 343 | int i; 344 | int pos; 345 | unsigned long long remainder; 346 | char buf[24]; 347 | 348 | if (ss->status != U_SSTREAM_OK) 349 | return; 350 | 351 | /* max digits + NUL := 21 bytes on 64-bit */ 352 | 353 | 354 | pos = 0; 355 | do 356 | { 357 | remainder = num % 10; 358 | num = num / 10; 359 | buf[pos++] = (char)('0' + remainder); 360 | } 361 | while (num); 362 | 363 | if (ss->len - ss->pos < (unsigned)pos + 1) /* not enough space */ 364 | { 365 | ss->status = U_SSTREAM_ERR_NO_SPACE; 366 | return; 367 | } 368 | 369 | for (i = pos; i > 0; i--) /* reverse copy */ 370 | { 371 | ss->str[ss->pos++] = buf[i - 1]; 372 | } 373 | 374 | ss->str[ss->pos] = '\0'; 375 | } 376 | 377 | union u64f 378 | { 379 | double f; 380 | unsigned long long i; 381 | }; 382 | 383 | static int uss_is_nan(double x) 384 | { 385 | int e; 386 | union u64f u; 387 | 388 | u.i = 0; 389 | u.f = x; 390 | 391 | e = (int)(u.i >> 52 & 0x7ff); 392 | e ^= 0x7ff; 393 | u.i <<= 12; /* mantissa */ 394 | 395 | return (e == 0 && u.i) ? 1 : 0; 396 | } 397 | 398 | static int uss_is_infinity(double x) 399 | { 400 | int e; 401 | union u64f u; 402 | 403 | u.i = 0; 404 | u.f = x; 405 | 406 | e = (int)(u.i >> 52 & 0x7ff); 407 | e ^= 0x7ff; 408 | u.i <<= 12; /* mantissa */ 409 | 410 | if (e == 0 && u.i == 0) 411 | { 412 | u.f = x; 413 | e = (int)(u.i >> 63); /* sign */ 414 | return e ? 1 : 2; 415 | } 416 | 417 | return 0; 418 | } 419 | 420 | /* 421 | * Code adapted from musl libc modf implementation. 422 | * https://steve.hollasch.net/cgindex/coding/ieeefloat.html 423 | * 424 | * Sign Exponent Mantissa 425 | * 1 [63] 11 [62-52] 52 [51-0] 426 | * S EEEEEEE EEEE FFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF 427 | */ 428 | static double uss_modf(double x, double *iptr) 429 | { 430 | int e; 431 | union u64f u; 432 | unsigned long long mask; 433 | 434 | u.i = 0; /* if unsigned long long becomes > 64-bit some day */ 435 | u.f = x; 436 | /* 437 | * get 11-bit exponent, cut off sign bit 438 | */ 439 | e = (int)(u.i >> 52 & 0x7ff); 440 | e -= 1023; /* substract bias */ 441 | 442 | /* no fractional part */ 443 | /* 444 | * exponent range 2..2^52 445 | */ 446 | if (e >= 52) 447 | { 448 | *iptr = x; 449 | /* 450 | * NaN when exponent = 1024 and mantissa != 0 451 | */ 452 | if (e == 1024 && u.i << 12 != 0) /* nan */ 453 | return x; 454 | u.i &= 1ULL << 63; /* keep the sign */ 455 | return u.f; 456 | } 457 | 458 | /* no integral part*/ 459 | if (e < 0) 460 | { 461 | u.i &= 1ULL << 63; 462 | *iptr = u.f; 463 | return x; 464 | } 465 | 466 | mask = 0xFFFFFFFFFFFFFFFFULL >> 12 >> e; 467 | if ((u.i & mask) == 0) 468 | { 469 | *iptr = x; 470 | u.i &= 1ULL << 63; 471 | return u.f; 472 | } 473 | u.i &= ~mask; 474 | *iptr = u.f; 475 | return x - u.f; 476 | } 477 | 478 | void U_sstream_put_double(U_SStream *ss, double num, int precision) 479 | { 480 | unsigned i; 481 | double ipart; 482 | double frac; 483 | double prec; 484 | long long b; 485 | char buf[32]; 486 | 487 | if (uss_is_nan(num)) 488 | { 489 | U_sstream_put_str(ss, "null"); 490 | return; 491 | } 492 | 493 | b = (unsigned)uss_is_infinity(num); 494 | if (b != 0) 495 | { 496 | if (b == 1) 497 | U_sstream_put_str(ss, "-"); 498 | 499 | U_sstream_put_str(ss, "1e99999"); 500 | return; 501 | } 502 | 503 | if (precision < 1) precision = 1; 504 | else if (precision > 18) precision = 18; 505 | 506 | frac = uss_modf(num, &ipart); 507 | 508 | /* 509 | * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MIN_SAFE_INTEGER 510 | */ 511 | if (ipart > 9007199254740991.0 || ipart < -9007199254740991.0) 512 | { 513 | /* error outside of max safe range 2^53-1 */ 514 | ss->status = U_SSTREAM_ERR_RANGE; 515 | return; 516 | } 517 | 518 | if (ipart < 0) 519 | { 520 | ipart = -ipart; 521 | frac = -frac; 522 | U_sstream_put_str(ss, "-"); 523 | } 524 | 525 | prec = 10; 526 | 527 | b = (long long)ipart; 528 | U_sstream_put_longlong(ss, b); 529 | 530 | i = 0; 531 | buf[i++] = '.'; 532 | 533 | for (;precision && i < sizeof(buf) - 1; precision--) 534 | { 535 | b = (long long)(frac * prec); 536 | b = b % 10; 537 | prec *= 10; 538 | buf[i++] = (char)(b + '0'); 539 | } 540 | buf[i] = '\0'; 541 | 542 | /* strip trailing zero and dot */ 543 | for (; i && (buf[i-1] == '0' || buf[i-1] == '.'); i--) 544 | buf[i-1] = '\0'; 545 | 546 | if (buf[0]) 547 | U_sstream_put_str(ss, &buf[0]); 548 | } 549 | 550 | static const char ss_hex_table[16] = { 551 | '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' 552 | }; 553 | 554 | void U_sstream_put_hex(U_SStream *ss, const void *data, unsigned size) 555 | { 556 | unsigned i; 557 | unsigned char nib; 558 | const unsigned char *buf; 559 | 560 | if ((ss->len - ss->pos) < (size * 2) + 1) 561 | { 562 | ss->status = U_SSTREAM_ERR_NO_SPACE; 563 | return; 564 | } 565 | 566 | buf = (const unsigned char*)data; 567 | 568 | for (i = 0; i < size; i++) 569 | { 570 | nib = buf[i]; 571 | ss->str[ss->pos] = ss_hex_table[(nib & 0xF0) >> 4]; 572 | ss->pos++; 573 | ss->str[ss->pos] = ss_hex_table[(nib & 0x0F)]; 574 | ss->pos++; 575 | } 576 | 577 | ss->str[ss->pos] = '\0'; 578 | } 579 | 580 | void U_sstream_seek(U_SStream *ss, unsigned pos) 581 | { 582 | if (pos <= ss->len) 583 | ss->pos = pos; 584 | } 585 | 586 | /** Converts a base 10 number string to signed long. 587 | * 588 | * Depending on sizeof(long) 4/8 the valid numeric range is: 589 | * 590 | * 32-bit: -2147483648 ... 2147483647 591 | * 64-bit: -9223372036854775808 ... 9223372036854775807 592 | * 593 | * The err variable is a bitmap: 594 | * 595 | * 0x01 invalid input 596 | * 0x02 range overflow 597 | * 0x04 range underflow 598 | * 599 | * \param s pointer to string, doesn't have to be '\0' terminated. 600 | * \param len length of s ala strlen(s). 601 | * \param endp pointer which will be set to first non 0-9 character (must NOT be NULL). 602 | * \param err pointer to error variable (must NOT be NULL). 603 | * 604 | * \return If the conversion is successful the number is returned and err set to 0. 605 | * On failure err has a non zero value. 606 | */ 607 | long U_strtol(const char *s, unsigned len, const char **endp, int *err) 608 | { 609 | int i; 610 | int e; 611 | long ch; 612 | unsigned long max; 613 | unsigned long result; 614 | 615 | e = 0; 616 | ch = 0; 617 | result = 0; 618 | 619 | max = ~0UL; 620 | max >>= 1; 621 | 622 | if (len == 0) 623 | { 624 | *err = 1; 625 | *endp = s; 626 | return 0; 627 | } 628 | 629 | /* skip whitespace */ 630 | while (len && (*s == ' ' || *s == '\t')) 631 | { 632 | s++; 633 | len--; 634 | } 635 | 636 | i = *s == '-' ? 1 : 0; 637 | 638 | for (; (unsigned)i < len; i++) 639 | { 640 | ch = (unsigned char)s[i]; 641 | if (ch < '0' || ch > '9') 642 | break; 643 | 644 | ch = ch - '0'; 645 | e |= (result * 10 + (unsigned)ch < result) ? 2 : 0; /* overflow */ 646 | result *= 10; 647 | result += (unsigned)ch; 648 | } 649 | 650 | if (i == 1 && *s == '-') e |= 1; 651 | else if (i == 0) e |= 1; 652 | 653 | if (result > max) 654 | { 655 | if (*s != '-') e |= 2; /* overflow */ 656 | else if (result > max + 1) e |= 4; /* underflow */ 657 | } 658 | 659 | *endp = &s[i]; 660 | *err = e; 661 | 662 | if (*s == '-') 663 | return -(long)result; 664 | 665 | return (long)result; 666 | } 667 | 668 | /* custom pow() */ 669 | static double pow_helper(double base, int exponent) 670 | { 671 | int i; 672 | int count; 673 | double result; 674 | 675 | count = exponent < 0 ? -exponent : exponent; 676 | 677 | result = 1.0; 678 | 679 | if (exponent < 0) 680 | base = 1.0 / base; 681 | 682 | for (i = 0; i < count; i++) 683 | result *= base; 684 | 685 | return result; 686 | } 687 | 688 | /** Converts a floating point number string to double. 689 | * 690 | * The err variable is a bitmap: 691 | * 692 | * 0x01 invalid input 693 | * 694 | * \param s pointer to string, doesn't have to be '\0' terminated. 695 | * \param len length of s ala strlen(s). 696 | * \param endp pointer which will be set to first non 0-9 character (must NOT be NULL). 697 | * \param err pointer to error variable (must NOT be NULL). 698 | * 699 | * \return If the conversion is successful the number is returned and err set to 0. 700 | * On failure err has a non zero value. 701 | */ 702 | double U_strtod(const char *str, unsigned len, const char **endp, int *err) 703 | { 704 | int sign; 705 | int exponent; 706 | int exp_sign; 707 | int exp_num; 708 | int decimal_places; 709 | double num; 710 | int required; 711 | 712 | sign = 1; 713 | exponent = 0; 714 | exp_sign = 1; 715 | exp_num = 0; 716 | decimal_places = 0; 717 | num = 0.0; 718 | required = 0; 719 | 720 | /* skip whitespace */ 721 | while (len && (*str == ' ' || *str == '\t')) 722 | { 723 | str++; 724 | len--; 725 | } 726 | 727 | if (len) 728 | { 729 | if (*str == '-') 730 | { 731 | sign = -1; 732 | str++; 733 | len--; 734 | } 735 | else if (*str == '+') 736 | { 737 | str++; 738 | len--; 739 | } 740 | } 741 | 742 | /* integer part */ 743 | while (len && *str >= '0' && *str <= '9') 744 | { 745 | required = 1; 746 | num = num * 10 + (*str - '0'); 747 | str++; 748 | len--; 749 | } 750 | 751 | /* decimal part */ 752 | if (len && *str == '.') 753 | { 754 | str++; 755 | len--; 756 | while (len && *str >= '0' && *str <= '9') 757 | { 758 | required = 1; 759 | num = num * 10 + (*str - '0'); 760 | decimal_places++; 761 | str++; 762 | len--; 763 | } 764 | } 765 | 766 | /* handle exponent */ 767 | if (len && (*str == 'e' || *str == 'E')) 768 | { 769 | str++; 770 | len--; 771 | if (len) 772 | { 773 | if (*str == '-') 774 | { 775 | exp_sign = -1; 776 | str++; 777 | len--; 778 | } 779 | else if (*str == '+') 780 | { 781 | str++; 782 | len--; 783 | } 784 | } 785 | 786 | while (len && *str >= '0' && *str <= '9') 787 | { 788 | exp_num = exp_num * 10 + (*str - '0'); 789 | str++; 790 | len--; 791 | } 792 | exponent = exp_sign * exp_num; 793 | } 794 | 795 | /* calculate final result */ 796 | num *= pow_helper(10.0, exponent); 797 | num /= pow_helper(10.0, decimal_places); 798 | 799 | *endp = str; 800 | *err = required == 0 ? 1 : 0; 801 | 802 | return sign * num; 803 | } 804 | -------------------------------------------------------------------------------- /main_windows.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021-2025 dresden elektronik ingenieurtechnik gmbh. 3 | * All rights reserved. 4 | * 5 | * The software in this package is published under the terms of the BSD 6 | * style license a copy of which has been included with this distribution in 7 | * the LICENSE.txt file. 8 | * 9 | */ 10 | 11 | /* 12 | #pragma comment(lib, "setupapi.lib") 13 | #pragma comment(lib, "shlwapi.lib") 14 | #pragma comment(lib, "advapi32.lib") 15 | */ 16 | 17 | #define WIN32_LEAN_AND_MEAN 18 | #include 19 | #include 20 | #include 21 | #include 22 | #ifndef __WATCOMC__ 23 | #include 24 | #endif 25 | #include 26 | #include 27 | 28 | #include "gcf.h" 29 | #include "u_sstream.h" 30 | #include "u_strlen.h" 31 | #include "u_mem.h" 32 | 33 | /* in gcf.c for now */ 34 | extern void U_sstream_put_u32hex(U_SStream *ss, unsigned long val); 35 | 36 | #ifdef USE_FTD2XX 37 | #include "ftd2xx/ftd2xx.h" 38 | #endif 39 | 40 | // https://hero.handmade.network/forums/code-discussion/t/94-guide_-_how_to_avoid_c_c++_runtime_on_windows 41 | 42 | typedef struct 43 | { 44 | PL_time_t timer; 45 | HANDLE fd; 46 | HANDLE hOut; 47 | int running; 48 | unsigned char rxbuf[64]; 49 | unsigned char txbuf[2048]; 50 | unsigned long txpos; 51 | 52 | LARGE_INTEGER frequency; 53 | BOOL frequencyValid; 54 | 55 | GCF *gcf; 56 | } PL_Internal; 57 | 58 | static PL_Internal platform; 59 | 60 | 61 | static int GetComPort(const char *enumerator, Device *devs, size_t max); 62 | 63 | /* for compiling without CRT */ 64 | int _fltused=0; 65 | 66 | #ifdef _MSC_VER 67 | #pragma function(memcpy) 68 | void *memcpy(void *dst, const void *src, SIZE_T n) 69 | #else 70 | void *memcpy(void *dst, const void *src, unsigned long n) 71 | #endif 72 | { 73 | return U_memcpy(dst, src, (unsigned long)n); 74 | } 75 | 76 | #ifdef _MSC_VER 77 | #pragma function(memset) 78 | void *memset(void *dst, int c, SIZE_T count) 79 | #else 80 | void *memset(void *dst, int c, size_t count) 81 | #endif 82 | { 83 | char *p = dst; 84 | for (;count; count--) 85 | *p++ = (char)c; 86 | 87 | return dst; 88 | } 89 | 90 | #ifdef _X86_ 91 | /* mingw32 on i686 doesn't have long devision */ 92 | __attribute((externally_visible)) 93 | long __divdi3(long n, long d) 94 | { 95 | long q = 0; 96 | if (n == 0 || d == 0) 97 | return 0; 98 | 99 | while (n >= d) 100 | { 101 | ++q; 102 | n -= d; 103 | } 104 | return q; 105 | } 106 | #endif 107 | 108 | /*! Returns a monotonic time in milliseconds. */ 109 | PL_time_t PL_Time(void) 110 | { 111 | if (platform.frequencyValid) 112 | { 113 | LARGE_INTEGER now; 114 | QueryPerformanceCounter(&now); 115 | return (1000LL * now.QuadPart) / platform.frequency.QuadPart; 116 | } 117 | 118 | return GetTickCount(); 119 | } 120 | 121 | /*! Lets the programm sleep for \p ms milliseconds. */ 122 | void PL_MSleep(unsigned long ms) 123 | { 124 | Sleep((DWORD)ms); 125 | } 126 | 127 | 128 | /*! Sets a timeout \p ms in milliseconds, after which a \c EV_TIMOUT event is generated. */ 129 | void PL_SetTimeout(unsigned long ms) 130 | { 131 | platform.timer = PL_Time() + ms; 132 | } 133 | 134 | /*! Clears an active timeout. */ 135 | void PL_ClearTimeout(void) 136 | { 137 | platform.timer = 0; 138 | } 139 | 140 | /* Fills up to \p max devices in the \p devs array. 141 | 142 | The output is used in list operation (-l). 143 | */ 144 | int PL_GetDevices(Device *devs, unsigned max) 145 | { 146 | // http://www.naughter.com/enumser.html 147 | 148 | int result = 0; 149 | unsigned i = 0; 150 | 151 | ZeroMemory(devs, sizeof(*devs) * max); 152 | 153 | GetComPort("USB", devs, max); 154 | GetComPort("FTDIBUS", devs, max); 155 | 156 | for (i = 0; i < max; i++) 157 | { 158 | if (devs[i].serial[0] != '\0' && devs[i].path[0] != '\0') 159 | result++; 160 | } 161 | 162 | return result; 163 | } 164 | 165 | static int GetComPort(const char *enumerator, Device *devs, size_t max) 166 | { 167 | int devcount = 0; 168 | 169 | if (max == 0) 170 | { 171 | return devcount; 172 | } 173 | 174 | int i; 175 | char ch; 176 | U_SStream ss; 177 | Device *dev = 0; 178 | HDEVINFO DeviceInfoSet; 179 | DWORD DeviceIndex =0; 180 | SP_DEVINFO_DATA DeviceInfoData; 181 | BYTE szBuffer[256]; 182 | wchar_t wcbuf[128]; 183 | DEVPROPTYPE ulPropertyType; 184 | DWORD dwSize = 0; 185 | DWORD dwType = 0; 186 | DEVPROPKEY PropertyKey; 187 | DEVPROPTYPE PropertyType = 0; 188 | 189 | unsigned vid; 190 | unsigned pid; 191 | 192 | // setupDiGetClassDevs returns a handle to a device information set 193 | DeviceInfoSet = SetupDiGetClassDevs( 194 | NULL, 195 | enumerator, 196 | NULL, 197 | DIGCF_ALLCLASSES | DIGCF_PRESENT); 198 | 199 | if (DeviceInfoSet == INVALID_HANDLE_VALUE) 200 | return devcount; 201 | 202 | // fills a block of memory with zeros 203 | ZeroMemory(&DeviceInfoData, sizeof(SP_DEVINFO_DATA)); 204 | DeviceInfoData.cbSize = sizeof(SP_DEVINFO_DATA); 205 | // receive information about an enumerated device 206 | while (SetupDiEnumDeviceInfo( 207 | DeviceInfoSet, 208 | DeviceIndex, 209 | &DeviceInfoData) && devcount < max) 210 | { 211 | DeviceIndex++; 212 | 213 | szBuffer[0] = '\0'; 214 | 215 | if (SetupDiGetDeviceInstanceId(DeviceInfoSet, &DeviceInfoData, (_TCHAR *)&szBuffer[0], sizeof(szBuffer), NULL)) 216 | { 217 | } 218 | 219 | // USB\VID_1CF1&PID_0030\DE1995634 @ DE1995634 220 | // FTDIBUS\VID_0403+PID_6015+DJ00QBWEA\0000 @ 0000 221 | if (szBuffer[0] == '\0') 222 | continue; 223 | 224 | U_sstream_init(&ss, &szBuffer[0], U_strlen((char*)&szBuffer[0])); 225 | 226 | // filter vendor and product ids 227 | if (U_sstream_find(&ss, "VID_1CF1") && U_sstream_find(&ss, "PID_0030")) // ConBee II 228 | { 229 | vid = 0x1cf1; 230 | pid = 0x0030; 231 | } 232 | else if (U_sstream_find(&ss, "VID_0403") && U_sstream_find(&ss, "PID_6015")) // ConBee I and III 233 | { 234 | vid = 0x0403; 235 | pid = 0x6015; 236 | } 237 | else if (U_sstream_find(&ss, "VID_1A86") && U_sstream_find(&ss, "PID_7523")) // CH340 ~ Hive 238 | { 239 | vid = 0x1a86; 240 | pid = 0x7523; 241 | } 242 | else if (U_sstream_find(&ss, "VID_303A") && U_sstream_find(&ss, "PID_1001")) // Espressif ~ FLS-M 243 | { 244 | vid = 0x303a; 245 | pid = 0x1001; 246 | } 247 | else 248 | { 249 | 250 | continue; 251 | } 252 | 253 | ss.pos += 8; // move behind PID_XXXX 254 | 255 | char serial[MAX_DEV_SERIALNR_LENGTH]; 256 | serial[0] = '\0'; 257 | 258 | // extract serial number 259 | // important: look for '+' first as the FTDI serial also contains a '\' ! 260 | if (U_sstream_peek_char(&ss) == '+' || U_sstream_peek_char(&ss) == '\\') 261 | { 262 | ss.pos++; 263 | for (i = 0; ss.pos < ss.len; ss.pos++, i++) 264 | { 265 | if (i + 2 >= (int)sizeof(serial)) 266 | break; 267 | 268 | ch = ss.str[ss.pos]; 269 | 270 | if ((ch >= 'A' && ch <= 'Z') || 271 | (ch >= 'a' && ch <= 'z') || 272 | (ch >= '0' && ch <= '9')) 273 | { 274 | serial[i] = ch; 275 | } 276 | else 277 | { 278 | // for some reason FTDIBUS\VID_0403+PID_6015+DJ00QBWEA\0000 279 | // has A\0000 appended, remove here 280 | if (ch == '\\' && i != 0 && serial[i - 1] == 'A') 281 | { 282 | serial[i - 1] = '\0'; 283 | } 284 | 285 | break; 286 | } 287 | 288 | } 289 | 290 | serial[i] = '\0'; 291 | } 292 | else if (vid == 0x303a && pid == 0x1001) 293 | { 294 | serial[0] = 'u'; 295 | serial[1] = 'n'; 296 | serial[2] = 'k'; 297 | serial[3] = 'n'; 298 | serial[4] = 'o'; 299 | serial[5] = 'w'; 300 | serial[6] = 'n'; 301 | serial[7] = '\0'; 302 | } 303 | else 304 | { 305 | continue; /* no serial number? */ 306 | } 307 | 308 | if (serial[0] == '\0') 309 | continue; 310 | 311 | dev = 0; 312 | 313 | for (i = 0; i < max; i++) 314 | { 315 | U_sstream_init(&ss, devs[i].serial, U_strlen(devs[i].serial)); 316 | 317 | if (U_sstream_starts_with(&ss, serial)) 318 | { 319 | dev = &devs[i]; // already known 320 | break; 321 | } 322 | } 323 | 324 | // no device with this serial yet, take an empty one 325 | if (!dev) 326 | { 327 | for (i = 0; i < max; i++) 328 | { 329 | if (devs[i].serial[0] == '\0') 330 | { 331 | dev = &devs[i]; 332 | devcount++; 333 | U_sstream_init(&ss, dev->serial, sizeof(dev->serial)); 334 | U_sstream_put_str(&ss, serial); 335 | break; 336 | } 337 | } 338 | } 339 | 340 | if (!dev) // all slots full 341 | { 342 | PL_Printf(DBG_DEBUG, "ALL SLOTS FULL\n"); 343 | continue; 344 | } 345 | 346 | /*** check device name (only ConBee II and ConBee III) ********************/ 347 | #ifndef __WATCOMC__ 348 | /* for ConBee III this happens when enumerator == "USB" */ 349 | PropertyKey = DEVPKEY_Device_BusReportedDeviceDesc; 350 | 351 | if (SetupDiGetDevicePropertyW(DeviceInfoSet, &DeviceInfoData, &PropertyKey, 352 | &PropertyType, (BYTE*)wcbuf, sizeof(wcbuf), NULL, 0)) 353 | { 354 | if (PropertyType == DEVPROP_TYPE_STRING) 355 | { 356 | for (i = 0; wcbuf[i]; i++) 357 | { 358 | szBuffer[i] = (BYTE)wcbuf[i]; // wchar to ASCII 359 | } 360 | szBuffer[i] = '\0'; 361 | 362 | // we may get here multiple times for ConBee III 363 | // the generic FTDI name will be overwritten with ConBee III if not already set 364 | if (dev->name[0] != 'C') 365 | { 366 | for (i = 0; i < sizeof(dev->name) && szBuffer[i]; i++) 367 | dev->name[i] = (char)szBuffer[i]; 368 | 369 | dev->name[i] = '\0'; 370 | 371 | U_sstream_init(&ss, dev->name, U_strlen(dev->name)); 372 | if (U_sstream_starts_with(&ss, "ConBee")) // ConBee II and III 373 | { 374 | dev->baudrate = PL_BAUDRATE_115200; 375 | } 376 | else if (vid == 0x0403) // FTDI ConBee I 377 | { 378 | U_sstream_init(&ss, dev->name, sizeof(dev->name)); 379 | U_sstream_put_str(&ss, "Serial FTDI"); 380 | dev->baudrate = PL_BAUDRATE_38400; 381 | 382 | } 383 | else if (vid == 0x1a86) // CH340 Hive 384 | { 385 | U_sstream_init(&ss, dev->name, sizeof(dev->name)); 386 | U_sstream_put_str(&ss, "Serial CH340"); 387 | dev->baudrate = PL_BAUDRATE_115200; 388 | } 389 | else if (vid == 0x303a) // Espressif ~ FLS-M 390 | { 391 | U_sstream_init(&ss, dev->name, sizeof(dev->name)); 392 | U_sstream_put_str(&ss, "Espressif"); 393 | dev->baudrate = PL_BAUDRATE_115200; 394 | } 395 | } 396 | } 397 | } 398 | #endif 399 | /**************************************************************************/ 400 | 401 | if (devs->name[0] == '\0') 402 | continue; 403 | 404 | // retrieves a specified Plug and Play device property 405 | if (SetupDiGetDeviceRegistryProperty (DeviceInfoSet, &DeviceInfoData, SPDRP_HARDWAREID, 406 | &ulPropertyType, (BYTE*)szBuffer, 407 | sizeof(szBuffer), 408 | &dwSize)) 409 | { 410 | HKEY hDeviceRegistryKey; 411 | // get registry the key 412 | // HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\DeviceMigration\Devices\USB 413 | 414 | hDeviceRegistryKey = SetupDiOpenDevRegKey(DeviceInfoSet, &DeviceInfoData,DICS_FLAG_GLOBAL, 0, DIREG_DEV, KEY_READ); 415 | 416 | if (hDeviceRegistryKey != INVALID_HANDLE_VALUE) 417 | { 418 | // read name of the port 419 | char pszPortName[20]; 420 | dwType = 0; 421 | dwSize = sizeof(pszPortName); 422 | 423 | if ((RegQueryValueEx(hDeviceRegistryKey, "PortName", NULL, &dwType, 424 | (LPBYTE) pszPortName, &dwSize) == ERROR_SUCCESS) && (dwType == REG_SZ)) 425 | { 426 | // is a com port? 427 | if (pszPortName[0] == 'C' && pszPortName[1] == 'O' && pszPortName[2] == 'M') 428 | { 429 | if (pszPortName[3] >= '0' && pszPortName[3] <= '9') 430 | { 431 | unsigned sz = 0; 432 | for (; pszPortName[sz] && sz + 1 < sizeof(dev->path); sz++) 433 | { 434 | dev->path[sz] = pszPortName[sz]; 435 | dev->stablepath[sz] = pszPortName[sz]; 436 | } 437 | dev->path[sz] = '\0'; 438 | dev->stablepath[sz] = '\0'; 439 | } 440 | } 441 | } 442 | 443 | RegCloseKey(hDeviceRegistryKey); 444 | } 445 | } 446 | } 447 | 448 | if (DeviceInfoSet) 449 | { 450 | SetupDiDestroyDeviceInfoList(DeviceInfoSet); 451 | } 452 | 453 | return devcount; 454 | } 455 | 456 | /*! Opens the serial port connection for device. 457 | 458 | \param path - The path like /dev/ttyACM0 or COM7. 459 | \returns GCF_SUCCESS or GCF_FAILED 460 | */ 461 | GCF_Status PL_Connect(const char *path, PL_Baudrate baudrate) 462 | { 463 | char buf[32]; 464 | U_SStream ss; 465 | 466 | if (platform.fd != INVALID_HANDLE_VALUE) 467 | { 468 | PL_Printf(DBG_DEBUG, "device already connected %s\n", path); 469 | return GCF_SUCCESS; 470 | } 471 | 472 | if (U_strlen(path) > 7) 473 | { 474 | return GCF_FAILED; 475 | } 476 | 477 | U_sstream_init(&ss, buf, sizeof(buf)); 478 | if (*path == 'C') 479 | { 480 | U_sstream_put_str(&ss, "\\\\.\\"); 481 | U_sstream_put_str(&ss, path); 482 | } 483 | else if (*path == '\\') 484 | { 485 | U_sstream_put_str(&ss, path); 486 | } 487 | else 488 | { 489 | return GCF_FAILED; 490 | } 491 | 492 | PL_Printf(DBG_INFO, "connect %s, baudrate %d\n", buf, (int)baudrate); 493 | 494 | platform.txpos = 0; 495 | 496 | platform.fd = CreateFile( 497 | buf, 498 | GENERIC_READ | GENERIC_WRITE, 499 | 0, 500 | NULL, 501 | OPEN_EXISTING, 502 | 0, 503 | NULL); 504 | 505 | if (platform.fd == INVALID_HANDLE_VALUE) 506 | { 507 | PL_Printf(DBG_DEBUG, "failed to open %s\n", buf); 508 | return GCF_FAILED; 509 | } 510 | 511 | static DCB dcbSerialParams; // Initializing DCB structure 512 | static COMMTIMEOUTS timeouts; //Initializing timeouts structure 513 | 514 | ZeroMemory(&dcbSerialParams, sizeof(dcbSerialParams)); 515 | ZeroMemory(&timeouts, sizeof(timeouts)); 516 | 517 | //Setting the Parameters for the SerialPort 518 | BOOL Status; 519 | dcbSerialParams.DCBlength = sizeof(dcbSerialParams); 520 | Status = GetCommState(platform.fd, &dcbSerialParams); //retreives the current settings 521 | if (Status == FALSE) 522 | { 523 | PL_Printf(DBG_DEBUG, "\nError to Get the Com state\n\n"); 524 | goto Exit1; 525 | } 526 | 527 | if (baudrate == PL_BAUDRATE_38400) 528 | { 529 | dcbSerialParams.BaudRate = CBR_38400; 530 | } 531 | else if (baudrate == PL_BAUDRATE_115200) 532 | { 533 | dcbSerialParams.BaudRate = CBR_115200; 534 | } 535 | else 536 | { 537 | dcbSerialParams.BaudRate = CBR_115200; 538 | } 539 | 540 | dcbSerialParams.ByteSize = 8; //ByteSize = 8 541 | dcbSerialParams.StopBits = ONESTOPBIT; //StopBits = 1 542 | dcbSerialParams.Parity = NOPARITY; //Parity = None 543 | dcbSerialParams.fBinary = TRUE; 544 | 545 | Status = SetCommState(platform.fd, &dcbSerialParams); 546 | if (Status == FALSE) 547 | { 548 | PL_Printf(DBG_DEBUG, "\nError to Setting DCB Structure\n\n"); 549 | goto Exit1; 550 | } 551 | //Setting Timeouts 552 | timeouts.ReadIntervalTimeout = 1; 553 | timeouts.ReadTotalTimeoutConstant = 20; 554 | timeouts.ReadTotalTimeoutMultiplier = 1; 555 | timeouts.WriteTotalTimeoutConstant = 0; 556 | timeouts.WriteTotalTimeoutMultiplier = 0; 557 | if (SetCommTimeouts(platform.fd, &timeouts) == 0) 558 | { 559 | PL_Printf(DBG_DEBUG, "\nError to Setting Time outs"); 560 | goto Exit1; 561 | } 562 | 563 | Status = SetCommMask(platform.fd, EV_RXCHAR); 564 | if (Status == FALSE) 565 | { 566 | PL_Printf(DBG_DEBUG, "\nError to in Setting CommMask\n\n"); 567 | goto Exit1; 568 | } 569 | 570 | PL_Printf(DBG_DEBUG, "connected com port %s, %lu\n", buf, (unsigned long)baudrate); 571 | 572 | return GCF_SUCCESS; 573 | 574 | Exit1: 575 | PL_Disconnect(); 576 | return GCF_FAILED; 577 | } 578 | 579 | /*! Closed the serial port connection. */ 580 | void PL_Disconnect(void) 581 | { 582 | PL_Printf(DBG_DEBUG, "PL_Disconnect\n"); 583 | if (platform.fd != INVALID_HANDLE_VALUE) 584 | { 585 | platform.txpos = 0; 586 | CloseHandle(platform.fd); 587 | platform.fd = INVALID_HANDLE_VALUE; 588 | } 589 | GCF_HandleEvent(platform.gcf, EV_DISCONNECTED); 590 | } 591 | 592 | /*! Shuts down platform layer (ends main loop). */ 593 | void PL_ShutDown(void) 594 | { 595 | platform.running = 0; 596 | } 597 | 598 | /*! Executes a MCU reset for ConBee I via FTDI CBUS0 reset. */ 599 | int PL_ResetFTDI(int num, const char *serialnum) 600 | { 601 | (void)num; 602 | #ifdef USE_FTD2XX 603 | 604 | unsigned seriallen; 605 | DWORD dev; 606 | DWORD numDevs; 607 | DWORD deviceId; 608 | FT_HANDLE ftHandle; 609 | FT_STATUS ftStatus; 610 | FT_DEVICE device; 611 | char serial[MAX_DEV_SERIALNR_LENGTH]; 612 | char description[64]; 613 | U_SStream ss; 614 | 615 | seriallen = U_strlen(serialnum); 616 | 617 | if (seriallen == 0) /* require serial number */ 618 | return -1; 619 | 620 | U_sstream_init(&ss, (char*)serialnum, seriallen); 621 | 622 | if (FT_Initialise() != FT_OK) 623 | { 624 | return -1; 625 | } 626 | 627 | if (FT_ListDevices(&numDevs, NULL, FT_LIST_NUMBER_ONLY) != FT_OK) 628 | { 629 | return -1; 630 | } 631 | 632 | if (numDevs == 0) 633 | { 634 | return -1; 635 | } 636 | 637 | for (dev = 0; dev < numDevs; dev++) 638 | { 639 | ftStatus = FT_Open((int)dev, &ftHandle); 640 | 641 | if (ftStatus != FT_OK) 642 | continue; 643 | 644 | serial[0] = '\0'; 645 | ftStatus = FT_GetDeviceInfo(ftHandle, &device, &deviceId, serial, description, NULL); 646 | 647 | if (ftStatus == FT_OK) 648 | { 649 | if (U_sstream_starts_with(&ss, &serial[0])) 650 | { 651 | UCHAR ucMask = 0xf1; // CBUS0 --> 1 652 | 653 | /* ucMask - Required value for bit mode mask. This sets up which bits are inputs and 654 | outputs. A bit value of 0 sets the corresponding pin to an input, a bit 655 | value of 1 sets the corresponding pin to an output. 656 | */ 657 | ucMask = 0xf1; // CBUS0 --> 1 658 | ftStatus = FT_SetBitMode(ftHandle, ucMask, FT_BITMODE_CBUS_BITBANG); 659 | if (ftStatus != FT_OK) 660 | goto err_close; 661 | 662 | ucMask = 0xf0; // CBUS0 --> 0 663 | ftStatus = FT_SetBitMode(ftHandle, ucMask, FT_BITMODE_CBUS_BITBANG); 664 | if (ftStatus != FT_OK) 665 | goto err_close; 666 | 667 | ucMask = 0xf1; // CBUS0 --> 1 668 | ftStatus = FT_SetBitMode(ftHandle, ucMask, FT_BITMODE_CBUS_BITBANG); 669 | if (ftStatus != FT_OK) 670 | goto err_close; 671 | 672 | ftStatus = FT_SetBitMode(ftHandle, 0, FT_BITMODE_RESET); 673 | if (ftStatus != FT_OK) 674 | goto err_close; 675 | 676 | FT_Close(ftHandle); 677 | 678 | return 0; 679 | } 680 | } 681 | 682 | err_close: 683 | FT_Close(ftHandle); 684 | 685 | } 686 | #endif /* USE_FTD2XX */ 687 | 688 | return -1; 689 | } 690 | 691 | /*! Executes a MCU reset for RaspBee I / II via GPIO17 reset pin. */ 692 | int PL_ResetRaspBee(void) 693 | { 694 | return -1; 695 | } 696 | 697 | int PL_ReadFile(const char *path, unsigned char *buf, unsigned long buflen) 698 | { 699 | HANDLE hFile; 700 | int result = -1; 701 | DWORD nread = 0; 702 | 703 | hFile = CreateFile(path, 704 | GENERIC_READ, 705 | FILE_SHARE_READ, 706 | NULL, // default security 707 | OPEN_EXISTING, // existing file only 708 | FILE_ATTRIBUTE_NORMAL, // normal file 709 | NULL); // no attr. template 710 | 711 | if (hFile == INVALID_HANDLE_VALUE) 712 | { 713 | return result; 714 | } 715 | 716 | if (ReadFile(hFile, buf, (DWORD)buflen, &nread, NULL)) 717 | { 718 | if (nread > 0) 719 | { 720 | result = (int)nread; 721 | } 722 | } 723 | 724 | CloseHandle(hFile); 725 | 726 | return result; 727 | } 728 | 729 | 730 | void PL_Print(const char *line) 731 | { 732 | DWORD nchars; 733 | 734 | if (platform.hOut == INVALID_HANDLE_VALUE) 735 | return; 736 | 737 | for (nchars = 0; line[nchars]; nchars++) 738 | { } 739 | 740 | if (nchars) 741 | { 742 | WriteConsoleA(platform.hOut, line, nchars, NULL, NULL); 743 | } 744 | } 745 | 746 | /* TODO(mpi) This function doesn't depend on platform anymore, and 747 | * should be moved into gcf.c so that platform layers only 748 | * need to provide PL_Print() 749 | */ 750 | void PL_Printf(DebugLevel level, const char *format, ...) 751 | { 752 | #ifdef NDEBUG 753 | if (level == DBG_DEBUG) 754 | { 755 | return; 756 | } 757 | #else 758 | (void)level; 759 | #endif 760 | 761 | static char buf[1024]; 762 | U_SStream ss; 763 | const char *fmt; 764 | const char *xx; 765 | long vlong; 766 | unsigned long vulong; 767 | va_list args; 768 | 769 | va_start (args, format); 770 | 771 | xx = 0; 772 | fmt = format; 773 | U_sstream_init(&ss, buf, sizeof(buf)); 774 | 775 | for (; *fmt && ss.pos + 1 < ss.len; fmt++) 776 | { 777 | if (xx) 778 | { 779 | if (*fmt == 's') 780 | { 781 | xx = (const char*)va_arg(args, const char*); 782 | U_sstream_put_str(&ss, xx); 783 | xx = 0; 784 | } 785 | else if (*fmt == 'x' || *fmt == 'X') 786 | { 787 | vulong = (unsigned long)va_arg(args, unsigned int); 788 | U_sstream_put_u32hex(&ss, vulong); 789 | xx = 0; 790 | } 791 | else if (*fmt == 'd') 792 | { 793 | if (fmt[-1] == 'l') vlong = (long)va_arg(args, long); 794 | else vlong = (long)va_arg(args, int); 795 | U_sstream_put_long(&ss, vlong); 796 | xx = 0; 797 | } 798 | else if (*fmt == 'u') 799 | { 800 | if (fmt[-1] == 'l') vulong = (unsigned long)va_arg(args, unsigned long); 801 | else vulong = (unsigned long)va_arg(args, unsigned int); 802 | U_sstream_put_long(&ss, (long)vulong); 803 | xx = 0; 804 | } 805 | else if (*fmt >= '0' && *fmt <= '9') 806 | { 807 | } 808 | else if (*fmt == 'l') 809 | { 810 | } 811 | else if (*fmt == '%') 812 | { 813 | U_sstream_put_str(&ss, "%"); 814 | xx = 0; 815 | } 816 | else 817 | { 818 | /* unknown format specifier */ 819 | U_sstream_put_str(&ss, "??"); 820 | xx = 0; 821 | } 822 | } 823 | else if (*fmt == '%') 824 | { 825 | xx = fmt + 1; 826 | } 827 | else 828 | { 829 | ss.str[ss.pos++] = *fmt; 830 | } 831 | } 832 | 833 | ss.str[ss.pos] = '\0'; 834 | 835 | va_end (args); 836 | 837 | if (ss.pos) 838 | { 839 | PL_Print(buf); 840 | } 841 | } 842 | 843 | void UI_GetWinSize(unsigned *w, unsigned *h) 844 | { 845 | // TODO 846 | *w = 80; 847 | *h = 60; 848 | } 849 | 850 | void UI_SetCursor(unsigned x, unsigned y) 851 | { 852 | (void)x; 853 | (void)y; 854 | } 855 | 856 | 857 | int PROT_Write(const unsigned char *data, unsigned len) 858 | { 859 | if (len == 0) 860 | return 0; 861 | 862 | Assert(platform.fd != INVALID_HANDLE_VALUE); 863 | 864 | BOOL Status; 865 | DWORD BytesWritten = 0; // No of bytes written to the port 866 | 867 | //Writing data to Serial Port 868 | Status = WriteFile(platform.fd,// Handle to the Serialport 869 | data, // Data to be written to the port 870 | len, // No of bytes to write into the port 871 | &BytesWritten, // No of bytes written to the port 872 | NULL); 873 | if (Status == FALSE) 874 | { 875 | DWORD dw = GetLastError(); 876 | PL_Printf(DBG_DEBUG, "failed write com port, error: 0%08X\n", dw); 877 | return 0; 878 | } 879 | 880 | if (BytesWritten != (int)len) 881 | { 882 | PL_Printf(DBG_DEBUG, "failed write of %u bytes (%d written)\n", len, (int)BytesWritten); 883 | } 884 | else 885 | { 886 | gcfDebugHex(platform.gcf, "send", data, len); 887 | } 888 | 889 | return (int)BytesWritten; 890 | } 891 | 892 | int PROT_Putc(unsigned char ch) 893 | { 894 | Assert(platform.txpos + 1 < sizeof(platform.txbuf)); 895 | if (platform.txpos + 1 < sizeof(platform.txbuf)) 896 | { 897 | platform.txbuf[platform.txpos] = ch; 898 | platform.txpos += 1; 899 | return 1; 900 | } 901 | return 0; 902 | } 903 | 904 | int PROT_Flush(void) 905 | { 906 | int result = 0; 907 | 908 | if (platform.txpos != 0 && platform.txpos < sizeof(platform.txbuf)) 909 | { 910 | result = PROT_Write(&platform.txbuf[0], (unsigned)platform.txpos); 911 | Assert(result == (int)platform.txpos); /* support/handle partial writes? */ 912 | platform.txpos = 0; 913 | } 914 | 915 | return result; 916 | } 917 | 918 | static void plInitOutput(void) 919 | { 920 | platform.hOut = GetStdHandle(STD_OUTPUT_HANDLE); 921 | 922 | if (platform.hOut == INVALID_HANDLE_VALUE) 923 | { 924 | return; 925 | } 926 | 927 | DWORD dwMode = 0; 928 | if (!GetConsoleMode(platform.hOut, &dwMode)) 929 | { 930 | return; 931 | } 932 | 933 | /* 934 | dwMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING; 935 | if (!SetConsoleMode(platform.hOut, dwMode)) 936 | { 937 | return; 938 | } 939 | */ 940 | } 941 | 942 | static void PL_Loop(GCF *gcf) 943 | { 944 | ZeroMemory(&platform, sizeof(platform)); 945 | platform.gcf = gcf; 946 | platform.fd = INVALID_HANDLE_VALUE; 947 | 948 | plInitOutput(); 949 | 950 | platform.running = 1; 951 | platform.frequencyValid = QueryPerformanceFrequency(&platform.frequency); 952 | 953 | GCF_HandleEvent(gcf, EV_PL_STARTED); 954 | 955 | BOOL Status; 956 | while (platform.running) 957 | { 958 | GCF_HandleEvent(gcf, EV_PL_LOOP); 959 | 960 | if (platform.fd == INVALID_HANDLE_VALUE) 961 | { 962 | Sleep(20); 963 | 964 | if (platform.timer != 0) 965 | { 966 | if (platform.timer < PL_Time()) 967 | { 968 | platform.timer = 0; 969 | GCF_HandleEvent(gcf, EV_TIMEOUT); 970 | } 971 | } 972 | 973 | continue; 974 | } 975 | 976 | DWORD NoBytesRead; 977 | Status = ReadFile(platform.fd, &platform.rxbuf, sizeof(platform.rxbuf), &NoBytesRead, NULL); 978 | 979 | if (Status == FALSE) 980 | { 981 | PL_Disconnect(); 982 | continue; 983 | } 984 | else if (NoBytesRead > 0) 985 | { 986 | GCF_Received(gcf, platform.rxbuf, (int)NoBytesRead); 987 | } 988 | else if (NoBytesRead == 0) 989 | { 990 | if (platform.timer != 0) 991 | { 992 | if (platform.timer < PL_Time()) 993 | { 994 | platform.timer = 0; 995 | GCF_HandleEvent(gcf, EV_TIMEOUT); 996 | } 997 | } 998 | else 999 | { 1000 | Sleep(4); 1001 | } 1002 | } 1003 | } 1004 | } 1005 | 1006 | static int PL_Main(int argc, char **argv) 1007 | { 1008 | GCF *gcf = GCF_Init(argc, argv); 1009 | if (gcf == NULL) 1010 | return 2; 1011 | 1012 | PL_Loop(gcf); 1013 | 1014 | GCF_Exit(gcf); 1015 | 1016 | return 0; 1017 | } 1018 | 1019 | #define MAX_CMDLINE_ARGS 16 1020 | #define MAX_CMDLINE_LEN 512 1021 | 1022 | void mainCRTStartup(void) 1023 | { 1024 | int i; 1025 | int argc; 1026 | 1027 | unsigned cmdllen = 0; 1028 | static char *argv[MAX_CMDLINE_ARGS]; 1029 | static char cmdlbuf[MAX_CMDLINE_LEN]; 1030 | 1031 | { 1032 | LPSTR pcmd = GetCommandLineA(); 1033 | cmdllen = 0; 1034 | 1035 | if (pcmd) 1036 | { 1037 | for (i = 0; pcmd[i] && (i + 1) < MAX_CMDLINE_LEN; i++) 1038 | { 1039 | cmdlbuf[i] = pcmd[i]; 1040 | } 1041 | cmdlbuf[i] = '\0'; 1042 | cmdllen = i; 1043 | } 1044 | } 1045 | 1046 | argc = 0; 1047 | 1048 | if (cmdllen) 1049 | { 1050 | int quote; 1051 | U_SStream ss; 1052 | U_sstream_init(&ss, cmdlbuf, cmdllen); 1053 | 1054 | quote = 0; 1055 | for (;argc < MAX_CMDLINE_ARGS && ss.pos < ss.len; argc++) 1056 | { 1057 | argv[argc] = &ss.str[ss.pos]; 1058 | for (;ss.pos < ss.len; ss.pos++) 1059 | { 1060 | if (ss.str[ss.pos] == '"') 1061 | { 1062 | quote = quote ? 0 : 1; 1063 | } 1064 | if (ss.str[ss.pos] == ' ' && quote == 0) 1065 | { 1066 | ss.str[ss.pos] = '\0'; 1067 | ss.pos++; 1068 | break; 1069 | } 1070 | } 1071 | } 1072 | } 1073 | 1074 | int ret = PL_Main(argc, argv); 1075 | ExitProcess(ret); 1076 | } 1077 | --------------------------------------------------------------------------------