diff --git a/g2core/config.cpp b/g2core/config.cpp index 5b0b048c..36166f2b 100644 --- a/g2core/config.cpp +++ b/g2core/config.cpp @@ -692,6 +692,7 @@ nvObj_t *nv_add_conditional_message(const char *string) // conditionally add * Inputs: * json_flags = JSON_OBJECT_FORMAT - print just the body w/o header or footer * json_flags = JSON_RESPONSE_FORMAT - print a full "r" object with footer + * json_flags = JSON_RESPONSE_TO_MUTED_FORMAT - JSON_RESPONSE_FORMAT, but only to muted channels * * text_flags = TEXT_INLINE_PAIRS - print text as name/value pairs on a single line * text_flags = TEXT_INLINE_VALUES - print text as comma separated values on a single line diff --git a/g2core/controller.cpp b/g2core/controller.cpp index b92b029f..34927f08 100755 --- a/g2core/controller.cpp +++ b/g2core/controller.cpp @@ -72,7 +72,7 @@ static stat_t _sync_to_planner(void); static stat_t _sync_to_tx_buffer(void); static stat_t _dispatch_command(void); static stat_t _dispatch_control(void); -static void _dispatch_kernel(void); +static void _dispatch_kernel(const devflags_t flags); static stat_t _controller_state(void); // manage controller state transitions static Motate::OutputPin safe_pin; @@ -191,7 +191,7 @@ static stat_t _dispatch_control() if (cs.controller_state != CONTROLLER_PAUSED) { devflags_t flags = DEV_IS_CTRL; if ((cs.bufp = xio_readline(flags, cs.linelen)) != NULL) { - _dispatch_kernel(); + _dispatch_kernel(flags); } } return (STAT_OK); @@ -202,15 +202,22 @@ static stat_t _dispatch_command() if (cs.controller_state != CONTROLLER_PAUSED) { devflags_t flags = DEV_IS_BOTH; if ((!mp_planner_is_full()) && (cs.bufp = xio_readline(flags, cs.linelen)) != NULL) { - _dispatch_kernel(); + _dispatch_kernel(flags); } } return (STAT_OK); } -static void _dispatch_kernel() +static void _dispatch_kernel(const devflags_t flags) { stat_t status; + + if (flags & DEV_IS_MUTED) { + status = STAT_INPUT_FROM_MUTED_CHANNEL_ERROR; + nv_reset_nv_list(); // get a fresh nvObj list + nv_add_string((const char *)"msg", "lines from muted devices are ignored"); + nv_print_list(status, TEXT_NO_PRINT, JSON_RESPONSE_TO_MUTED_FORMAT); + } while ((*cs.bufp == SPC) || (*cs.bufp == TAB)) { // position past any leading whitespace cs.bufp++; @@ -299,6 +306,23 @@ void controller_set_connected(bool is_connected) { } } +/* + * controller_set_muted(bool) - hook for xio to tell the controller that we + * have/don't have one or more muted devices. + */ + +void controller_set_muted(bool is_muted) { + // TODO: care about text mode + if (is_muted) { + // one channel just got muted. + const bool only_to_muted = true; + xio_writeline("{\"muted\":true}\n", only_to_muted); + } else { + // one channel just got unmuted, announce it (except to the muted) + xio_writeline("{\"muted\":false}\n"); + } +} + /* * controller_parse_control() - return true if command is a control (versus data) * Note: parsing for control is somewhat naiive. This will need to get better @@ -456,4 +480,4 @@ stat_t _test_system_assertions() return (STAT_OK); } - \ No newline at end of file + diff --git a/g2core/controller.h b/g2core/controller.h index e7e2f01e..ad307f88 100755 --- a/g2core/controller.h +++ b/g2core/controller.h @@ -84,6 +84,7 @@ extern controller_t cs; // controller state structure void controller_init(void); void controller_run(void); void controller_set_connected(bool is_connected); +void controller_set_muted(bool is_muted); bool controller_parse_control(char *p); #endif // End of include guard: CONTROLLER_H_ONCE diff --git a/g2core/error.h b/g2core/error.h index 15ff8e65..5c802261 100644 --- a/g2core/error.h +++ b/g2core/error.h @@ -204,7 +204,7 @@ char *get_status_message(stat_t status); #define STAT_MAX_DEPTH_EXCEEDED 115 // JSON exceeded maximum nesting depth #define STAT_VALUE_TYPE_ERROR 116 // JSON value does not agree with variable type -#define STAT_ERROR_117 117 +#define STAT_INPUT_FROM_MUTED_CHANNEL_ERROR 117 // input from a muted channel was ignored #define STAT_ERROR_118 118 #define STAT_ERROR_119 119 diff --git a/g2core/json_parser.cpp b/g2core/json_parser.cpp index 9aa2abdd..7a8859a9 100755 --- a/g2core/json_parser.cpp +++ b/g2core/json_parser.cpp @@ -499,7 +499,9 @@ void json_print_list(stat_t status, uint8_t flags) switch (flags) { case JSON_NO_PRINT: break; case JSON_OBJECT_FORMAT: { json_print_object(nv_body); break; } - case JSON_RESPONSE_FORMAT: { json_print_response(status); break; } + case JSON_RESPONSE_FORMAT: + case JSON_RESPONSE_TO_MUTED_FORMAT: + { json_print_response(status, flags == JSON_RESPONSE_TO_MUTED_FORMAT); break; } } } @@ -522,7 +524,7 @@ void json_print_list(stat_t status, uint8_t flags) * on all the (non-silent) responses. */ -void json_print_response(uint8_t status) +void json_print_response(uint8_t status, const bool only_to_muted /*= false*/) { if (js.json_verbosity == JV_SILENT) { // silent means no responses return; @@ -599,7 +601,7 @@ void json_print_response(uint8_t status) // serialize the JSON response and print it if there were no errors if (json_serialize(nv_header, cs.out_buf, sizeof(cs.out_buf)) >= 0) { - xio_writeline(cs.out_buf); + xio_writeline(cs.out_buf, only_to_muted); } } diff --git a/g2core/json_parser.h b/g2core/json_parser.h index e379ede3..c45894f6 100755 --- a/g2core/json_parser.h +++ b/g2core/json_parser.h @@ -57,7 +57,8 @@ typedef enum { typedef enum { // json output print modes JSON_NO_PRINT = 0, // don't print anything if you find yourself in JSON mode JSON_OBJECT_FORMAT, // print just the body as a json object - JSON_RESPONSE_FORMAT // print the header/body/footer as a response object + JSON_RESPONSE_FORMAT, // print the header/body/footer as a response object + JSON_RESPONSE_TO_MUTED_FORMAT // print the header/body/footer as a response object, only to muted channels } jsonFormats; typedef struct jsSingleton { @@ -85,7 +86,7 @@ void json_parser(char *str); void json_parse_for_exec(char *str, bool execute); uint16_t json_serialize(nvObj_t *nv, char *out_buf, uint16_t size); void json_print_object(nvObj_t *nv); -void json_print_response(uint8_t status); +void json_print_response(uint8_t status, const bool only_to_muted = false); void json_print_list(stat_t status, uint8_t flags); stat_t json_set_jv(nvObj_t *nv); diff --git a/g2core/settings/settings_Printrbot_Simple_1608.h b/g2core/settings/settings_Printrbot_Simple_1608.h index d9c71c28..88c022b1 100755 --- a/g2core/settings/settings_Printrbot_Simple_1608.h +++ b/g2core/settings/settings_Printrbot_Simple_1608.h @@ -59,6 +59,7 @@ #define COMM_MODE JSON_MODE // one of: TEXT_MODE, JSON_MODE #define XIO_ENABLE_FLOW_CONTROL FLOW_CONTROL_RTS // FLOW_CONTROL_OFF, FLOW_CONTROL_RTS +#define XIO_UART_MUTES_WHEN_USB_CONNECTED 1 // Mute the UART when USB connects #define TEXT_VERBOSITY TV_VERBOSE // one of: TV_SILENT, TV_VERBOSE #define JSON_VERBOSITY JV_LINENUM // one of: JV_SILENT, JV_FOOTER, JV_CONFIGS, JV_MESSAGES, JV_LINENUM, JV_VERBOSE diff --git a/g2core/settings/settings_default.h b/g2core/settings/settings_default.h index bb33cdd2..887ed005 100755 --- a/g2core/settings/settings_default.h +++ b/g2core/settings/settings_default.h @@ -141,6 +141,10 @@ #define XIO_ENABLE_FLOW_CONTROL FLOW_CONTROL_RTS // FLOW_CONTROL_OFF, FLOW_CONTROL_XON, FLOW_CONTROL_RTS #endif +#ifndef XIO_UART_MUTES_WHEN_USB_CONNECTED +#define XIO_UART_MUTES_WHEN_USB_CONNECTED 0 // UART will be muted when USB connected (off by default) +#endif + #ifndef JSON_VERBOSITY #define JSON_VERBOSITY JV_MESSAGES // {jv: JV_SILENT, JV_FOOTER, JV_CONFIGS, JV_MESSAGES, JV_LINENUM, JV_VERBOSE #endif diff --git a/g2core/xio.cpp b/g2core/xio.cpp index cc9dc874..7678899b 100755 --- a/g2core/xio.cpp +++ b/g2core/xio.cpp @@ -116,14 +116,18 @@ struct xioDeviceWrapperBase { // C++ base class for device primit // bool canWrite() { return caps & DEV_CAN_WRITE; } // bool canBeCtrl() { return caps & DEV_CAN_BE_CTRL; } // bool canBeData() { return caps & DEV_CAN_BE_DATA; } - bool isAlwaysDataAndCtrl() { return caps & DEV_IS_ALWAYS_BOTH; } bool isCtrl() { return flags & DEV_IS_CTRL; } // called externally: DeviceWrappers[i]->isCtrl() bool isData() { return flags & DEV_IS_DATA; } // subclasses can call directly (no pointer): isCtrl() bool isPrimary() { return flags & DEV_IS_PRIMARY; } + + bool isAlwaysDataAndCtrl() { return caps & DEV_IS_ALWAYS_BOTH; } + bool isMuteAsSecondary() { return caps & DEV_IS_MUTE_SECONDARY; } + bool isConnected() { return flags & DEV_IS_CONNECTED; } bool isNotConnected() { return !(flags & DEV_IS_CONNECTED); } bool isReady() { return flags & DEV_IS_READY; } bool isActive() { return flags & DEV_IS_ACTIVE; } + bool isMuted() { return flags & DEV_IS_MUTED; } // Combination checks bool isCtrlAndActive() { return ((flags & (DEV_IS_CTRL|DEV_IS_ACTIVE)) == (DEV_IS_CTRL|DEV_IS_ACTIVE)); } @@ -143,13 +147,16 @@ struct xioDeviceWrapperBase { // C++ base class for device primit void setAsConnectedAndReady() { flags |= ( DEV_IS_CONNECTED | DEV_IS_READY); }; void setAsPrimaryActiveDualRole() { - if (isAlwaysDataAndCtrl()) { - flags |= (DEV_IS_CTRL | DEV_IS_DATA | DEV_IS_ACTIVE); + if (isAlwaysDataAndCtrl() || isMuteAsSecondary()) { + // In both cases, it cannot be a PRIMARY + // Also, we remove a MUTED flag + flags = (flags & ~DEV_IS_MUTED) | (DEV_IS_CTRL | DEV_IS_DATA | DEV_IS_ACTIVE); } else { flags |= (DEV_IS_CTRL | DEV_IS_DATA | DEV_IS_PRIMARY | DEV_IS_ACTIVE); } }; void setAsActiveData() { flags |= ( DEV_IS_DATA | DEV_IS_ACTIVE); }; + void setAsMuted() { flags = (flags & ~(DEV_IS_PRIMARY | DEV_IS_DATA | DEV_IS_CTRL)) | DEV_IS_MUTED; }; void clearFlags() { flags = DEV_FLAGS_CLEAR; } xioDeviceWrapperBase(uint8_t _caps) : caps(_caps), @@ -201,6 +208,7 @@ struct xio_t { }; void remove_data_from_primary() { + // Why is this first pass here? -RG for (int8_t i = 0; i < _dev_count; ++i) { if (DeviceWrappers[i]->isDataAndActive()) { return; @@ -214,10 +222,28 @@ struct xio_t { } }; - void deactivate_all_channels() { - for(int8_t i = 0; i < _dev_count; ++i) { - DeviceWrappers[i]->clearActive(); + bool check_muted_secondary_channels() { + bool muted_something = false; + for (int8_t i = 0; i < _dev_count; ++i) { + if (DeviceWrappers[i]->isMuteAsSecondary()) { + DeviceWrappers[i]->setAsMuted(); + muted_something = true; + } } + return muted_something; + } + + bool deactivate_and_unmute_channels() { + bool unmuted_something = false; + for(int8_t i = 0; i < _dev_count; ++i) { + if (DeviceWrappers[i]->isMuted()) { + unmuted_something = true; + DeviceWrappers[i]->setAsPrimaryActiveDualRole(); // NOTE: muted secondary devices won't be set PRIMARY + } else { + DeviceWrappers[i]->clearActive(); + } + } + return unmuted_something; }; // ##### Cross-Device read/write/etc. functions @@ -232,11 +258,17 @@ struct xio_t { * In the current environment, these are not foreseen to cause trouble since these * are blocking writes and we expect to only really be writing to one device. */ - size_t write(const char *buffer, size_t size) + size_t write(const char *buffer, size_t size, bool only_to_muted) { size_t total_written = -1; for (int8_t i = 0; i < _dev_count; ++i) { - if (DeviceWrappers[i]->isCtrlAndActive()) { + bool ok_channel = false; + if (!only_to_muted) { + ok_channel = DeviceWrappers[i]->isCtrlAndActive(); + } else { + ok_channel = DeviceWrappers[i]->isMuted(); + } + if (ok_channel) { const char *buf = buffer; int16_t to_write = size; while (to_write > 0) { @@ -256,10 +288,10 @@ struct xio_t { * The input buffer must be NUL terminated */ - int16_t writeline(const char *buffer) + int16_t writeline(const char *buffer, bool only_to_muted) { int16_t len = strlen(buffer); - return write(buffer, len); + return write(buffer, len, only_to_muted); }; /* @@ -331,8 +363,8 @@ struct xio_t { // We only do this second pass if this is not a CTRL-only read if (!checkForCtrlOnly(limit_flags)) { for (uint8_t dev=0; dev < _dev_count; dev++) { - if (!DeviceWrappers[dev]->isActive()) - continue; +// if (!DeviceWrappers[dev]->isActive()) +// continue; ret_buffer = DeviceWrappers[dev]->readline(limit_flags, size); @@ -884,29 +916,60 @@ struct xioDeviceWrapper : xioDeviceWrapperBase { // describes a device for re void connectedStateChanged(bool connected) { if (connected) { if (isNotConnected()) { - //USB0 or UART has just connected - //Case 1: This is the first channel to connect - - // set it as CTRL+DATA+PRIMARY channel - //Case 2: This is the second (or later) channel to connect - - // set it as DATA channel, remove DATA flag from PRIMARY channel - //... inactive channels are counted as closed + // USB0 or UART has just connected + // If one of the devices isAlwaysDataAndCtrl(): + // We treat *it* as if it's the only device connected. + // We treat *the other devices* as if it's NOT connected. + // Case 1: This is the first channel to connect - + // set it as CTRL+DATA+PRIMARY channel + // mark all isMutedAsSecondary() as MUTED, and call controller_set_muted(true) if needed + // Case 2: This is the second (or later) channel to connect - + // Case 2a: This device is !isMuteAsSecondary() + // set it as DATA channel, remove DATA flag from PRIMARY channel + // mark all isMutedAsSecondary() as MUTED, and call controller_set_muted(true) if needed + // ... inactive channels are counted as closed + // Case 2b: This devices isMuteAsSecondary(), and needs to be "muted." + // set it as a MUTED channel, call controller_set_connected(true) + // then controller_set_muted(true) + setAsConnectedAndReady(); if (isAlwaysDataAndCtrl()) { + // Case 1 (ignoring others) setActive(); controller_set_connected(true); + + // Case 2b (not ignoring others) + if (isMuteAsSecondary() && xio.others_connected(this)) { + controller_set_muted(true); // something was muted + } + return; } if(!xio.others_connected(this)) { // Case 1 setAsPrimaryActiveDualRole(); - // report that we now have a connection (only for the first one) + // report that there is now have a connection (only for the first one) controller_set_connected(true); - } else { - // Case 2 + // make sure secondary channels (that don't show up in isConnected) are muted + if (xio.check_muted_secondary_channels()) { + controller_set_muted(true); // something was muted + } + } + else if (isMuteAsSecondary()) { + // Case 2b + setAsMuted(); + controller_set_connected(true); // it DID just just get connected + controller_set_muted(true); // but it muted it too + } + else { + // Case 2a xio.remove_data_from_primary(); + if (xio.check_muted_secondary_channels()) { + controller_set_muted(true); // something was muted + } setAsActiveData(); } } // flags & DEV_IS_DISCONNECTED @@ -916,9 +979,9 @@ struct xioDeviceWrapper : xioDeviceWrapperBase { // describes a device for re //USB0 has just disconnected //Case 1: This channel disconnected while it was a ctrl+data channel (and no other channels are open) - - // finalize this channel + // finalize this channel, unmute muted channels //Case 2: This channel disconnected while it was a primary ctrl channel (and other channels are open) - - // finalize this channel, deactivate other channels + // finalize this channel, unmute muted channels, deactivate other channels //Case 3: This channel disconnected while it was a non-primary ctrl channel (and other channels are open) - // finalize this channel, leave other channels alone //Case 4: This channel disconnected while it was a data channel (and other channels are open, including a primary) @@ -933,20 +996,23 @@ struct xioDeviceWrapper : xioDeviceWrapperBase { // describes a device for re flush(); flushRead(); - if(checkForNotActive(oldflags) || isAlwaysDataAndCtrl()) { + if (checkForNotActive(oldflags) || isAlwaysDataAndCtrl()) { // Case 5a, 5b - } else if(checkForCtrlAndData(oldflags) || !xio.others_connected(this)) { + } else if (checkForCtrlAndData(oldflags) || !xio.others_connected(this)) { // Case 1 - if(!checkForCtrlAndData(oldflags) || xio.others_connected(this)) { - rpt_exception(STAT_XIO_ASSERTION_FAILURE, "xio_dev() assertion error"); // where is this supposed to go!? + if (xio.deactivate_and_unmute_channels()) { + controller_set_muted(false); // something was unmuted + } else { + controller_set_connected(false); } - controller_set_connected(false); - } else if(checkForCtrlAndPrimary(oldflags)) { + } else if (checkForCtrlAndPrimary(oldflags)) { // Case 2 - xio.deactivate_all_channels(); - } else if(checkForCtrl(oldflags)) { + if (xio.deactivate_and_unmute_channels()) { + controller_set_muted(false); // something was unmuted + } + } else if (checkForCtrl(oldflags)) { // Case 3 - } else if(checkForData(oldflags)) { + } else if (checkForData(oldflags)) { // Case 4 xio.remove_data_from_primary(); } @@ -970,9 +1036,14 @@ xioDeviceWrapper serialUSB1Wrapper { #endif #endif // XIO_HAS_USB #if XIO_HAS_UART==1 +#if defined(XIO_UART_MUTES_WHEN_USB_CONNECTED) && (XIO_UART_MUTES_WHEN_USB_CONNECTED==1) +constexpr devflags_t _serial0ExtraFlags = DEV_IS_ALWAYS_BOTH | DEV_IS_MUTE_SECONDARY; +#else +constexpr devflags_t _serial0ExtraFlags = DEV_IS_ALWAYS_BOTH; +#endif xioDeviceWrapper serial0Wrapper { &Serial, - (DEV_CAN_READ | DEV_CAN_WRITE | DEV_IS_ALWAYS_BOTH) + (DEV_CAN_READ | DEV_CAN_WRITE | _serial0ExtraFlags) }; #endif // XIO_HAS_UART @@ -1032,9 +1103,9 @@ stat_t xio_test_assertions() * write() - write a buffer to a device */ -size_t xio_write(const char *buffer, size_t size) +size_t xio_write(const char *buffer, size_t size, bool only_to_muted /*= false*/) { - return xio.write(buffer, size); + return xio.write(buffer, size, only_to_muted); } /* @@ -1049,9 +1120,9 @@ char *xio_readline(devflags_t &flags, uint16_t &size) return xio.readline(flags, size); } -int16_t xio_writeline(const char *buffer) +int16_t xio_writeline(const char *buffer, bool only_to_muted /*= false*/) { - return xio.writeline(buffer); + return xio.writeline(buffer, only_to_muted); } bool xio_connected() diff --git a/g2core/xio.h b/g2core/xio.h index 0369af21..b235f7d1 100755 --- a/g2core/xio.h +++ b/g2core/xio.h @@ -65,17 +65,19 @@ typedef uint16_t devflags_t; // might need to bump to 32 be 16 or 32 // device capabilities flags -#define DEV_CAN_BE_CTRL (0x0001) // device can be a control channel -#define DEV_CAN_BE_DATA (0x0002) // device can be a data channel -#define DEV_IS_ALWAYS_BOTH (0x0004) // device is always a control and a data channel -#define DEV_CAN_READ (0x0010) -#define DEV_CAN_WRITE (0x0020) +#define DEV_CAN_BE_CTRL (0x0001) // device can be a control channel +#define DEV_CAN_BE_DATA (0x0002) // device can be a data channel +#define DEV_IS_ALWAYS_BOTH (0x0004) // device is always a control and a data channel +#define DEV_IS_MUTE_SECONDARY (0x0008) // device is "muted" as a non-primary device +#define DEV_CAN_READ (0x0010) +#define DEV_CAN_WRITE (0x0020) // Device state flags // channel state #define DEV_IS_CTRL (0x0001) // device is set as a control channel #define DEV_IS_DATA (0x0002) // device is set as a data channel #define DEV_IS_PRIMARY (0x0004) // device is the primary control channel +#define DEV_IS_MUTED (0x0008) // device is muted as it is currently the non-primary device // device connection state #define DEV_IS_CONNECTED (0x0020) // device is connected (e.g. USB) @@ -113,9 +115,9 @@ enum xioSPIMode { void xio_init(void); stat_t xio_test_assertions(void); -size_t xio_write(const uint8_t *buffer, size_t size); +size_t xio_write(const char *buffer, size_t size, bool only_to_muted = false); char *xio_readline(devflags_t &flags, uint16_t &size); -int16_t xio_writeline(const char *buffer); +int16_t xio_writeline(const char *buffer, bool only_to_muted = false); bool xio_connected(); stat_t xio_set_spi(nvObj_t *nv);