|
11 | 11 | * <fenn AT users DOT sourceforge DOT net> |
12 | 12 | * Stephen Wille Padnos |
13 | 13 | * <swpadnos AT users DOT sourceforge DOT net> |
| 14 | + * Petter Reinholdtsen <pere@hungry.com> |
14 | 15 | * |
15 | 16 | * This program is free software; you can redistribute it and/or |
16 | 17 | * modify it under the terms of version 2 of the GNU General |
@@ -2823,6 +2824,264 @@ int do_setexact_cmd() { |
2823 | 2824 | return retval; |
2824 | 2825 | } |
2825 | 2826 |
|
| 2827 | +#include <map> |
| 2828 | +#include <vector> |
| 2829 | +#include <algorithm> |
| 2830 | + |
| 2831 | +static void plantuml_escape_alias(const char *name, FILE *dst) { |
| 2832 | + const char *p = name; |
| 2833 | + while (*p) { |
| 2834 | + if (*p == '-' || *p == '.' || *p == ' ') fputc('_', dst); |
| 2835 | + else fputc(*p, dst); |
| 2836 | + p++; |
| 2837 | + } |
| 2838 | +} |
| 2839 | + |
| 2840 | +static void plantuml_escape_string(const char *name, FILE *dst) { |
| 2841 | + const char *p = name; |
| 2842 | + while (*p) { |
| 2843 | + if (*p == '\\' || *p == '"') fputc('\\', dst); |
| 2844 | + fputc(*p, dst); |
| 2845 | + p++; |
| 2846 | + } |
| 2847 | +} |
| 2848 | + |
| 2849 | + |
| 2850 | +static std::string pin_to_instance(const char *pin_name) { |
| 2851 | + const char *last_dot = strrchr(pin_name, '.'); |
| 2852 | + if (last_dot) return std::string(pin_name, last_dot - pin_name); |
| 2853 | + return std::string(pin_name); |
| 2854 | +} |
| 2855 | + |
| 2856 | +static const char *pin_field_name(const char *pin_name) { |
| 2857 | + const char *last_dot = strrchr(pin_name, '.'); |
| 2858 | + return last_dot ? last_dot + 1 : pin_name; |
| 2859 | +} |
| 2860 | + |
| 2861 | +void generate_plantuml(FILE *dst) { |
| 2862 | + struct pin_entry_t { |
| 2863 | + char full_name[HAL_NAME_LEN+1]; |
| 2864 | + bool connected; |
| 2865 | + }; |
| 2866 | + |
| 2867 | + struct inst_pins_t { |
| 2868 | + char display_name[HAL_NAME_LEN+1]; |
| 2869 | + char comp_type[HAL_NAME_LEN+1]; |
| 2870 | + std::vector<pin_entry_t> in_pins; |
| 2871 | + std::vector<pin_entry_t> out_pins; |
| 2872 | + }; |
| 2873 | + |
| 2874 | + // Signal entity: hexagon with connected writer/readers per pin |
| 2875 | + struct sig_pin_t { |
| 2876 | + char alias[HAL_NAME_LEN+1]; // instance alias (e.g. and2_0) |
| 2877 | + char field[64]; // last segment of pin name |
| 2878 | + }; |
| 2879 | + |
| 2880 | + struct sig_entry_t { |
| 2881 | + char name[HAL_NAME_LEN+1]; |
| 2882 | + sig_pin_t *writer; // single writer pin (or NULL) |
| 2883 | + std::vector<sig_pin_t *> readers; |
| 2884 | + }; |
| 2885 | + |
| 2886 | + std::map<std::string, inst_pins_t *> inst_map; |
| 2887 | + std::vector<sig_entry_t> signals; |
| 2888 | + |
| 2889 | + try { |
| 2890 | + rtapi_mutex_get(&(hal_data->mutex)); |
| 2891 | + |
| 2892 | + // Collect all pins per instance |
| 2893 | + SHMFIELD(hal_pin_t) next = hal_data->pin_list_ptr; |
| 2894 | + while (next != 0) { |
| 2895 | + hal_pin_t *pin = SHMPTR(next); |
| 2896 | + std::string inst_name = pin_to_instance(pin->name); |
| 2897 | + |
| 2898 | + if (inst_map.find(inst_name) == inst_map.end()) { |
| 2899 | + inst_pins_t *ip = new inst_pins_t(); |
| 2900 | + snprintf(ip->display_name, sizeof(ip->display_name), "%s", inst_name.c_str()); |
| 2901 | + ip->comp_type[0] = '\0'; |
| 2902 | + ip->in_pins.reserve(32); |
| 2903 | + ip->out_pins.reserve(32); |
| 2904 | + inst_map[inst_name] = ip; |
| 2905 | + } |
| 2906 | + |
| 2907 | + // Look up component type from pin owner |
| 2908 | + hal_comp_t *owner = SHMPTR(pin->owner_ptr); |
| 2909 | + if (owner && strlen(inst_map[inst_name]->comp_type) == 0) { |
| 2910 | + snprintf(inst_map[inst_name]->comp_type, sizeof(inst_map[inst_name]->comp_type), "%s", owner->name); |
| 2911 | + } |
| 2912 | + |
| 2913 | + pin_entry_t pe; |
| 2914 | + snprintf(pe.full_name, sizeof(pe.full_name), "%s", pin->name); |
| 2915 | + pe.connected = false; |
| 2916 | + |
| 2917 | + if (pin->dir == HAL_IN) { |
| 2918 | + inst_map[inst_name]->in_pins.push_back(pe); |
| 2919 | + } else { |
| 2920 | + inst_map[inst_name]->out_pins.push_back(pe); |
| 2921 | + } |
| 2922 | + |
| 2923 | + next = pin->next_ptr; |
| 2924 | + } |
| 2925 | + |
| 2926 | + // Walk signals to find connected pins and build signal entries |
| 2927 | + SHMFIELD(hal_sig_t) sig_next = hal_data->sig_list_ptr; |
| 2928 | + while (sig_next != 0) { |
| 2929 | + hal_sig_t *sig = SHMPTR(sig_next); |
| 2930 | + hal_pin_t *writer = NULL; |
| 2931 | + std::vector<hal_pin_t *> readers; |
| 2932 | + |
| 2933 | + hal_pin_t *pin = halpr_find_pin_by_sig(sig, 0); |
| 2934 | + while (pin != 0) { |
| 2935 | + if (pin->dir == HAL_OUT) writer = pin; |
| 2936 | + else readers.push_back(pin); |
| 2937 | + pin = halpr_find_pin_by_sig(sig, pin); |
| 2938 | + } |
| 2939 | + |
| 2940 | + // Emit signals that have at least one writer or reader connected |
| 2941 | + if (writer || !readers.empty()) { |
| 2942 | + sig_entry_t se; |
| 2943 | + snprintf(se.name, sizeof(se.name), "%s", sig->name); |
| 2944 | + se.writer = NULL; |
| 2945 | + se.readers.reserve(readers.size()); |
| 2946 | + |
| 2947 | + // Build writer entry and mark pin connected |
| 2948 | + if (writer) { |
| 2949 | + std::string wi = pin_to_instance(writer->name); |
| 2950 | + auto *wip = inst_map[wi]; |
| 2951 | + if (wip) { |
| 2952 | + for (auto &pe : wip->out_pins) { |
| 2953 | + if (strcmp(pe.full_name, writer->name) == 0) pe.connected = true; |
| 2954 | + } |
| 2955 | + } |
| 2956 | + sig_pin_t *sp = new sig_pin_t(); |
| 2957 | + snprintf(sp->alias, sizeof(sp->alias), "%s", wi.c_str()); |
| 2958 | + const char *wf = pin_field_name(writer->name); |
| 2959 | + size_t wflen = strlen(wf); |
| 2960 | + if (wflen >= sizeof(sp->field)) wflen = sizeof(sp->field) - 1; |
| 2961 | + memcpy(sp->field, wf, wflen); |
| 2962 | + sp->field[wflen] = '\0'; |
| 2963 | + se.writer = sp; |
| 2964 | + } |
| 2965 | + |
| 2966 | + // Build reader entries and mark pins connected |
| 2967 | + for (auto &reader : readers) { |
| 2968 | + std::string ri = pin_to_instance(reader->name); |
| 2969 | + auto *rip = inst_map[ri]; |
| 2970 | + if (rip) { |
| 2971 | + for (auto &pe : rip->in_pins) { |
| 2972 | + if (strcmp(pe.full_name, reader->name) == 0) pe.connected = true; |
| 2973 | + } |
| 2974 | + } |
| 2975 | + sig_pin_t *sp = new sig_pin_t(); |
| 2976 | + snprintf(sp->alias, sizeof(sp->alias), "%s", ri.c_str()); |
| 2977 | + const char *rf = pin_field_name(reader->name); |
| 2978 | + size_t rflen = strlen(rf); |
| 2979 | + if (rflen >= sizeof(sp->field)) rflen = sizeof(sp->field) - 1; |
| 2980 | + memcpy(sp->field, rf, rflen); |
| 2981 | + sp->field[rflen] = '\0'; |
| 2982 | + se.readers.push_back(sp); |
| 2983 | + } |
| 2984 | + |
| 2985 | + signals.push_back(se); |
| 2986 | + } |
| 2987 | + |
| 2988 | + sig_next = sig->next_ptr; |
| 2989 | + } |
| 2990 | + |
| 2991 | + rtapi_mutex_give(&(hal_data->mutex)); |
| 2992 | + } catch (...) { |
| 2993 | + rtapi_mutex_give(&(hal_data->mutex)); |
| 2994 | + |
| 2995 | + // Cleanup any partial allocations before re-throwing |
| 2996 | + for (auto &se : signals) { |
| 2997 | + delete se.writer; |
| 2998 | + for (auto &rp : se.readers) delete rp; |
| 2999 | + } |
| 3000 | + for (auto &kv : inst_map) delete kv.second; |
| 3001 | + throw; |
| 3002 | + } |
| 3003 | + |
| 3004 | + fprintf(dst, "@startuml\n"); |
| 3005 | + |
| 3006 | + // Emit only instances that have at least one connected pin |
| 3007 | + for (auto &kv : inst_map) { |
| 3008 | + inst_pins_t *ip = kv.second; |
| 3009 | + bool has_conn = false; |
| 3010 | + for (const auto &pe : ip->in_pins) if (pe.connected) { has_conn = true; break; } |
| 3011 | + for (const auto &pe : ip->out_pins) if (pe.connected) { has_conn = true; break; } |
| 3012 | + if (!has_conn) continue; |
| 3013 | + |
| 3014 | + fputs("[", dst); |
| 3015 | + fputs(ip->display_name, dst); |
| 3016 | + if (strlen(ip->comp_type) > 0) { |
| 3017 | + fprintf(dst, " (%s)", ip->comp_type); |
| 3018 | + } |
| 3019 | + |
| 3020 | + for (const auto &pe : ip->in_pins) { |
| 3021 | + const char *f = pin_field_name(pe.full_name); |
| 3022 | + fputs("\\n", dst); |
| 3023 | + fputs(f, dst); |
| 3024 | + fputs(" in", dst); |
| 3025 | + if (!pe.connected) fputs(" (unconnected)", dst); |
| 3026 | + } |
| 3027 | + for (const auto &pe : ip->out_pins) { |
| 3028 | + const char *f = pin_field_name(pe.full_name); |
| 3029 | + fputs("\\n", dst); |
| 3030 | + fputs(f, dst); |
| 3031 | + fputs(" out", dst); |
| 3032 | + if (!pe.connected) fputs(" (unconnected)", dst); |
| 3033 | + } |
| 3034 | + |
| 3035 | + fprintf(dst, "] as "); |
| 3036 | + plantuml_escape_alias(ip->display_name, dst); |
| 3037 | + fprintf(dst, "\n"); |
| 3038 | + } |
| 3039 | + |
| 3040 | + // Emit signals as queue entities (hexagon not available in PlantUML v1.2020) |
| 3041 | + for (size_t i = 0; i < signals.size(); i++) { |
| 3042 | + char alias_buf[HAL_NAME_LEN + 1]; |
| 3043 | + snprintf(alias_buf, sizeof(alias_buf), "sig_%zu", i); |
| 3044 | + fprintf(dst, "queue \""); |
| 3045 | + plantuml_escape_string(signals[i].name, dst); |
| 3046 | + fprintf(dst, "\" as %s\n", alias_buf); |
| 3047 | + } |
| 3048 | + |
| 3049 | + // Emit edges: writer --> signal and signal --> reader |
| 3050 | + for (size_t i = 0; i < signals.size(); i++) { |
| 3051 | + char alias_buf[HAL_NAME_LEN + 1]; |
| 3052 | + snprintf(alias_buf, sizeof(alias_buf), "sig_%zu", i); |
| 3053 | + |
| 3054 | + if (signals[i].writer) { |
| 3055 | + plantuml_escape_alias(signals[i].writer->alias, dst); |
| 3056 | + fprintf(dst, " \""); |
| 3057 | + plantuml_escape_string(signals[i].writer->field, dst); |
| 3058 | + fprintf(dst, "\" --> "); |
| 3059 | + fputs(alias_buf, dst); |
| 3060 | + fprintf(dst, "\n"); |
| 3061 | + } |
| 3062 | + for (auto &rp : signals[i].readers) { |
| 3063 | + fputs(alias_buf, dst); |
| 3064 | + fprintf(dst, " --> \""); |
| 3065 | + plantuml_escape_string(rp->field, dst); |
| 3066 | + fprintf(dst, "\" "); |
| 3067 | + plantuml_escape_alias(rp->alias, dst); |
| 3068 | + fprintf(dst, "\n"); |
| 3069 | + } |
| 3070 | + } |
| 3071 | + |
| 3072 | + // Cleanup signal allocations |
| 3073 | + for (auto &se : signals) { |
| 3074 | + delete se.writer; |
| 3075 | + for (auto &rp : se.readers) delete rp; |
| 3076 | + } |
| 3077 | + |
| 3078 | + for (auto &kv : inst_map) { |
| 3079 | + delete kv.second; |
| 3080 | + } |
| 3081 | + |
| 3082 | + fprintf(dst, "@enduml\n"); |
| 3083 | +} |
| 3084 | + |
2826 | 3085 | int do_help_cmd(char *command) |
2827 | 3086 | { |
2828 | 3087 | if (!command) { |
|
0 commit comments