././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1736112705.0172837 python-samsung-mdc-1.15.0/0000755000175100002000000000000014736575101014734 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1736112699.0 python-samsung-mdc-1.15.0/LICENSE0000644000175100002000000000273314736575073015756 0ustar00runnerdockerBSD 3-Clause License Copyright (c) 2021, Victor Gavro Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 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. 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. 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. ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1736112705.0172837 python-samsung-mdc-1.15.0/PKG-INFO0000644000175100002000000011673414736575101016045 0ustar00runnerdockerMetadata-Version: 2.1 Name: python-samsung-mdc Version: 1.15.0 Summary: Samsung Multiple Display Control (MDC) protocol implementation (asyncio library + CLI interface) Home-page: http://github.com/vgavro/samsung-mdc Author: Victor Gavro Author-email: vgavro@gmail.com License: BSD-3-Clause Keywords: samsung,mdc Classifier: License :: OSI Approved :: BSD License Classifier: Intended Audience :: Developers Classifier: Intended Audience :: System Administrators Classifier: Intended Audience :: Telecommunications Industry Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 Classifier: Development Status :: 5 - Production/Stable Classifier: Operating System :: OS Independent Classifier: Topic :: Multimedia :: Video :: Display Classifier: Topic :: Home Automation Classifier: Topic :: Utilities Requires-Python: >=3.7,<4.0 Description-Content-Type: text/markdown Provides-Extra: test Provides-Extra: serial Provides-Extra: all License-File: LICENSE # Samsung-MDC This is implementation of Samsung MDC (Multiple Display Control) protocol on **python3.7+** and **asyncio** with most comprehensive CLI (command line interface). It allows you to control a variety of different sources (TV, Monitor) through the built-in RS-232C or Ethernet interface. [MDC Protocol specification - v15.0 2020-11-06](https://vgavro.github.io/samsung-mdc/MDC-Protocol.pdf) * Implemented *83* commands * Easy to extend using simple declarative API - see [samsung_mdc/commands.py](https://github.com/vgavro/samsung-mdc/blob/master/samsung_mdc/commands.py) * Detailed [CLI](#usage) help and parameters validation * Run commands async on numerous targets (using asyncio) * TCP and SERIAL mode (for RJ45 and RS232C connection types) * TCP over TLS mode ("Secured Protocol" using PIN) * [script](#script) command for advanced usage * [Python example](#python-example) Not implemented: some more commands (PRs are welcome) Also see: [Samsung MDC Unified](http://www.samsung-mcloud.com/01_Software/04_Tools/MDC/v1235/) - Reference Application (GUI, Windows) with partially implemented functionality. ## Install ``` # using pipx https://pypa.github.io/pipx/ pipx run python-samsung-mdc --help # OR global install/upgrade sudo pip3 install --upgrade python-samsung-mdc samsung-mdc --help # OR local git clone https://github.com/vgavro/samsung-mdc cd ./samsung-mdc python3 -m venv venv ./venv/bin/pip3 install -e ./ ./venv/bin/samsung-mdc --help ``` ### Windows install 1. Install Git && Git Bash: https://git-scm.com/download/win 2. Install Python 3 latest release (tested with 3.9): https://www.python.org/downloads/windows/ 3. Run "Git Bash", type in console: ``` pip3 install --upgrade python-samsung-mdc # NOTE: python "Scripts" folder is not in %PATH% in Windows by default, # so you may want to create alias for Git Bash echo alias samsung-mdc=\'python3 -m samsung_mdc\' >> ~/.bash_profile source ~/.bash_profile # test it samsung-mdc --help ``` ## Usage ``` Usage: samsung-mdc [OPTIONS] TARGET COMMAND [ARGS]... Try 'samsung-mdc --help COMMAND' for command info For multiple targets commands will be running async, so result order may differ. TARGET may be: DISPLAY_ID@IP[:PORT] (default port: 1515, example: 0@192.168.0.10:1515) FILENAME with target list (separated by newline) For serial port connection: DISPLAY_ID@PORT_NAME for Windows (example: 1@COM1) DISPLAY_ID@PORT_PATH (example: 1@/dev/ttyUSB0) We're trying to make autodetection of connection mode by port name, but you may want to use --mode option. Options: --version Show the version and exit. -v, --verbose -m, --mode [auto|tcp|serial] default: auto -p, --pin INTEGER 4-digit PIN for secured TLS connection. If PIN provided, "Secured Protocol" must be enabled on remote device. -t, --timeout FLOAT read/write/connect timeout in seconds (default: 5) (connect can be overridden with separate option) --connect-timeout FLOAT -h, --help Show this message and exit. ``` ### Commands: * [status](#status) `(POWER_STATE VOLUME MUTE_STATE INPUT_SOURCE_STATE PICTURE_ASPECT_STATE N_TIME_NF F_TIME_NF)` * [video](#video) `(CONTRAST BRIGHTNESS SHARPNESS COLOR TINT COLOR_TONE_STATE COLOR_TEMPERATURE _IGNORE)` * [rgb](#rgb) `(CONTRAST BRIGHTNESS COLOR_TONE_STATE COLOR_TEMPERATURE _IGNORE RED_GAIN GREEN_GAIN BLUE_GAIN)` * [serial_number](#serial_number) `(SERIAL_NUMBER)` * [error_status](#error_status) `(LAMP_ERROR_STATE TEMPERATURE_ERROR_STATE BRIGHTNESS_SENSOR_ERROR_STATE INPUT_SOURCE_ERROR_STATE TEMPERATURE FAN_ERROR_STATE)` * [software_version](#software_version) `(SOFTWARE_VERSION)` * [model_number](#model_number) `(MODEL_SPECIES MODEL_CODE TV_SUPPORT)` * [power](#power) `[POWER_STATE]` * [volume](#volume) `[VOLUME]` * [mute](#mute) `[MUTE_STATE]` * [input_source](#input_source) `[INPUT_SOURCE_STATE]` * [picture_aspect](#picture_aspect) `[PICTURE_ASPECT_STATE]` * [screen_mode](#screen_mode) `[SCREEN_MODE_STATE]` * [screen_size](#screen_size) `(INCHES)` * [network_configuration](#network_configuration) `[IP_ADDRESS SUBNET_MASK GATEWAY_ADDRESS DNS_SERVER_ADDRESS]` * [network_mode](#network_mode) `[NETWORK_MODE_STATE]` * [network_ap_config](#network_ap_config) `SSID PASSWORD` * [weekly_restart](#weekly_restart) `[WEEKDAY TIME]` * [magicinfo_channel](#magicinfo_channel) `CHANNEL_NUMBER` * [magicinfo_server](#magicinfo_server) `[MAGICINFO_SERVER_URL]` * [magicinfo_content_orientation](#magicinfo_content_orientation) `[ORIENTATION_MODE_STATE]` * [mdc_connection](#mdc_connection) `[MDC_CONNECTION_TYPE]` * [contrast](#contrast) `[CONTRAST]` * [brightness](#brightness) `[BRIGHTNESS]` * [sharpness](#sharpness) `[SHARPNESS]` * [color](#color) `[COLOR]` * [tint](#tint) `[TINT]` * [h_position](#h_position) `H_POSITION_MOVE_TO` * [v_position](#v_position) `V_POSITION_MOVE_TO` * [auto_power](#auto_power) `[AUTO_POWER_STATE]` * [clear_menu](#clear_menu) * [ir_state](#ir_state) `[IR_STATE]` * [rgb_contrast](#rgb_contrast) `[CONTRAST]` * [rgb_brightness](#rgb_brightness) `[BRIGHTNESS]` * [auto_adjustment_on](#auto_adjustment_on) * [color_tone](#color_tone) `[COLOR_TONE_STATE]` * [color_temperature](#color_temperature) `[HECTO_KELVIN]` * [standby](#standby) `[STANDBY_STATE]` * [auto_lamp](#auto_lamp) `[MAX_TIME MAX_LAMP_VALUE MIN_TIME MIN_LAMP_VALUE]` * [manual_lamp](#manual_lamp) `[LAMP_VALUE]` * [inverse](#inverse) `[INVERSE_STATE]` * [video_wall_mode](#video_wall_mode) `[VIDEO_WALL_MODE]` * [safety_lock](#safety_lock) `[LOCK_STATE]` * [panel_lock](#panel_lock) `[LOCK_STATE]` * [channel_change](#channel_change) `CHANGE_TO` * [volume_change](#volume_change) `CHANGE_TO` * [ticker](#ticker) `[ON_OFF START_TIME END_TIME POS_HORIZ POS_VERTI MOTION_ON_OFF MOTION_DIR MOTION_SPEED FONT_SIZE FOREGROUND_COLOR BACKGROUND_COLOR FOREGROUND_OPACITY BACKGROUND_OPACITY MESSAGE]` * [device_name](#device_name) `(DEVICE_NAME)` * [osd](#osd) `[OSD_ENABLED]` * [picture_mode](#picture_mode) `[PICTURE_MODE_STATE]` * [sound_mode](#sound_mode) `[SOUND_MODE_STATE]` * [all_keys_lock](#all_keys_lock) `[LOCK_STATE]` * [panel_on_time](#panel_on_time) `(MIN10)` * [video_wall_state](#video_wall_state) `[VIDEO_WALL_STATE]` * [video_wall_model](#video_wall_model) `[MODEL SERIAL]` * [model_name](#model_name) `(MODEL_NAME)` * [energy_saving](#energy_saving) `[ENERGY_SAVING_STATE]` * [reset](#reset) `RESET_TARGET` * [osd_type](#osd_type) `[OSD_TYPE OSD_ENABLED]` * [timer_13](#timer_13) `TIMER_ID [ON_TIME ON_ENABLED OFF_TIME OFF_ENABLED REPEAT MANUAL_WEEKDAY VOLUME INPUT_SOURCE_STATE HOLIDAY_APPLY]` * [timer_15](#timer_15) `TIMER_ID [ON_TIME ON_ENABLED OFF_TIME OFF_ENABLED ON_REPEAT ON_MANUAL_WEEKDAY OFF_REPEAT OFF_MANUAL_WEEKDAY VOLUME INPUT_SOURCE_STATE HOLIDAY_APPLY]` * [clock_m](#clock_m) `[DATETIME]` * [holiday_set](#holiday_set) `HOLIDAY_MANAGE START_MONTH START_DAY END_MONTH END_DAY` * [holiday_get](#holiday_get) `[INDEX]` * [virtual_remote](#virtual_remote) `KEY_CODE` * [network_standby](#network_standby) `[NETWORK_STANDBY_STATE]` * [dst](#dst) `[DST_STATE START_MONTH START_WEEK START_WEEKDAY START_TIME END_MONTH END_WEEK END_WEEKDAY END_TIME OFFSET]` * [auto_id_setting](#auto_id_setting) `[AUTO_ID_SETTING_STATE]` * [display_id](#display_id) `DISPLAY_ID_STATE` * [clock_s](#clock_s) `[DATETIME]` * [launcher_play_via](#launcher_play_via) `[PLAY_VIA_MODE]` * [launcher_url_address](#launcher_url_address) `[URL_ADDRESS]` * [osd_menu_orientation](#osd_menu_orientation) `[ORIENTATION_MODE_STATE]` * [osd_source_content_orientation](#osd_source_content_orientation) `[ORIENTATION_MODE_STATE]` * [osd_aspect_ratio](#osd_aspect_ratio) `[ASPECT_RATIO_STATE]` * [osd_pip_orientation](#osd_pip_orientation) `[ORIENTATION_MODE_STATE]` * [osd_menu_size](#osd_menu_size) `[MENU_SIZE_STATE]` * [auto_source_switch](#auto_source_switch) `[AUTO_SOURCE_SWITCH_STATE]` * [auto_source](#auto_source) `[PRIMARY_SOURCE_RECOVERY PRIMARY_SOURCE SECONDARY_SOURCE]` * [panel](#panel) `[PANEL_STATE]` * [screen_mute](#screen_mute) `[SCREEN_MUTE_STATUS]` * [script](#script) `[OPTIONS] SCRIPT_FILE` * [raw](#raw) `[OPTIONS] COMMAND [DATA]` #### status ``` Usage: samsung-mdc [OPTIONS] TARGET status Get the device various state like power, volume, sound mute, input source, picture aspect ratio. Note: For no audio models volume and mute returns 0xFF (255). N_TIME_NF, F_TIME_NF: OnTime/OffTime ON/OFF value (old type timer, now it's always 0x00). Data: POWER_STATE OFF | ON | REBOOT VOLUME int (0-100) MUTE_STATE OFF | ON | NONE INPUT_SOURCE_STATE NONE | S_VIDEO | COMPONENT | AV | AV2 | SCART1 | DVI | PC | BNC | DVI_VIDEO | MAGIC_INFO | HDMI1 | HDMI1_PC | HDMI2 | HDMI2_PC | DISPLAY_PORT_1 | DISPLAY_PORT_2 | DISPLAY_PORT_3 | RF_TV | HDMI3 | HDMI3_PC | HDMI4 | HDMI4_PC | TV_DTV | PLUG_IN_MODE | HD_BASE_T | OCM | MEDIA_MAGIC_INFO_S | WIDI_SCREEN_MIRRORING | INTERNAL_USB | URL_LAUNCHER | IWB | WEB_BROWSER | REMOTE_WORKSPACE PICTURE_ASPECT_STATE PC_16_9 | PC_4_3 | PC_ORIGINAL_RATIO | PC_21_9 | PC_CUSTOM | VIDEO_AUTO_WIDE | VIDEO_16_9 | VIDEO_ZOOM | VIDEO_ZOOM_1 | VIDEO_ZOOM_2 | VIDEO_SCREEN_FIT | VIDEO_4_3 | VIDEO_WIDE_FIT | VIDEO_CUSTOM | VIDEO_SMART_VIEW_1 | VIDEO_SMART_VIEW_2 | VIDEO_WIDE_ZOOM | VIDEO_21_9 N_TIME_NF int F_TIME_NF int ``` #### video ``` Usage: samsung-mdc [OPTIONS] TARGET video Data: CONTRAST int (0-100) BRIGHTNESS int (0-100) SHARPNESS int (0-100) COLOR int (0-100) TINT int (0-100) COLOR_TONE_STATE COOL_2 | COOL_1 | NORMAL | WARM_1 | WARM_2 | OFF COLOR_TEMPERATURE int _IGNORE int (0-0) ``` #### rgb ``` Usage: samsung-mdc [OPTIONS] TARGET rgb Data: CONTRAST int (0-100) BRIGHTNESS int (0-100) COLOR_TONE_STATE COOL_2 | COOL_1 | NORMAL | WARM_1 | WARM_2 | OFF COLOR_TEMPERATURE int _IGNORE int (0-0) RED_GAIN int GREEN_GAIN int BLUE_GAIN int ``` #### serial_number ``` Usage: samsung-mdc [OPTIONS] TARGET serial_number Data: SERIAL_NUMBER str ``` #### error_status ``` Usage: samsung-mdc [OPTIONS] TARGET error_status Data: LAMP_ERROR_STATE NORMAL | ERROR TEMPERATURE_ERROR_STATE NORMAL | ERROR BRIGHTNESS_SENSOR_ERROR_STATE NONE | ERROR | NORMAL INPUT_SOURCE_ERROR_STATE NORMAL | ERROR | INVALID TEMPERATURE int FAN_ERROR_STATE NORMAL | ERROR | NONE ``` #### software_version ``` Usage: samsung-mdc [OPTIONS] TARGET software_version Data: SOFTWARE_VERSION str ``` #### model_number ``` Usage: samsung-mdc [OPTIONS] TARGET model_number Data: MODEL_SPECIES PDP | LCD | DLP | LED | CRT | OLED MODEL_CODE int TV_SUPPORT SUPPORTED | NOT_SUPPORTED ``` #### power ``` Usage: samsung-mdc [OPTIONS] TARGET power [POWER_STATE] Data: POWER_STATE OFF | ON | REBOOT ``` #### volume ``` Usage: samsung-mdc [OPTIONS] TARGET volume [VOLUME] Data: VOLUME int (0-100) ``` #### mute ``` Usage: samsung-mdc [OPTIONS] TARGET mute [MUTE_STATE] Data: MUTE_STATE OFF | ON | NONE ``` #### input_source ``` Usage: samsung-mdc [OPTIONS] TARGET input_source [INPUT_SOURCE_STATE] Get/Set the device source which is shown on the screen. DVI_VIDEO, HDMI1_PC, HDMI2_PC, HDMI3_PC, HDMI4_PC: get only. URL_LAUNCHER, MAGIC_INFO, TV or some ports require support by model. On TIMER functions, Do not use WIDI_SCREEN_MIRRORING. Data: INPUT_SOURCE_STATE NONE | S_VIDEO | COMPONENT | AV | AV2 | SCART1 | DVI | PC | BNC | DVI_VIDEO | MAGIC_INFO | HDMI1 | HDMI1_PC | HDMI2 | HDMI2_PC | DISPLAY_PORT_1 | DISPLAY_PORT_2 | DISPLAY_PORT_3 | RF_TV | HDMI3 | HDMI3_PC | HDMI4 | HDMI4_PC | TV_DTV | PLUG_IN_MODE | HD_BASE_T | OCM | MEDIA_MAGIC_INFO_S | WIDI_SCREEN_MIRRORING | INTERNAL_USB | URL_LAUNCHER | IWB | WEB_BROWSER | REMOTE_WORKSPACE ``` #### picture_aspect ``` Usage: samsung-mdc [OPTIONS] TARGET picture_aspect [PICTURE_ASPECT_STATE] Get/Set the device picture size (aspect ratio). Working Condition: Will not work with VIDEO_WALL_STATE is ON. Note: Some of the image sizes are not supported depending on input signals. Data: PICTURE_ASPECT_STATE PC_16_9 | PC_4_3 | PC_ORIGINAL_RATIO | PC_21_9 | PC_CUSTOM | VIDEO_AUTO_WIDE | VIDEO_16_9 | VIDEO_ZOOM | VIDEO_ZOOM_1 | VIDEO_ZOOM_2 | VIDEO_SCREEN_FIT | VIDEO_4_3 | VIDEO_WIDE_FIT | VIDEO_CUSTOM | VIDEO_SMART_VIEW_1 | VIDEO_SMART_VIEW_2 | VIDEO_WIDE_ZOOM | VIDEO_21_9 ``` #### screen_mode ``` Usage: samsung-mdc [OPTIONS] TARGET screen_mode [SCREEN_MODE_STATE] Data: SCREEN_MODE_STATE MODE_16_9 | MODE_ZOOM | MODE_4_3 | MODE_WIDE_ZOOM ``` #### screen_size ``` Usage: samsung-mdc [OPTIONS] TARGET screen_size Data: INCHES int (0-255) ``` #### network_configuration ``` Usage: samsung-mdc [OPTIONS] TARGET network_configuration [IP_ADDRESS SUBNET_MASK GATEWAY_ADDRESS DNS_SERVER_ADDRESS] Data: IP_ADDRESS IP address SUBNET_MASK IP address GATEWAY_ADDRESS IP address DNS_SERVER_ADDRESS IP address ``` #### network_mode ``` Usage: samsung-mdc [OPTIONS] TARGET network_mode [NETWORK_MODE_STATE] Data: NETWORK_MODE_STATE DYNAMIC | STATIC ``` #### network_ap_config ``` Usage: samsung-mdc [OPTIONS] TARGET network_ap_config SSID PASSWORD Add new SSID info to device connection history with its password. Note: device may change network and response may not be received. Data: SSID str PASSWORD str ``` #### weekly_restart ``` Usage: samsung-mdc [OPTIONS] TARGET weekly_restart [WEEKDAY TIME] Data: WEEKDAY list(,) SUN | SAT | FRI | THU | WED | TUE | MON TIME time (format: %H:%M) ``` #### magicinfo_channel ``` Usage: samsung-mdc [OPTIONS] TARGET magicinfo_channel CHANNEL_NUMBER Set MagicInfo Channel by Direct Channel Number which is used by MagicInfo S Player. Data: CHANNEL_NUMBER int ``` #### magicinfo_server ``` Usage: samsung-mdc [OPTIONS] TARGET magicinfo_server [MAGICINFO_SERVER_URL] MagicInfo Server URL. Example: "http://example.com:80" Data: MAGICINFO_SERVER_URL str ``` #### magicinfo_content_orientation ``` Usage: samsung-mdc [OPTIONS] TARGET magicinfo_content_orientation [ORIENTATION_MODE_STATE] Data: ORIENTATION_MODE_STATE LANDSCAPE_0 | PORTRAIT_270 | LANDSCAPE_180 | PORTRAIT_90 ``` #### mdc_connection ``` Usage: samsung-mdc [OPTIONS] TARGET mdc_connection [MDC_CONNECTION_TYPE] Note: Depends on the product specification, if it is set as RJ45 then serial MDC will not work. Data: MDC_CONNECTION_TYPE RS232C | RJ45 ``` #### contrast ``` Usage: samsung-mdc [OPTIONS] TARGET contrast [CONTRAST] Data: CONTRAST int (0-100) ``` #### brightness ``` Usage: samsung-mdc [OPTIONS] TARGET brightness [BRIGHTNESS] Data: BRIGHTNESS int (0-100) ``` #### sharpness ``` Usage: samsung-mdc [OPTIONS] TARGET sharpness [SHARPNESS] Data: SHARPNESS int (0-100) ``` #### color ``` Usage: samsung-mdc [OPTIONS] TARGET color [COLOR] Data: COLOR int (0-100) ``` #### tint ``` Usage: samsung-mdc [OPTIONS] TARGET tint [TINT] Control the device tint. Adjust the ratio of green to red tint level. Red: TINT value, Green: ( 100 - TINT ) value. Note: Tint could only be set in 50 Steps (0, 2, 4, 6... 100). Data: TINT int (0-100) ``` #### h_position ``` Usage: samsung-mdc [OPTIONS] TARGET h_position H_POSITION_MOVE_TO Data: H_POSITION_MOVE_TO LEFT | RIGHT ``` #### v_position ``` Usage: samsung-mdc [OPTIONS] TARGET v_position V_POSITION_MOVE_TO Data: V_POSITION_MOVE_TO UP | DOWN ``` #### auto_power ``` Usage: samsung-mdc [OPTIONS] TARGET auto_power [AUTO_POWER_STATE] Data: AUTO_POWER_STATE OFF | ON ``` #### clear_menu ``` Usage: samsung-mdc [OPTIONS] TARGET clear_menu ``` #### ir_state ``` Usage: samsung-mdc [OPTIONS] TARGET ir_state [IR_STATE] Enables/disables IR (Infrared) receiving function (Remote Control). Working Condition: * Can operate regardless of whether power is ON/OFF. (If DPMS Situation in LFD, it operate Remocon regardless of set value). Data: IR_STATE DISABLED | ENABLED ``` #### rgb_contrast ``` Usage: samsung-mdc [OPTIONS] TARGET rgb_contrast [CONTRAST] Data: CONTRAST int (0-100) ``` #### rgb_brightness ``` Usage: samsung-mdc [OPTIONS] TARGET rgb_brightness [BRIGHTNESS] Data: BRIGHTNESS int (0-100) ``` #### auto_adjustment_on ``` Usage: samsung-mdc [OPTIONS] TARGET auto_adjustment_on ``` #### color_tone ``` Usage: samsung-mdc [OPTIONS] TARGET color_tone [COLOR_TONE_STATE] Data: COLOR_TONE_STATE COOL_2 | COOL_1 | NORMAL | WARM_1 | WARM_2 | OFF ``` #### color_temperature ``` Usage: samsung-mdc [OPTIONS] TARGET color_temperature [HECTO_KELVIN] Color temperature function. Unit is hectoKelvin (hK) (x*100 Kelvin) (example: 28 = 2800K). Supported values - 28, 30, 35, 40... 160. For older models: 0-10=(x*100K + 5000K), 253=2800K, 254=3000K, 255=4000K Data: HECTO_KELVIN int ``` #### standby ``` Usage: samsung-mdc [OPTIONS] TARGET standby [STANDBY_STATE] Data: STANDBY_STATE OFF | ON | AUTO ``` #### auto_lamp ``` Usage: samsung-mdc [OPTIONS] TARGET auto_lamp [MAX_TIME MAX_LAMP_VALUE MIN_TIME MIN_LAMP_VALUE] Auto Lamp function (backlight). Note: When Manual Lamp Control is on, Auto Lamp Control will automatically turn off. Data: MAX_TIME time (format: %H:%M) MAX_LAMP_VALUE int (0-100) MIN_TIME time (format: %H:%M) MIN_LAMP_VALUE int (0-100) ``` #### manual_lamp ``` Usage: samsung-mdc [OPTIONS] TARGET manual_lamp [LAMP_VALUE] Manual Lamp function (backlight). Note: When Auto Lamp Control is on, Manual Lamp Control will automatically turn off. Data: LAMP_VALUE int (0-100) ``` #### inverse ``` Usage: samsung-mdc [OPTIONS] TARGET inverse [INVERSE_STATE] Data: INVERSE_STATE OFF | ON ``` #### video_wall_mode ``` Usage: samsung-mdc [OPTIONS] TARGET video_wall_mode [VIDEO_WALL_MODE] Get/Set the device in aspect ratio of the video wall. FULL: stretch input source to fill display NATURAL: Keep aspect ratio of input source; do not fill display. Note: Needs VIDEO_WALL_STATE to be ON. Data: VIDEO_WALL_MODE NATURAL | FULL ``` #### safety_lock ``` Usage: samsung-mdc [OPTIONS] TARGET safety_lock [LOCK_STATE] Data: LOCK_STATE OFF | ON ``` #### panel_lock ``` Usage: samsung-mdc [OPTIONS] TARGET panel_lock [LOCK_STATE] Data: LOCK_STATE OFF | ON ``` #### channel_change ``` Usage: samsung-mdc [OPTIONS] TARGET channel_change CHANGE_TO Data: CHANGE_TO UP | DOWN ``` #### volume_change ``` Usage: samsung-mdc [OPTIONS] TARGET volume_change CHANGE_TO Data: CHANGE_TO UP | DOWN ``` #### ticker ``` Usage: samsung-mdc [OPTIONS] TARGET ticker [ON_OFF START_TIME END_TIME POS_HORIZ POS_VERTI MOTION_ON_OFF MOTION_DIR MOTION_SPEED FONT_SIZE FOREGROUND_COLOR BACKGROUND_COLOR FOREGROUND_OPACITY BACKGROUND_OPACITY MESSAGE] Get/Set the device ticker. (Show text message overlay on the screen) Note: POS_HORIZ or POS_VERT are NONE in GET response if unsupported by the display. Data: ON_OFF bool START_TIME time (format: %H:%M) END_TIME time (format: %H:%M) POS_HORIZ CENTER | LEFT | RIGHT | NONE POS_VERTI MIDDLE | TOP | BOTTOM | NONE MOTION_ON_OFF bool MOTION_DIR LEFT | RIGHT | UP | DOWN MOTION_SPEED NORMAL | SLOW | FAST FONT_SIZE STANDARD | SMALL | LARGE FOREGROUND_COLOR BLACK | WHITE | RED | GREEN | BLUE | YELLOW | MAGENTA | CYAN BACKGROUND_COLOR BLACK | WHITE | RED | GREEN | BLUE | YELLOW | MAGENTA | CYAN FOREGROUND_OPACITY FLASHING | FLASH_ALL | OFF BACKGROUND_OPACITY SOLID | TRANSPARENT | TRANSLUCENT | UNKNOWN MESSAGE str ``` #### device_name ``` Usage: samsung-mdc [OPTIONS] TARGET device_name It reads the device name which user set up in network. Shows the information about entered device name. Data: DEVICE_NAME str ``` #### osd ``` Usage: samsung-mdc [OPTIONS] TARGET osd [OSD_ENABLED] Turns OSD (On-screen display) on/off. Data: OSD_ENABLED bool ``` #### picture_mode ``` Usage: samsung-mdc [OPTIONS] TARGET picture_mode [PICTURE_MODE_STATE] Data: PICTURE_MODE_STATE DYNAMIC | STANDARD | MOVIE | CUSTOM_TV | NATURAL | CALIBRATION_TV | ENTERTAIN | INTERNET | TEXT | CUSTOM | ADVERTISEMENT | INFORMATION | CALIBRATION | SHOP_MALL_VIDEO | SHOP_MALL_TEXT | OFFICE_SCHOOL_VIDEO | OFFICE_SCHOOL_TEXT | TERMINAL_STATION_VIDEO | TERMINAL_STATION_TEXT | VIDEO_WALL_VIDEO | VIDEO_WALL_TEXT | HDR_PLUS | OFF | RESERVED_OTHER ``` #### sound_mode ``` Usage: samsung-mdc [OPTIONS] TARGET sound_mode [SOUND_MODE_STATE] Data: SOUND_MODE_STATE STANDARD | MUSIC | MOVIE | SPEECH | CUSTOM | AMPLIFY | OPTIMIZED ``` #### all_keys_lock ``` Usage: samsung-mdc [OPTIONS] TARGET all_keys_lock [LOCK_STATE] Turns both REMOCON and Panel Key Lock function on/off. Note: Can operate regardless of whether power is on/off. Data: LOCK_STATE OFF | ON ``` #### panel_on_time ``` Usage: samsung-mdc [OPTIONS] TARGET panel_on_time Get the device panel on total time. Return value increased every 10 mins. To get hours use "MIN10 / 6". Data: MIN10 int ``` #### video_wall_state ``` Usage: samsung-mdc [OPTIONS] TARGET video_wall_state [VIDEO_WALL_STATE] Get/Set the device in video wall state. This will split the primary input source into smaller N number of squares and display them instead. Note: The device needs to be capable of this operation. Usually a primary high resolution source signal is daisy chained to lower resolution displays in a video wall using HDMI/DP. Data: VIDEO_WALL_STATE OFF | ON ``` #### video_wall_model ``` Usage: samsung-mdc [OPTIONS] TARGET video_wall_model [MODEL SERIAL] Get/Set video wall model. MODEL: Size of the wall in (x, y) coordinates; ie. "2,2" or "4,1" SERIAL: Serial number - position of the display in the video wall, counting from the first display. Note: Needs VIDEO_WALL_STATE to be ON. Data: MODEL Video Wall model (format: X,Y eg. 4,5) SERIAL int (1-255) ``` #### model_name ``` Usage: samsung-mdc [OPTIONS] TARGET model_name Data: MODEL_NAME str ``` #### energy_saving ``` Usage: samsung-mdc [OPTIONS] TARGET energy_saving [ENERGY_SAVING_STATE] Data: ENERGY_SAVING_STATE OFF | LOW | MEDIUM | HIGH | PICTURE_OFF ``` #### reset ``` Usage: samsung-mdc [OPTIONS] TARGET reset RESET_TARGET Data: RESET_TARGET PICTURE | SOUND | SETUP | ALL | SCREEN_DISPLAY ``` #### osd_type ``` Usage: samsung-mdc [OPTIONS] TARGET osd_type [OSD_TYPE OSD_ENABLED] Turns OSD (On-screen display) specific message types on/off. Data: OSD_TYPE SOURCE | NOT_OPTIMUM_MODE | NO_SIGNAL | MDC | SCHEDULE_CHANNEL OSD_ENABLED bool ``` #### timer_13 ``` Usage: samsung-mdc [OPTIONS] TARGET timer_13 TIMER_ID [ON_TIME ON_ENABLED OFF_TIME OFF_ENABLED REPEAT MANUAL_WEEKDAY VOLUME INPUT_SOURCE_STATE HOLIDAY_APPLY] Integrated timer function (13 data-length version). Note: This depends on product and will not work on newer versions. Data: TIMER_ID int (1-7) ON_TIME time (format: %H:%M) ON_ENABLED bool OFF_TIME time (format: %H:%M) OFF_ENABLED bool REPEAT ONCE | EVERYDAY | MON_FRI | MON_SAT | SAT_SUN | MANUAL_WEEKDAY MANUAL_WEEKDAY list(,) SUN | MON | TUE | WED | THU | FRI | SAT VOLUME int (0-100) INPUT_SOURCE_STATE NONE | S_VIDEO | COMPONENT | AV | AV2 | SCART1 | DVI | PC | BNC | DVI_VIDEO | MAGIC_INFO | HDMI1 | HDMI1_PC | HDMI2 | HDMI2_PC | DISPLAY_PORT_1 | DISPLAY_PORT_2 | DISPLAY_PORT_3 | RF_TV | HDMI3 | HDMI3_PC | HDMI4 | HDMI4_PC | TV_DTV | PLUG_IN_MODE | HD_BASE_T | OCM | MEDIA_MAGIC_INFO_S | WIDI_SCREEN_MIRRORING | INTERNAL_USB | URL_LAUNCHER | IWB | WEB_BROWSER | REMOTE_WORKSPACE HOLIDAY_APPLY DONT_APPLY_BOTH | APPLY_BOTH | ON_TIMER_ONLY_APPLY | OFF_TIMER_ONLY_APPLY ``` #### timer_15 ``` Usage: samsung-mdc [OPTIONS] TARGET timer_15 TIMER_ID [ON_TIME ON_ENABLED OFF_TIME OFF_ENABLED ON_REPEAT ON_MANUAL_WEEKDAY OFF_REPEAT OFF_MANUAL_WEEKDAY VOLUME INPUT_SOURCE_STATE HOLIDAY_APPLY] Integrated timer function (15 data-length version). Note: This depends on product and will not work on older versions. ON_TIME/OFF_TIME: turn ON/OFF display at specific time of day ON_ACTIVE/OFF_ACTIVE: if timer is not active, values are ignored, so there may be only OFF timer, ON timer, or both. REPEAT: On which day timer is enabled (combined with HOLIDAY_APPLY and MANUAL_WEEKDAY) Data: TIMER_ID int (1-7) ON_TIME time (format: %H:%M) ON_ENABLED bool OFF_TIME time (format: %H:%M) OFF_ENABLED bool ON_REPEAT ONCE | EVERYDAY | MON_FRI | MON_SAT | SAT_SUN | MANUAL_WEEKDAY ON_MANUAL_WEEKDAY list(,) SUN | MON | TUE | WED | THU | FRI | SAT OFF_REPEAT ONCE | EVERYDAY | MON_FRI | MON_SAT | SAT_SUN | MANUAL_WEEKDAY OFF_MANUAL_WEEKDAY list(,) SUN | MON | TUE | WED | THU | FRI | SAT VOLUME int (0-100) INPUT_SOURCE_STATE NONE | S_VIDEO | COMPONENT | AV | AV2 | SCART1 | DVI | PC | BNC | DVI_VIDEO | MAGIC_INFO | HDMI1 | HDMI1_PC | HDMI2 | HDMI2_PC | DISPLAY_PORT_1 | DISPLAY_PORT_2 | DISPLAY_PORT_3 | RF_TV | HDMI3 | HDMI3_PC | HDMI4 | HDMI4_PC | TV_DTV | PLUG_IN_MODE | HD_BASE_T | OCM | MEDIA_MAGIC_INFO_S | WIDI_SCREEN_MIRRORING | INTERNAL_USB | URL_LAUNCHER | IWB | WEB_BROWSER | REMOTE_WORKSPACE HOLIDAY_APPLY DONT_APPLY_BOTH | APPLY_BOTH | ON_TIMER_ONLY_APPLY | OFF_TIMER_ONLY_APPLY ``` #### clock_m ``` Usage: samsung-mdc [OPTIONS] TARGET clock_m [DATETIME] Current time function (minute precision). Note: This is for models developed until 2013. For newer models see CLOCK_S function (seconds precision). Data: DATETIME datetime (format: %Y-%m-%dT%H:%M / %Y-%m-%d %H:%M) ``` #### holiday_set ``` Usage: samsung-mdc [OPTIONS] TARGET holiday_set HOLIDAY_MANAGE START_MONTH START_DAY END_MONTH END_DAY Add/Delete the device holiday schedule with the holiday schedule itself start month/day and end month/day. Note: On DELETE_ALL all parameters should be 0x00. Data: HOLIDAY_MANAGE ADD | DELETE | DELETE_ALL START_MONTH int (0-12) START_DAY int (0-31) END_MONTH int (0-12) END_DAY int (0-31) ``` #### holiday_get ``` Usage: samsung-mdc [OPTIONS] TARGET holiday_get [INDEX] Get the device holiday schedule. If INDEX is not specified, returns total number of Holiday Information. Data: INDEX int Response extra: START_MONTH int START_DAY int END_MONTH int END_DAY int ``` #### virtual_remote ``` Usage: samsung-mdc [OPTIONS] TARGET virtual_remote KEY_CODE This function support that MDC command can work same as remote control. Note: In a certain model, 0x79 CONTENT key works as HOME and 0x1F DISPLAY key works as INFO. Data: KEY_CODE KEY_SOURCE | KEY_POWER | KEY_1 | KEY_2 | KEY_3 | KEY_VOLUME_UP | KEY_4 | KEY_5 | KEY_6 | KEY_VOLUME_DOWN | KEY_7 | KEY_8 | KEY_9 | KEY_MUTE | KEY_CHANNEL_DOWN | KEY_0 | KEY_CHANNEL_UP | KEY_GREEN | KEY_YELLOW | KEY_CYAN | KEY_MENU | KEY_DISPLAY | KEY_DIGIT | KEY_PIP_TV_VIDEO | KEY_EXIT | KEY_MAGICINFO | KEY_REW | KEY_STOP | KEY_PLAY | KEY_FF | KEY_PAUSE | KEY_TOOLS | KEY_RETURN | KEY_MAGICINFO_LITE | KEY_CURSOR_UP | KEY_CURSOR_DOWN | KEY_CURSOR_RIGHT | KEY_CURSOR_LEFT | KEY_ENTER | KEY_RED | KEY_LOCK | KEY_CONTENT | DISCRET_POWER_OFF | KEY_3D ``` #### network_standby ``` Usage: samsung-mdc [OPTIONS] TARGET network_standby [NETWORK_STANDBY_STATE] Data: NETWORK_STANDBY_STATE OFF | ON ``` #### dst ``` Usage: samsung-mdc [OPTIONS] TARGET dst [DST_STATE START_MONTH START_WEEK START_WEEKDAY START_TIME END_MONTH END_WEEK END_WEEKDAY END_TIME OFFSET] Data: DST_STATE OFF | AUTO | MANUAL START_MONTH JAN | FEB | MAR | APR | MAY | JUN | JUL | AUG | SEP | OCT | NOV | DEC START_WEEK WEEK_1 | WEEK_2 | WEEK_3 | WEEK_4 | WEEK_LAST START_WEEKDAY SUN | MON | TUE | WED | THU | FRI | SAT START_TIME time (format: %H:%M) END_MONTH JAN | FEB | MAR | APR | MAY | JUN | JUL | AUG | SEP | OCT | NOV | DEC END_WEEK WEEK_1 | WEEK_2 | WEEK_3 | WEEK_4 | WEEK_LAST END_WEEKDAY SUN | MON | TUE | WED | THU | FRI | SAT END_TIME time (format: %H:%M) OFFSET PLUS_1_00 | PLUS_2_00 Response extra: TUNER_SUPPORT bool ``` #### auto_id_setting ``` Usage: samsung-mdc [OPTIONS] TARGET auto_id_setting [AUTO_ID_SETTING_STATE] Data: AUTO_ID_SETTING_STATE START | END ``` #### display_id ``` Usage: samsung-mdc [OPTIONS] TARGET display_id DISPLAY_ID_STATE Data: DISPLAY_ID_STATE OFF | ON ``` #### clock_s ``` Usage: samsung-mdc [OPTIONS] TARGET clock_s [DATETIME] Current time function (second precision). Note: This is for models developed after 2013. For older models see CLOCK_M function (minute precision). Data: DATETIME datetime (format: %Y-%m-%dT%H:%M:%S / %Y-%m-%d %H:%M:%S) ``` #### launcher_play_via ``` Usage: samsung-mdc [OPTIONS] TARGET launcher_play_via [PLAY_VIA_MODE] Data: PLAY_VIA_MODE MAGIC_INFO | URL_LAUNCHER | MAGIC_IWB ``` #### launcher_url_address ``` Usage: samsung-mdc [OPTIONS] TARGET launcher_url_address [URL_ADDRESS] Data: URL_ADDRESS str ``` #### osd_menu_orientation ``` Usage: samsung-mdc [OPTIONS] TARGET osd_menu_orientation [ORIENTATION_MODE_STATE] Data: ORIENTATION_MODE_STATE LANDSCAPE_0 | PORTRAIT_270 | LANDSCAPE_180 | PORTRAIT_90 ``` #### osd_source_content_orientation ``` Usage: samsung-mdc [OPTIONS] TARGET osd_source_content_orientation [ORIENTATION_MODE_STATE] Data: ORIENTATION_MODE_STATE LANDSCAPE_0 | PORTRAIT_270 | LANDSCAPE_180 | PORTRAIT_90 ``` #### osd_aspect_ratio ``` Usage: samsung-mdc [OPTIONS] TARGET osd_aspect_ratio [ASPECT_RATIO_STATE] Get/Set the device aspect ratio under portrait mode which set the rotated screen to be full or original. Data: ASPECT_RATIO_STATE FULL_SCREEN | ORIGINAL ``` #### osd_pip_orientation ``` Usage: samsung-mdc [OPTIONS] TARGET osd_pip_orientation [ORIENTATION_MODE_STATE] Data: ORIENTATION_MODE_STATE LANDSCAPE_0 | PORTRAIT_270 | LANDSCAPE_180 | PORTRAIT_90 ``` #### osd_menu_size ``` Usage: samsung-mdc [OPTIONS] TARGET osd_menu_size [MENU_SIZE_STATE] Data: MENU_SIZE_STATE ORIGINAL | MEDIUM | SMALL ``` #### auto_source_switch ``` Usage: samsung-mdc [OPTIONS] TARGET auto_source_switch [AUTO_SOURCE_SWITCH_STATE] Data: AUTO_SOURCE_SWITCH_STATE OFF | ON ``` #### auto_source ``` Usage: samsung-mdc [OPTIONS] TARGET auto_source [PRIMARY_SOURCE_RECOVERY PRIMARY_SOURCE SECONDARY_SOURCE] Data: PRIMARY_SOURCE_RECOVERY OFF | ON PRIMARY_SOURCE NONE | S_VIDEO | COMPONENT | AV | AV2 | SCART1 | DVI | PC | BNC | DVI_VIDEO | MAGIC_INFO | HDMI1 | HDMI1_PC | HDMI2 | HDMI2_PC | DISPLAY_PORT_1 | DISPLAY_PORT_2 | DISPLAY_PORT_3 | RF_TV | HDMI3 | HDMI3_PC | HDMI4 | HDMI4_PC | TV_DTV | PLUG_IN_MODE | HD_BASE_T | OCM | MEDIA_MAGIC_INFO_S | WIDI_SCREEN_MIRRORING | INTERNAL_USB | URL_LAUNCHER | IWB | WEB_BROWSER | REMOTE_WORKSPACE SECONDARY_SOURCE NONE | S_VIDEO | COMPONENT | AV | AV2 | SCART1 | DVI | PC | BNC | DVI_VIDEO | MAGIC_INFO | HDMI1 | HDMI1_PC | HDMI2 | HDMI2_PC | DISPLAY_PORT_1 | DISPLAY_PORT_2 | DISPLAY_PORT_3 | RF_TV | HDMI3 | HDMI3_PC | HDMI4 | HDMI4_PC | TV_DTV | PLUG_IN_MODE | HD_BASE_T | OCM | MEDIA_MAGIC_INFO_S | WIDI_SCREEN_MIRRORING | INTERNAL_USB | URL_LAUNCHER | IWB | WEB_BROWSER | REMOTE_WORKSPACE ``` #### panel ``` Usage: samsung-mdc [OPTIONS] TARGET panel [PANEL_STATE] Data: PANEL_STATE ON | OFF ``` #### screen_mute ``` Usage: samsung-mdc [OPTIONS] TARGET screen_mute [SCREEN_MUTE_STATUS] Data: SCREEN_MUTE_STATUS ON | OFF ``` #### script ``` Usage: samsung-mdc [OPTIONS] TARGET script [OPTIONS] SCRIPT_FILE Script file with commands to execute. Commands for multiple targets will be running async, but commands order is preserved for device (and is running on same connection), exit on first fail unless retry options provided. You may use jinja2 templating engine to {% include "other_script" %} or {{ VAR_KEY }} rendering in combination with --var VAR_KEY VAR_VALUE options. It's highly recommended to use sleep option for virtual_remote! Additional commands: sleep SECONDS (FLOAT, --sleep option for this command is ignored) disconnect Format: command1 [ARGS]... command2 [ARGS]... Example: samsung-mdc ./targets.txt script -s 3 -r 1 -v KEY enter ./commands.txt # commands.txt content power on sleep 5 clear_menu virtual_remote key_menu virtual_remote key_down virtual_remote {{ KEY }} clear_menu Arguments: script_file Text file with commands, separated by newline. Options: -s, --sleep FLOAT Pause between commands (seconds) --retry-command INTEGER Retry command if failed (count) --retry-command-sleep FLOAT Sleep before command retry (seconds) -r, --retry-script INTEGER Retry script if failed (count) --retry-script-sleep FLOAT Sleep before script retry (seconds) --ignore-nak Ignore negative acknowledgement errors -v, --var NAME VALUE Variable "{{ NAME }}" in script will be replaced by VALUE --help Show this message and exit. ``` #### raw ``` Usage: samsung-mdc [OPTIONS] TARGET raw [OPTIONS] COMMAND [DATA] Helper command to send raw data for test purposes. Arguments: command Command and (optionally) subcommand (example: a1 or a1:b2) data Data payload if any (example: a1:b2) ``` ## Troubleshooting ### Finding DISPLAY ID On most devices it's usually `0` or `1`. Some devices may use `255` (0xFF) or `254` (0xFE) as all/any display, but behavior in such cases for more than 1 display is undefined. Display id can be found using remote control: `Home` -> `ID Settings`. ### NAKError If you receive NAK errors on some commands, you may try to: * Ensure that device is powered on and completely loaded * Switch to input source HDMI1 * Reboot device * Reset all settings * Disable MagicINFO * Factory reset (using "Service Menu") ## Python example ```python3 import asyncio from samsung_mdc import MDC async def main(ip, display_id): async with MDC(ip, verbose=True) as mdc: # First argument of command is always display_id status = await mdc.status(display_id) print(status) # Result is always tuple if status[0] != MDC.power.POWER_STATE.ON: # Command arguments are always Sequence (tuple, list) await mdc.power(display_id, [MDC.power.POWER_STATE.ON]) await mdc.close() # Force reconnect on next command await asyncio.sleep(15) await mdc.display_id(display_id, [MDC.display_id.DISPLAY_ID_STATE.ON]) # You may also use names or values instead of enums await mdc.display_id(display_id, ['ON']) # same await mdc.display_id(display_id, [1]) # same # If you see "Connected" and timeout error, try other display_id (0, 1) asyncio.run(main('192.168.0.10', 1)) ``` ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1736112699.0 python-samsung-mdc-1.15.0/README.md0000644000175100002000000011477314736575073016240 0ustar00runnerdocker# Samsung-MDC This is implementation of Samsung MDC (Multiple Display Control) protocol on **python3.7+** and **asyncio** with most comprehensive CLI (command line interface). It allows you to control a variety of different sources (TV, Monitor) through the built-in RS-232C or Ethernet interface. [MDC Protocol specification - v15.0 2020-11-06](https://vgavro.github.io/samsung-mdc/MDC-Protocol.pdf) * Implemented *83* commands * Easy to extend using simple declarative API - see [samsung_mdc/commands.py](https://github.com/vgavro/samsung-mdc/blob/master/samsung_mdc/commands.py) * Detailed [CLI](#usage) help and parameters validation * Run commands async on numerous targets (using asyncio) * TCP and SERIAL mode (for RJ45 and RS232C connection types) * TCP over TLS mode ("Secured Protocol" using PIN) * [script](#script) command for advanced usage * [Python example](#python-example) Not implemented: some more commands (PRs are welcome) Also see: [Samsung MDC Unified](http://www.samsung-mcloud.com/01_Software/04_Tools/MDC/v1235/) - Reference Application (GUI, Windows) with partially implemented functionality. ## Install ``` # using pipx https://pypa.github.io/pipx/ pipx run python-samsung-mdc --help # OR global install/upgrade sudo pip3 install --upgrade python-samsung-mdc samsung-mdc --help # OR local git clone https://github.com/vgavro/samsung-mdc cd ./samsung-mdc python3 -m venv venv ./venv/bin/pip3 install -e ./ ./venv/bin/samsung-mdc --help ``` ### Windows install 1. Install Git && Git Bash: https://git-scm.com/download/win 2. Install Python 3 latest release (tested with 3.9): https://www.python.org/downloads/windows/ 3. Run "Git Bash", type in console: ``` pip3 install --upgrade python-samsung-mdc # NOTE: python "Scripts" folder is not in %PATH% in Windows by default, # so you may want to create alias for Git Bash echo alias samsung-mdc=\'python3 -m samsung_mdc\' >> ~/.bash_profile source ~/.bash_profile # test it samsung-mdc --help ``` ## Usage ``` Usage: samsung-mdc [OPTIONS] TARGET COMMAND [ARGS]... Try 'samsung-mdc --help COMMAND' for command info For multiple targets commands will be running async, so result order may differ. TARGET may be: DISPLAY_ID@IP[:PORT] (default port: 1515, example: 0@192.168.0.10:1515) FILENAME with target list (separated by newline) For serial port connection: DISPLAY_ID@PORT_NAME for Windows (example: 1@COM1) DISPLAY_ID@PORT_PATH (example: 1@/dev/ttyUSB0) We're trying to make autodetection of connection mode by port name, but you may want to use --mode option. Options: --version Show the version and exit. -v, --verbose -m, --mode [auto|tcp|serial] default: auto -p, --pin INTEGER 4-digit PIN for secured TLS connection. If PIN provided, "Secured Protocol" must be enabled on remote device. -t, --timeout FLOAT read/write/connect timeout in seconds (default: 5) (connect can be overridden with separate option) --connect-timeout FLOAT -h, --help Show this message and exit. ``` ### Commands: * [status](#status) `(POWER_STATE VOLUME MUTE_STATE INPUT_SOURCE_STATE PICTURE_ASPECT_STATE N_TIME_NF F_TIME_NF)` * [video](#video) `(CONTRAST BRIGHTNESS SHARPNESS COLOR TINT COLOR_TONE_STATE COLOR_TEMPERATURE _IGNORE)` * [rgb](#rgb) `(CONTRAST BRIGHTNESS COLOR_TONE_STATE COLOR_TEMPERATURE _IGNORE RED_GAIN GREEN_GAIN BLUE_GAIN)` * [serial_number](#serial_number) `(SERIAL_NUMBER)` * [error_status](#error_status) `(LAMP_ERROR_STATE TEMPERATURE_ERROR_STATE BRIGHTNESS_SENSOR_ERROR_STATE INPUT_SOURCE_ERROR_STATE TEMPERATURE FAN_ERROR_STATE)` * [software_version](#software_version) `(SOFTWARE_VERSION)` * [model_number](#model_number) `(MODEL_SPECIES MODEL_CODE TV_SUPPORT)` * [power](#power) `[POWER_STATE]` * [volume](#volume) `[VOLUME]` * [mute](#mute) `[MUTE_STATE]` * [input_source](#input_source) `[INPUT_SOURCE_STATE]` * [picture_aspect](#picture_aspect) `[PICTURE_ASPECT_STATE]` * [screen_mode](#screen_mode) `[SCREEN_MODE_STATE]` * [screen_size](#screen_size) `(INCHES)` * [network_configuration](#network_configuration) `[IP_ADDRESS SUBNET_MASK GATEWAY_ADDRESS DNS_SERVER_ADDRESS]` * [network_mode](#network_mode) `[NETWORK_MODE_STATE]` * [network_ap_config](#network_ap_config) `SSID PASSWORD` * [weekly_restart](#weekly_restart) `[WEEKDAY TIME]` * [magicinfo_channel](#magicinfo_channel) `CHANNEL_NUMBER` * [magicinfo_server](#magicinfo_server) `[MAGICINFO_SERVER_URL]` * [magicinfo_content_orientation](#magicinfo_content_orientation) `[ORIENTATION_MODE_STATE]` * [mdc_connection](#mdc_connection) `[MDC_CONNECTION_TYPE]` * [contrast](#contrast) `[CONTRAST]` * [brightness](#brightness) `[BRIGHTNESS]` * [sharpness](#sharpness) `[SHARPNESS]` * [color](#color) `[COLOR]` * [tint](#tint) `[TINT]` * [h_position](#h_position) `H_POSITION_MOVE_TO` * [v_position](#v_position) `V_POSITION_MOVE_TO` * [auto_power](#auto_power) `[AUTO_POWER_STATE]` * [clear_menu](#clear_menu) * [ir_state](#ir_state) `[IR_STATE]` * [rgb_contrast](#rgb_contrast) `[CONTRAST]` * [rgb_brightness](#rgb_brightness) `[BRIGHTNESS]` * [auto_adjustment_on](#auto_adjustment_on) * [color_tone](#color_tone) `[COLOR_TONE_STATE]` * [color_temperature](#color_temperature) `[HECTO_KELVIN]` * [standby](#standby) `[STANDBY_STATE]` * [auto_lamp](#auto_lamp) `[MAX_TIME MAX_LAMP_VALUE MIN_TIME MIN_LAMP_VALUE]` * [manual_lamp](#manual_lamp) `[LAMP_VALUE]` * [inverse](#inverse) `[INVERSE_STATE]` * [video_wall_mode](#video_wall_mode) `[VIDEO_WALL_MODE]` * [safety_lock](#safety_lock) `[LOCK_STATE]` * [panel_lock](#panel_lock) `[LOCK_STATE]` * [channel_change](#channel_change) `CHANGE_TO` * [volume_change](#volume_change) `CHANGE_TO` * [ticker](#ticker) `[ON_OFF START_TIME END_TIME POS_HORIZ POS_VERTI MOTION_ON_OFF MOTION_DIR MOTION_SPEED FONT_SIZE FOREGROUND_COLOR BACKGROUND_COLOR FOREGROUND_OPACITY BACKGROUND_OPACITY MESSAGE]` * [device_name](#device_name) `(DEVICE_NAME)` * [osd](#osd) `[OSD_ENABLED]` * [picture_mode](#picture_mode) `[PICTURE_MODE_STATE]` * [sound_mode](#sound_mode) `[SOUND_MODE_STATE]` * [all_keys_lock](#all_keys_lock) `[LOCK_STATE]` * [panel_on_time](#panel_on_time) `(MIN10)` * [video_wall_state](#video_wall_state) `[VIDEO_WALL_STATE]` * [video_wall_model](#video_wall_model) `[MODEL SERIAL]` * [model_name](#model_name) `(MODEL_NAME)` * [energy_saving](#energy_saving) `[ENERGY_SAVING_STATE]` * [reset](#reset) `RESET_TARGET` * [osd_type](#osd_type) `[OSD_TYPE OSD_ENABLED]` * [timer_13](#timer_13) `TIMER_ID [ON_TIME ON_ENABLED OFF_TIME OFF_ENABLED REPEAT MANUAL_WEEKDAY VOLUME INPUT_SOURCE_STATE HOLIDAY_APPLY]` * [timer_15](#timer_15) `TIMER_ID [ON_TIME ON_ENABLED OFF_TIME OFF_ENABLED ON_REPEAT ON_MANUAL_WEEKDAY OFF_REPEAT OFF_MANUAL_WEEKDAY VOLUME INPUT_SOURCE_STATE HOLIDAY_APPLY]` * [clock_m](#clock_m) `[DATETIME]` * [holiday_set](#holiday_set) `HOLIDAY_MANAGE START_MONTH START_DAY END_MONTH END_DAY` * [holiday_get](#holiday_get) `[INDEX]` * [virtual_remote](#virtual_remote) `KEY_CODE` * [network_standby](#network_standby) `[NETWORK_STANDBY_STATE]` * [dst](#dst) `[DST_STATE START_MONTH START_WEEK START_WEEKDAY START_TIME END_MONTH END_WEEK END_WEEKDAY END_TIME OFFSET]` * [auto_id_setting](#auto_id_setting) `[AUTO_ID_SETTING_STATE]` * [display_id](#display_id) `DISPLAY_ID_STATE` * [clock_s](#clock_s) `[DATETIME]` * [launcher_play_via](#launcher_play_via) `[PLAY_VIA_MODE]` * [launcher_url_address](#launcher_url_address) `[URL_ADDRESS]` * [osd_menu_orientation](#osd_menu_orientation) `[ORIENTATION_MODE_STATE]` * [osd_source_content_orientation](#osd_source_content_orientation) `[ORIENTATION_MODE_STATE]` * [osd_aspect_ratio](#osd_aspect_ratio) `[ASPECT_RATIO_STATE]` * [osd_pip_orientation](#osd_pip_orientation) `[ORIENTATION_MODE_STATE]` * [osd_menu_size](#osd_menu_size) `[MENU_SIZE_STATE]` * [auto_source_switch](#auto_source_switch) `[AUTO_SOURCE_SWITCH_STATE]` * [auto_source](#auto_source) `[PRIMARY_SOURCE_RECOVERY PRIMARY_SOURCE SECONDARY_SOURCE]` * [panel](#panel) `[PANEL_STATE]` * [screen_mute](#screen_mute) `[SCREEN_MUTE_STATUS]` * [script](#script) `[OPTIONS] SCRIPT_FILE` * [raw](#raw) `[OPTIONS] COMMAND [DATA]` #### status ``` Usage: samsung-mdc [OPTIONS] TARGET status Get the device various state like power, volume, sound mute, input source, picture aspect ratio. Note: For no audio models volume and mute returns 0xFF (255). N_TIME_NF, F_TIME_NF: OnTime/OffTime ON/OFF value (old type timer, now it's always 0x00). Data: POWER_STATE OFF | ON | REBOOT VOLUME int (0-100) MUTE_STATE OFF | ON | NONE INPUT_SOURCE_STATE NONE | S_VIDEO | COMPONENT | AV | AV2 | SCART1 | DVI | PC | BNC | DVI_VIDEO | MAGIC_INFO | HDMI1 | HDMI1_PC | HDMI2 | HDMI2_PC | DISPLAY_PORT_1 | DISPLAY_PORT_2 | DISPLAY_PORT_3 | RF_TV | HDMI3 | HDMI3_PC | HDMI4 | HDMI4_PC | TV_DTV | PLUG_IN_MODE | HD_BASE_T | OCM | MEDIA_MAGIC_INFO_S | WIDI_SCREEN_MIRRORING | INTERNAL_USB | URL_LAUNCHER | IWB | WEB_BROWSER | REMOTE_WORKSPACE PICTURE_ASPECT_STATE PC_16_9 | PC_4_3 | PC_ORIGINAL_RATIO | PC_21_9 | PC_CUSTOM | VIDEO_AUTO_WIDE | VIDEO_16_9 | VIDEO_ZOOM | VIDEO_ZOOM_1 | VIDEO_ZOOM_2 | VIDEO_SCREEN_FIT | VIDEO_4_3 | VIDEO_WIDE_FIT | VIDEO_CUSTOM | VIDEO_SMART_VIEW_1 | VIDEO_SMART_VIEW_2 | VIDEO_WIDE_ZOOM | VIDEO_21_9 N_TIME_NF int F_TIME_NF int ``` #### video ``` Usage: samsung-mdc [OPTIONS] TARGET video Data: CONTRAST int (0-100) BRIGHTNESS int (0-100) SHARPNESS int (0-100) COLOR int (0-100) TINT int (0-100) COLOR_TONE_STATE COOL_2 | COOL_1 | NORMAL | WARM_1 | WARM_2 | OFF COLOR_TEMPERATURE int _IGNORE int (0-0) ``` #### rgb ``` Usage: samsung-mdc [OPTIONS] TARGET rgb Data: CONTRAST int (0-100) BRIGHTNESS int (0-100) COLOR_TONE_STATE COOL_2 | COOL_1 | NORMAL | WARM_1 | WARM_2 | OFF COLOR_TEMPERATURE int _IGNORE int (0-0) RED_GAIN int GREEN_GAIN int BLUE_GAIN int ``` #### serial_number ``` Usage: samsung-mdc [OPTIONS] TARGET serial_number Data: SERIAL_NUMBER str ``` #### error_status ``` Usage: samsung-mdc [OPTIONS] TARGET error_status Data: LAMP_ERROR_STATE NORMAL | ERROR TEMPERATURE_ERROR_STATE NORMAL | ERROR BRIGHTNESS_SENSOR_ERROR_STATE NONE | ERROR | NORMAL INPUT_SOURCE_ERROR_STATE NORMAL | ERROR | INVALID TEMPERATURE int FAN_ERROR_STATE NORMAL | ERROR | NONE ``` #### software_version ``` Usage: samsung-mdc [OPTIONS] TARGET software_version Data: SOFTWARE_VERSION str ``` #### model_number ``` Usage: samsung-mdc [OPTIONS] TARGET model_number Data: MODEL_SPECIES PDP | LCD | DLP | LED | CRT | OLED MODEL_CODE int TV_SUPPORT SUPPORTED | NOT_SUPPORTED ``` #### power ``` Usage: samsung-mdc [OPTIONS] TARGET power [POWER_STATE] Data: POWER_STATE OFF | ON | REBOOT ``` #### volume ``` Usage: samsung-mdc [OPTIONS] TARGET volume [VOLUME] Data: VOLUME int (0-100) ``` #### mute ``` Usage: samsung-mdc [OPTIONS] TARGET mute [MUTE_STATE] Data: MUTE_STATE OFF | ON | NONE ``` #### input_source ``` Usage: samsung-mdc [OPTIONS] TARGET input_source [INPUT_SOURCE_STATE] Get/Set the device source which is shown on the screen. DVI_VIDEO, HDMI1_PC, HDMI2_PC, HDMI3_PC, HDMI4_PC: get only. URL_LAUNCHER, MAGIC_INFO, TV or some ports require support by model. On TIMER functions, Do not use WIDI_SCREEN_MIRRORING. Data: INPUT_SOURCE_STATE NONE | S_VIDEO | COMPONENT | AV | AV2 | SCART1 | DVI | PC | BNC | DVI_VIDEO | MAGIC_INFO | HDMI1 | HDMI1_PC | HDMI2 | HDMI2_PC | DISPLAY_PORT_1 | DISPLAY_PORT_2 | DISPLAY_PORT_3 | RF_TV | HDMI3 | HDMI3_PC | HDMI4 | HDMI4_PC | TV_DTV | PLUG_IN_MODE | HD_BASE_T | OCM | MEDIA_MAGIC_INFO_S | WIDI_SCREEN_MIRRORING | INTERNAL_USB | URL_LAUNCHER | IWB | WEB_BROWSER | REMOTE_WORKSPACE ``` #### picture_aspect ``` Usage: samsung-mdc [OPTIONS] TARGET picture_aspect [PICTURE_ASPECT_STATE] Get/Set the device picture size (aspect ratio). Working Condition: Will not work with VIDEO_WALL_STATE is ON. Note: Some of the image sizes are not supported depending on input signals. Data: PICTURE_ASPECT_STATE PC_16_9 | PC_4_3 | PC_ORIGINAL_RATIO | PC_21_9 | PC_CUSTOM | VIDEO_AUTO_WIDE | VIDEO_16_9 | VIDEO_ZOOM | VIDEO_ZOOM_1 | VIDEO_ZOOM_2 | VIDEO_SCREEN_FIT | VIDEO_4_3 | VIDEO_WIDE_FIT | VIDEO_CUSTOM | VIDEO_SMART_VIEW_1 | VIDEO_SMART_VIEW_2 | VIDEO_WIDE_ZOOM | VIDEO_21_9 ``` #### screen_mode ``` Usage: samsung-mdc [OPTIONS] TARGET screen_mode [SCREEN_MODE_STATE] Data: SCREEN_MODE_STATE MODE_16_9 | MODE_ZOOM | MODE_4_3 | MODE_WIDE_ZOOM ``` #### screen_size ``` Usage: samsung-mdc [OPTIONS] TARGET screen_size Data: INCHES int (0-255) ``` #### network_configuration ``` Usage: samsung-mdc [OPTIONS] TARGET network_configuration [IP_ADDRESS SUBNET_MASK GATEWAY_ADDRESS DNS_SERVER_ADDRESS] Data: IP_ADDRESS IP address SUBNET_MASK IP address GATEWAY_ADDRESS IP address DNS_SERVER_ADDRESS IP address ``` #### network_mode ``` Usage: samsung-mdc [OPTIONS] TARGET network_mode [NETWORK_MODE_STATE] Data: NETWORK_MODE_STATE DYNAMIC | STATIC ``` #### network_ap_config ``` Usage: samsung-mdc [OPTIONS] TARGET network_ap_config SSID PASSWORD Add new SSID info to device connection history with its password. Note: device may change network and response may not be received. Data: SSID str PASSWORD str ``` #### weekly_restart ``` Usage: samsung-mdc [OPTIONS] TARGET weekly_restart [WEEKDAY TIME] Data: WEEKDAY list(,) SUN | SAT | FRI | THU | WED | TUE | MON TIME time (format: %H:%M) ``` #### magicinfo_channel ``` Usage: samsung-mdc [OPTIONS] TARGET magicinfo_channel CHANNEL_NUMBER Set MagicInfo Channel by Direct Channel Number which is used by MagicInfo S Player. Data: CHANNEL_NUMBER int ``` #### magicinfo_server ``` Usage: samsung-mdc [OPTIONS] TARGET magicinfo_server [MAGICINFO_SERVER_URL] MagicInfo Server URL. Example: "http://example.com:80" Data: MAGICINFO_SERVER_URL str ``` #### magicinfo_content_orientation ``` Usage: samsung-mdc [OPTIONS] TARGET magicinfo_content_orientation [ORIENTATION_MODE_STATE] Data: ORIENTATION_MODE_STATE LANDSCAPE_0 | PORTRAIT_270 | LANDSCAPE_180 | PORTRAIT_90 ``` #### mdc_connection ``` Usage: samsung-mdc [OPTIONS] TARGET mdc_connection [MDC_CONNECTION_TYPE] Note: Depends on the product specification, if it is set as RJ45 then serial MDC will not work. Data: MDC_CONNECTION_TYPE RS232C | RJ45 ``` #### contrast ``` Usage: samsung-mdc [OPTIONS] TARGET contrast [CONTRAST] Data: CONTRAST int (0-100) ``` #### brightness ``` Usage: samsung-mdc [OPTIONS] TARGET brightness [BRIGHTNESS] Data: BRIGHTNESS int (0-100) ``` #### sharpness ``` Usage: samsung-mdc [OPTIONS] TARGET sharpness [SHARPNESS] Data: SHARPNESS int (0-100) ``` #### color ``` Usage: samsung-mdc [OPTIONS] TARGET color [COLOR] Data: COLOR int (0-100) ``` #### tint ``` Usage: samsung-mdc [OPTIONS] TARGET tint [TINT] Control the device tint. Adjust the ratio of green to red tint level. Red: TINT value, Green: ( 100 - TINT ) value. Note: Tint could only be set in 50 Steps (0, 2, 4, 6... 100). Data: TINT int (0-100) ``` #### h_position ``` Usage: samsung-mdc [OPTIONS] TARGET h_position H_POSITION_MOVE_TO Data: H_POSITION_MOVE_TO LEFT | RIGHT ``` #### v_position ``` Usage: samsung-mdc [OPTIONS] TARGET v_position V_POSITION_MOVE_TO Data: V_POSITION_MOVE_TO UP | DOWN ``` #### auto_power ``` Usage: samsung-mdc [OPTIONS] TARGET auto_power [AUTO_POWER_STATE] Data: AUTO_POWER_STATE OFF | ON ``` #### clear_menu ``` Usage: samsung-mdc [OPTIONS] TARGET clear_menu ``` #### ir_state ``` Usage: samsung-mdc [OPTIONS] TARGET ir_state [IR_STATE] Enables/disables IR (Infrared) receiving function (Remote Control). Working Condition: * Can operate regardless of whether power is ON/OFF. (If DPMS Situation in LFD, it operate Remocon regardless of set value). Data: IR_STATE DISABLED | ENABLED ``` #### rgb_contrast ``` Usage: samsung-mdc [OPTIONS] TARGET rgb_contrast [CONTRAST] Data: CONTRAST int (0-100) ``` #### rgb_brightness ``` Usage: samsung-mdc [OPTIONS] TARGET rgb_brightness [BRIGHTNESS] Data: BRIGHTNESS int (0-100) ``` #### auto_adjustment_on ``` Usage: samsung-mdc [OPTIONS] TARGET auto_adjustment_on ``` #### color_tone ``` Usage: samsung-mdc [OPTIONS] TARGET color_tone [COLOR_TONE_STATE] Data: COLOR_TONE_STATE COOL_2 | COOL_1 | NORMAL | WARM_1 | WARM_2 | OFF ``` #### color_temperature ``` Usage: samsung-mdc [OPTIONS] TARGET color_temperature [HECTO_KELVIN] Color temperature function. Unit is hectoKelvin (hK) (x*100 Kelvin) (example: 28 = 2800K). Supported values - 28, 30, 35, 40... 160. For older models: 0-10=(x*100K + 5000K), 253=2800K, 254=3000K, 255=4000K Data: HECTO_KELVIN int ``` #### standby ``` Usage: samsung-mdc [OPTIONS] TARGET standby [STANDBY_STATE] Data: STANDBY_STATE OFF | ON | AUTO ``` #### auto_lamp ``` Usage: samsung-mdc [OPTIONS] TARGET auto_lamp [MAX_TIME MAX_LAMP_VALUE MIN_TIME MIN_LAMP_VALUE] Auto Lamp function (backlight). Note: When Manual Lamp Control is on, Auto Lamp Control will automatically turn off. Data: MAX_TIME time (format: %H:%M) MAX_LAMP_VALUE int (0-100) MIN_TIME time (format: %H:%M) MIN_LAMP_VALUE int (0-100) ``` #### manual_lamp ``` Usage: samsung-mdc [OPTIONS] TARGET manual_lamp [LAMP_VALUE] Manual Lamp function (backlight). Note: When Auto Lamp Control is on, Manual Lamp Control will automatically turn off. Data: LAMP_VALUE int (0-100) ``` #### inverse ``` Usage: samsung-mdc [OPTIONS] TARGET inverse [INVERSE_STATE] Data: INVERSE_STATE OFF | ON ``` #### video_wall_mode ``` Usage: samsung-mdc [OPTIONS] TARGET video_wall_mode [VIDEO_WALL_MODE] Get/Set the device in aspect ratio of the video wall. FULL: stretch input source to fill display NATURAL: Keep aspect ratio of input source; do not fill display. Note: Needs VIDEO_WALL_STATE to be ON. Data: VIDEO_WALL_MODE NATURAL | FULL ``` #### safety_lock ``` Usage: samsung-mdc [OPTIONS] TARGET safety_lock [LOCK_STATE] Data: LOCK_STATE OFF | ON ``` #### panel_lock ``` Usage: samsung-mdc [OPTIONS] TARGET panel_lock [LOCK_STATE] Data: LOCK_STATE OFF | ON ``` #### channel_change ``` Usage: samsung-mdc [OPTIONS] TARGET channel_change CHANGE_TO Data: CHANGE_TO UP | DOWN ``` #### volume_change ``` Usage: samsung-mdc [OPTIONS] TARGET volume_change CHANGE_TO Data: CHANGE_TO UP | DOWN ``` #### ticker ``` Usage: samsung-mdc [OPTIONS] TARGET ticker [ON_OFF START_TIME END_TIME POS_HORIZ POS_VERTI MOTION_ON_OFF MOTION_DIR MOTION_SPEED FONT_SIZE FOREGROUND_COLOR BACKGROUND_COLOR FOREGROUND_OPACITY BACKGROUND_OPACITY MESSAGE] Get/Set the device ticker. (Show text message overlay on the screen) Note: POS_HORIZ or POS_VERT are NONE in GET response if unsupported by the display. Data: ON_OFF bool START_TIME time (format: %H:%M) END_TIME time (format: %H:%M) POS_HORIZ CENTER | LEFT | RIGHT | NONE POS_VERTI MIDDLE | TOP | BOTTOM | NONE MOTION_ON_OFF bool MOTION_DIR LEFT | RIGHT | UP | DOWN MOTION_SPEED NORMAL | SLOW | FAST FONT_SIZE STANDARD | SMALL | LARGE FOREGROUND_COLOR BLACK | WHITE | RED | GREEN | BLUE | YELLOW | MAGENTA | CYAN BACKGROUND_COLOR BLACK | WHITE | RED | GREEN | BLUE | YELLOW | MAGENTA | CYAN FOREGROUND_OPACITY FLASHING | FLASH_ALL | OFF BACKGROUND_OPACITY SOLID | TRANSPARENT | TRANSLUCENT | UNKNOWN MESSAGE str ``` #### device_name ``` Usage: samsung-mdc [OPTIONS] TARGET device_name It reads the device name which user set up in network. Shows the information about entered device name. Data: DEVICE_NAME str ``` #### osd ``` Usage: samsung-mdc [OPTIONS] TARGET osd [OSD_ENABLED] Turns OSD (On-screen display) on/off. Data: OSD_ENABLED bool ``` #### picture_mode ``` Usage: samsung-mdc [OPTIONS] TARGET picture_mode [PICTURE_MODE_STATE] Data: PICTURE_MODE_STATE DYNAMIC | STANDARD | MOVIE | CUSTOM_TV | NATURAL | CALIBRATION_TV | ENTERTAIN | INTERNET | TEXT | CUSTOM | ADVERTISEMENT | INFORMATION | CALIBRATION | SHOP_MALL_VIDEO | SHOP_MALL_TEXT | OFFICE_SCHOOL_VIDEO | OFFICE_SCHOOL_TEXT | TERMINAL_STATION_VIDEO | TERMINAL_STATION_TEXT | VIDEO_WALL_VIDEO | VIDEO_WALL_TEXT | HDR_PLUS | OFF | RESERVED_OTHER ``` #### sound_mode ``` Usage: samsung-mdc [OPTIONS] TARGET sound_mode [SOUND_MODE_STATE] Data: SOUND_MODE_STATE STANDARD | MUSIC | MOVIE | SPEECH | CUSTOM | AMPLIFY | OPTIMIZED ``` #### all_keys_lock ``` Usage: samsung-mdc [OPTIONS] TARGET all_keys_lock [LOCK_STATE] Turns both REMOCON and Panel Key Lock function on/off. Note: Can operate regardless of whether power is on/off. Data: LOCK_STATE OFF | ON ``` #### panel_on_time ``` Usage: samsung-mdc [OPTIONS] TARGET panel_on_time Get the device panel on total time. Return value increased every 10 mins. To get hours use "MIN10 / 6". Data: MIN10 int ``` #### video_wall_state ``` Usage: samsung-mdc [OPTIONS] TARGET video_wall_state [VIDEO_WALL_STATE] Get/Set the device in video wall state. This will split the primary input source into smaller N number of squares and display them instead. Note: The device needs to be capable of this operation. Usually a primary high resolution source signal is daisy chained to lower resolution displays in a video wall using HDMI/DP. Data: VIDEO_WALL_STATE OFF | ON ``` #### video_wall_model ``` Usage: samsung-mdc [OPTIONS] TARGET video_wall_model [MODEL SERIAL] Get/Set video wall model. MODEL: Size of the wall in (x, y) coordinates; ie. "2,2" or "4,1" SERIAL: Serial number - position of the display in the video wall, counting from the first display. Note: Needs VIDEO_WALL_STATE to be ON. Data: MODEL Video Wall model (format: X,Y eg. 4,5) SERIAL int (1-255) ``` #### model_name ``` Usage: samsung-mdc [OPTIONS] TARGET model_name Data: MODEL_NAME str ``` #### energy_saving ``` Usage: samsung-mdc [OPTIONS] TARGET energy_saving [ENERGY_SAVING_STATE] Data: ENERGY_SAVING_STATE OFF | LOW | MEDIUM | HIGH | PICTURE_OFF ``` #### reset ``` Usage: samsung-mdc [OPTIONS] TARGET reset RESET_TARGET Data: RESET_TARGET PICTURE | SOUND | SETUP | ALL | SCREEN_DISPLAY ``` #### osd_type ``` Usage: samsung-mdc [OPTIONS] TARGET osd_type [OSD_TYPE OSD_ENABLED] Turns OSD (On-screen display) specific message types on/off. Data: OSD_TYPE SOURCE | NOT_OPTIMUM_MODE | NO_SIGNAL | MDC | SCHEDULE_CHANNEL OSD_ENABLED bool ``` #### timer_13 ``` Usage: samsung-mdc [OPTIONS] TARGET timer_13 TIMER_ID [ON_TIME ON_ENABLED OFF_TIME OFF_ENABLED REPEAT MANUAL_WEEKDAY VOLUME INPUT_SOURCE_STATE HOLIDAY_APPLY] Integrated timer function (13 data-length version). Note: This depends on product and will not work on newer versions. Data: TIMER_ID int (1-7) ON_TIME time (format: %H:%M) ON_ENABLED bool OFF_TIME time (format: %H:%M) OFF_ENABLED bool REPEAT ONCE | EVERYDAY | MON_FRI | MON_SAT | SAT_SUN | MANUAL_WEEKDAY MANUAL_WEEKDAY list(,) SUN | MON | TUE | WED | THU | FRI | SAT VOLUME int (0-100) INPUT_SOURCE_STATE NONE | S_VIDEO | COMPONENT | AV | AV2 | SCART1 | DVI | PC | BNC | DVI_VIDEO | MAGIC_INFO | HDMI1 | HDMI1_PC | HDMI2 | HDMI2_PC | DISPLAY_PORT_1 | DISPLAY_PORT_2 | DISPLAY_PORT_3 | RF_TV | HDMI3 | HDMI3_PC | HDMI4 | HDMI4_PC | TV_DTV | PLUG_IN_MODE | HD_BASE_T | OCM | MEDIA_MAGIC_INFO_S | WIDI_SCREEN_MIRRORING | INTERNAL_USB | URL_LAUNCHER | IWB | WEB_BROWSER | REMOTE_WORKSPACE HOLIDAY_APPLY DONT_APPLY_BOTH | APPLY_BOTH | ON_TIMER_ONLY_APPLY | OFF_TIMER_ONLY_APPLY ``` #### timer_15 ``` Usage: samsung-mdc [OPTIONS] TARGET timer_15 TIMER_ID [ON_TIME ON_ENABLED OFF_TIME OFF_ENABLED ON_REPEAT ON_MANUAL_WEEKDAY OFF_REPEAT OFF_MANUAL_WEEKDAY VOLUME INPUT_SOURCE_STATE HOLIDAY_APPLY] Integrated timer function (15 data-length version). Note: This depends on product and will not work on older versions. ON_TIME/OFF_TIME: turn ON/OFF display at specific time of day ON_ACTIVE/OFF_ACTIVE: if timer is not active, values are ignored, so there may be only OFF timer, ON timer, or both. REPEAT: On which day timer is enabled (combined with HOLIDAY_APPLY and MANUAL_WEEKDAY) Data: TIMER_ID int (1-7) ON_TIME time (format: %H:%M) ON_ENABLED bool OFF_TIME time (format: %H:%M) OFF_ENABLED bool ON_REPEAT ONCE | EVERYDAY | MON_FRI | MON_SAT | SAT_SUN | MANUAL_WEEKDAY ON_MANUAL_WEEKDAY list(,) SUN | MON | TUE | WED | THU | FRI | SAT OFF_REPEAT ONCE | EVERYDAY | MON_FRI | MON_SAT | SAT_SUN | MANUAL_WEEKDAY OFF_MANUAL_WEEKDAY list(,) SUN | MON | TUE | WED | THU | FRI | SAT VOLUME int (0-100) INPUT_SOURCE_STATE NONE | S_VIDEO | COMPONENT | AV | AV2 | SCART1 | DVI | PC | BNC | DVI_VIDEO | MAGIC_INFO | HDMI1 | HDMI1_PC | HDMI2 | HDMI2_PC | DISPLAY_PORT_1 | DISPLAY_PORT_2 | DISPLAY_PORT_3 | RF_TV | HDMI3 | HDMI3_PC | HDMI4 | HDMI4_PC | TV_DTV | PLUG_IN_MODE | HD_BASE_T | OCM | MEDIA_MAGIC_INFO_S | WIDI_SCREEN_MIRRORING | INTERNAL_USB | URL_LAUNCHER | IWB | WEB_BROWSER | REMOTE_WORKSPACE HOLIDAY_APPLY DONT_APPLY_BOTH | APPLY_BOTH | ON_TIMER_ONLY_APPLY | OFF_TIMER_ONLY_APPLY ``` #### clock_m ``` Usage: samsung-mdc [OPTIONS] TARGET clock_m [DATETIME] Current time function (minute precision). Note: This is for models developed until 2013. For newer models see CLOCK_S function (seconds precision). Data: DATETIME datetime (format: %Y-%m-%dT%H:%M / %Y-%m-%d %H:%M) ``` #### holiday_set ``` Usage: samsung-mdc [OPTIONS] TARGET holiday_set HOLIDAY_MANAGE START_MONTH START_DAY END_MONTH END_DAY Add/Delete the device holiday schedule with the holiday schedule itself start month/day and end month/day. Note: On DELETE_ALL all parameters should be 0x00. Data: HOLIDAY_MANAGE ADD | DELETE | DELETE_ALL START_MONTH int (0-12) START_DAY int (0-31) END_MONTH int (0-12) END_DAY int (0-31) ``` #### holiday_get ``` Usage: samsung-mdc [OPTIONS] TARGET holiday_get [INDEX] Get the device holiday schedule. If INDEX is not specified, returns total number of Holiday Information. Data: INDEX int Response extra: START_MONTH int START_DAY int END_MONTH int END_DAY int ``` #### virtual_remote ``` Usage: samsung-mdc [OPTIONS] TARGET virtual_remote KEY_CODE This function support that MDC command can work same as remote control. Note: In a certain model, 0x79 CONTENT key works as HOME and 0x1F DISPLAY key works as INFO. Data: KEY_CODE KEY_SOURCE | KEY_POWER | KEY_1 | KEY_2 | KEY_3 | KEY_VOLUME_UP | KEY_4 | KEY_5 | KEY_6 | KEY_VOLUME_DOWN | KEY_7 | KEY_8 | KEY_9 | KEY_MUTE | KEY_CHANNEL_DOWN | KEY_0 | KEY_CHANNEL_UP | KEY_GREEN | KEY_YELLOW | KEY_CYAN | KEY_MENU | KEY_DISPLAY | KEY_DIGIT | KEY_PIP_TV_VIDEO | KEY_EXIT | KEY_MAGICINFO | KEY_REW | KEY_STOP | KEY_PLAY | KEY_FF | KEY_PAUSE | KEY_TOOLS | KEY_RETURN | KEY_MAGICINFO_LITE | KEY_CURSOR_UP | KEY_CURSOR_DOWN | KEY_CURSOR_RIGHT | KEY_CURSOR_LEFT | KEY_ENTER | KEY_RED | KEY_LOCK | KEY_CONTENT | DISCRET_POWER_OFF | KEY_3D ``` #### network_standby ``` Usage: samsung-mdc [OPTIONS] TARGET network_standby [NETWORK_STANDBY_STATE] Data: NETWORK_STANDBY_STATE OFF | ON ``` #### dst ``` Usage: samsung-mdc [OPTIONS] TARGET dst [DST_STATE START_MONTH START_WEEK START_WEEKDAY START_TIME END_MONTH END_WEEK END_WEEKDAY END_TIME OFFSET] Data: DST_STATE OFF | AUTO | MANUAL START_MONTH JAN | FEB | MAR | APR | MAY | JUN | JUL | AUG | SEP | OCT | NOV | DEC START_WEEK WEEK_1 | WEEK_2 | WEEK_3 | WEEK_4 | WEEK_LAST START_WEEKDAY SUN | MON | TUE | WED | THU | FRI | SAT START_TIME time (format: %H:%M) END_MONTH JAN | FEB | MAR | APR | MAY | JUN | JUL | AUG | SEP | OCT | NOV | DEC END_WEEK WEEK_1 | WEEK_2 | WEEK_3 | WEEK_4 | WEEK_LAST END_WEEKDAY SUN | MON | TUE | WED | THU | FRI | SAT END_TIME time (format: %H:%M) OFFSET PLUS_1_00 | PLUS_2_00 Response extra: TUNER_SUPPORT bool ``` #### auto_id_setting ``` Usage: samsung-mdc [OPTIONS] TARGET auto_id_setting [AUTO_ID_SETTING_STATE] Data: AUTO_ID_SETTING_STATE START | END ``` #### display_id ``` Usage: samsung-mdc [OPTIONS] TARGET display_id DISPLAY_ID_STATE Data: DISPLAY_ID_STATE OFF | ON ``` #### clock_s ``` Usage: samsung-mdc [OPTIONS] TARGET clock_s [DATETIME] Current time function (second precision). Note: This is for models developed after 2013. For older models see CLOCK_M function (minute precision). Data: DATETIME datetime (format: %Y-%m-%dT%H:%M:%S / %Y-%m-%d %H:%M:%S) ``` #### launcher_play_via ``` Usage: samsung-mdc [OPTIONS] TARGET launcher_play_via [PLAY_VIA_MODE] Data: PLAY_VIA_MODE MAGIC_INFO | URL_LAUNCHER | MAGIC_IWB ``` #### launcher_url_address ``` Usage: samsung-mdc [OPTIONS] TARGET launcher_url_address [URL_ADDRESS] Data: URL_ADDRESS str ``` #### osd_menu_orientation ``` Usage: samsung-mdc [OPTIONS] TARGET osd_menu_orientation [ORIENTATION_MODE_STATE] Data: ORIENTATION_MODE_STATE LANDSCAPE_0 | PORTRAIT_270 | LANDSCAPE_180 | PORTRAIT_90 ``` #### osd_source_content_orientation ``` Usage: samsung-mdc [OPTIONS] TARGET osd_source_content_orientation [ORIENTATION_MODE_STATE] Data: ORIENTATION_MODE_STATE LANDSCAPE_0 | PORTRAIT_270 | LANDSCAPE_180 | PORTRAIT_90 ``` #### osd_aspect_ratio ``` Usage: samsung-mdc [OPTIONS] TARGET osd_aspect_ratio [ASPECT_RATIO_STATE] Get/Set the device aspect ratio under portrait mode which set the rotated screen to be full or original. Data: ASPECT_RATIO_STATE FULL_SCREEN | ORIGINAL ``` #### osd_pip_orientation ``` Usage: samsung-mdc [OPTIONS] TARGET osd_pip_orientation [ORIENTATION_MODE_STATE] Data: ORIENTATION_MODE_STATE LANDSCAPE_0 | PORTRAIT_270 | LANDSCAPE_180 | PORTRAIT_90 ``` #### osd_menu_size ``` Usage: samsung-mdc [OPTIONS] TARGET osd_menu_size [MENU_SIZE_STATE] Data: MENU_SIZE_STATE ORIGINAL | MEDIUM | SMALL ``` #### auto_source_switch ``` Usage: samsung-mdc [OPTIONS] TARGET auto_source_switch [AUTO_SOURCE_SWITCH_STATE] Data: AUTO_SOURCE_SWITCH_STATE OFF | ON ``` #### auto_source ``` Usage: samsung-mdc [OPTIONS] TARGET auto_source [PRIMARY_SOURCE_RECOVERY PRIMARY_SOURCE SECONDARY_SOURCE] Data: PRIMARY_SOURCE_RECOVERY OFF | ON PRIMARY_SOURCE NONE | S_VIDEO | COMPONENT | AV | AV2 | SCART1 | DVI | PC | BNC | DVI_VIDEO | MAGIC_INFO | HDMI1 | HDMI1_PC | HDMI2 | HDMI2_PC | DISPLAY_PORT_1 | DISPLAY_PORT_2 | DISPLAY_PORT_3 | RF_TV | HDMI3 | HDMI3_PC | HDMI4 | HDMI4_PC | TV_DTV | PLUG_IN_MODE | HD_BASE_T | OCM | MEDIA_MAGIC_INFO_S | WIDI_SCREEN_MIRRORING | INTERNAL_USB | URL_LAUNCHER | IWB | WEB_BROWSER | REMOTE_WORKSPACE SECONDARY_SOURCE NONE | S_VIDEO | COMPONENT | AV | AV2 | SCART1 | DVI | PC | BNC | DVI_VIDEO | MAGIC_INFO | HDMI1 | HDMI1_PC | HDMI2 | HDMI2_PC | DISPLAY_PORT_1 | DISPLAY_PORT_2 | DISPLAY_PORT_3 | RF_TV | HDMI3 | HDMI3_PC | HDMI4 | HDMI4_PC | TV_DTV | PLUG_IN_MODE | HD_BASE_T | OCM | MEDIA_MAGIC_INFO_S | WIDI_SCREEN_MIRRORING | INTERNAL_USB | URL_LAUNCHER | IWB | WEB_BROWSER | REMOTE_WORKSPACE ``` #### panel ``` Usage: samsung-mdc [OPTIONS] TARGET panel [PANEL_STATE] Data: PANEL_STATE ON | OFF ``` #### screen_mute ``` Usage: samsung-mdc [OPTIONS] TARGET screen_mute [SCREEN_MUTE_STATUS] Data: SCREEN_MUTE_STATUS ON | OFF ``` #### script ``` Usage: samsung-mdc [OPTIONS] TARGET script [OPTIONS] SCRIPT_FILE Script file with commands to execute. Commands for multiple targets will be running async, but commands order is preserved for device (and is running on same connection), exit on first fail unless retry options provided. You may use jinja2 templating engine to {% include "other_script" %} or {{ VAR_KEY }} rendering in combination with --var VAR_KEY VAR_VALUE options. It's highly recommended to use sleep option for virtual_remote! Additional commands: sleep SECONDS (FLOAT, --sleep option for this command is ignored) disconnect Format: command1 [ARGS]... command2 [ARGS]... Example: samsung-mdc ./targets.txt script -s 3 -r 1 -v KEY enter ./commands.txt # commands.txt content power on sleep 5 clear_menu virtual_remote key_menu virtual_remote key_down virtual_remote {{ KEY }} clear_menu Arguments: script_file Text file with commands, separated by newline. Options: -s, --sleep FLOAT Pause between commands (seconds) --retry-command INTEGER Retry command if failed (count) --retry-command-sleep FLOAT Sleep before command retry (seconds) -r, --retry-script INTEGER Retry script if failed (count) --retry-script-sleep FLOAT Sleep before script retry (seconds) --ignore-nak Ignore negative acknowledgement errors -v, --var NAME VALUE Variable "{{ NAME }}" in script will be replaced by VALUE --help Show this message and exit. ``` #### raw ``` Usage: samsung-mdc [OPTIONS] TARGET raw [OPTIONS] COMMAND [DATA] Helper command to send raw data for test purposes. Arguments: command Command and (optionally) subcommand (example: a1 or a1:b2) data Data payload if any (example: a1:b2) ``` ## Troubleshooting ### Finding DISPLAY ID On most devices it's usually `0` or `1`. Some devices may use `255` (0xFF) or `254` (0xFE) as all/any display, but behavior in such cases for more than 1 display is undefined. Display id can be found using remote control: `Home` -> `ID Settings`. ### NAKError If you receive NAK errors on some commands, you may try to: * Ensure that device is powered on and completely loaded * Switch to input source HDMI1 * Reboot device * Reset all settings * Disable MagicINFO * Factory reset (using "Service Menu") ## Python example ```python3 import asyncio from samsung_mdc import MDC async def main(ip, display_id): async with MDC(ip, verbose=True) as mdc: # First argument of command is always display_id status = await mdc.status(display_id) print(status) # Result is always tuple if status[0] != MDC.power.POWER_STATE.ON: # Command arguments are always Sequence (tuple, list) await mdc.power(display_id, [MDC.power.POWER_STATE.ON]) await mdc.close() # Force reconnect on next command await asyncio.sleep(15) await mdc.display_id(display_id, [MDC.display_id.DISPLAY_ID_STATE.ON]) # You may also use names or values instead of enums await mdc.display_id(display_id, ['ON']) # same await mdc.display_id(display_id, [1]) # same # If you see "Connected" and timeout error, try other display_id (0, 1) asyncio.run(main('192.168.0.10', 1)) ``` ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1736112705.0172837 python-samsung-mdc-1.15.0/python_samsung_mdc.egg-info/0000755000175100002000000000000014736575101022327 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1736112704.0 python-samsung-mdc-1.15.0/python_samsung_mdc.egg-info/PKG-INFO0000644000175100002000000011673414736575100023437 0ustar00runnerdockerMetadata-Version: 2.1 Name: python-samsung-mdc Version: 1.15.0 Summary: Samsung Multiple Display Control (MDC) protocol implementation (asyncio library + CLI interface) Home-page: http://github.com/vgavro/samsung-mdc Author: Victor Gavro Author-email: vgavro@gmail.com License: BSD-3-Clause Keywords: samsung,mdc Classifier: License :: OSI Approved :: BSD License Classifier: Intended Audience :: Developers Classifier: Intended Audience :: System Administrators Classifier: Intended Audience :: Telecommunications Industry Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 Classifier: Development Status :: 5 - Production/Stable Classifier: Operating System :: OS Independent Classifier: Topic :: Multimedia :: Video :: Display Classifier: Topic :: Home Automation Classifier: Topic :: Utilities Requires-Python: >=3.7,<4.0 Description-Content-Type: text/markdown Provides-Extra: test Provides-Extra: serial Provides-Extra: all License-File: LICENSE # Samsung-MDC This is implementation of Samsung MDC (Multiple Display Control) protocol on **python3.7+** and **asyncio** with most comprehensive CLI (command line interface). It allows you to control a variety of different sources (TV, Monitor) through the built-in RS-232C or Ethernet interface. [MDC Protocol specification - v15.0 2020-11-06](https://vgavro.github.io/samsung-mdc/MDC-Protocol.pdf) * Implemented *83* commands * Easy to extend using simple declarative API - see [samsung_mdc/commands.py](https://github.com/vgavro/samsung-mdc/blob/master/samsung_mdc/commands.py) * Detailed [CLI](#usage) help and parameters validation * Run commands async on numerous targets (using asyncio) * TCP and SERIAL mode (for RJ45 and RS232C connection types) * TCP over TLS mode ("Secured Protocol" using PIN) * [script](#script) command for advanced usage * [Python example](#python-example) Not implemented: some more commands (PRs are welcome) Also see: [Samsung MDC Unified](http://www.samsung-mcloud.com/01_Software/04_Tools/MDC/v1235/) - Reference Application (GUI, Windows) with partially implemented functionality. ## Install ``` # using pipx https://pypa.github.io/pipx/ pipx run python-samsung-mdc --help # OR global install/upgrade sudo pip3 install --upgrade python-samsung-mdc samsung-mdc --help # OR local git clone https://github.com/vgavro/samsung-mdc cd ./samsung-mdc python3 -m venv venv ./venv/bin/pip3 install -e ./ ./venv/bin/samsung-mdc --help ``` ### Windows install 1. Install Git && Git Bash: https://git-scm.com/download/win 2. Install Python 3 latest release (tested with 3.9): https://www.python.org/downloads/windows/ 3. Run "Git Bash", type in console: ``` pip3 install --upgrade python-samsung-mdc # NOTE: python "Scripts" folder is not in %PATH% in Windows by default, # so you may want to create alias for Git Bash echo alias samsung-mdc=\'python3 -m samsung_mdc\' >> ~/.bash_profile source ~/.bash_profile # test it samsung-mdc --help ``` ## Usage ``` Usage: samsung-mdc [OPTIONS] TARGET COMMAND [ARGS]... Try 'samsung-mdc --help COMMAND' for command info For multiple targets commands will be running async, so result order may differ. TARGET may be: DISPLAY_ID@IP[:PORT] (default port: 1515, example: 0@192.168.0.10:1515) FILENAME with target list (separated by newline) For serial port connection: DISPLAY_ID@PORT_NAME for Windows (example: 1@COM1) DISPLAY_ID@PORT_PATH (example: 1@/dev/ttyUSB0) We're trying to make autodetection of connection mode by port name, but you may want to use --mode option. Options: --version Show the version and exit. -v, --verbose -m, --mode [auto|tcp|serial] default: auto -p, --pin INTEGER 4-digit PIN for secured TLS connection. If PIN provided, "Secured Protocol" must be enabled on remote device. -t, --timeout FLOAT read/write/connect timeout in seconds (default: 5) (connect can be overridden with separate option) --connect-timeout FLOAT -h, --help Show this message and exit. ``` ### Commands: * [status](#status) `(POWER_STATE VOLUME MUTE_STATE INPUT_SOURCE_STATE PICTURE_ASPECT_STATE N_TIME_NF F_TIME_NF)` * [video](#video) `(CONTRAST BRIGHTNESS SHARPNESS COLOR TINT COLOR_TONE_STATE COLOR_TEMPERATURE _IGNORE)` * [rgb](#rgb) `(CONTRAST BRIGHTNESS COLOR_TONE_STATE COLOR_TEMPERATURE _IGNORE RED_GAIN GREEN_GAIN BLUE_GAIN)` * [serial_number](#serial_number) `(SERIAL_NUMBER)` * [error_status](#error_status) `(LAMP_ERROR_STATE TEMPERATURE_ERROR_STATE BRIGHTNESS_SENSOR_ERROR_STATE INPUT_SOURCE_ERROR_STATE TEMPERATURE FAN_ERROR_STATE)` * [software_version](#software_version) `(SOFTWARE_VERSION)` * [model_number](#model_number) `(MODEL_SPECIES MODEL_CODE TV_SUPPORT)` * [power](#power) `[POWER_STATE]` * [volume](#volume) `[VOLUME]` * [mute](#mute) `[MUTE_STATE]` * [input_source](#input_source) `[INPUT_SOURCE_STATE]` * [picture_aspect](#picture_aspect) `[PICTURE_ASPECT_STATE]` * [screen_mode](#screen_mode) `[SCREEN_MODE_STATE]` * [screen_size](#screen_size) `(INCHES)` * [network_configuration](#network_configuration) `[IP_ADDRESS SUBNET_MASK GATEWAY_ADDRESS DNS_SERVER_ADDRESS]` * [network_mode](#network_mode) `[NETWORK_MODE_STATE]` * [network_ap_config](#network_ap_config) `SSID PASSWORD` * [weekly_restart](#weekly_restart) `[WEEKDAY TIME]` * [magicinfo_channel](#magicinfo_channel) `CHANNEL_NUMBER` * [magicinfo_server](#magicinfo_server) `[MAGICINFO_SERVER_URL]` * [magicinfo_content_orientation](#magicinfo_content_orientation) `[ORIENTATION_MODE_STATE]` * [mdc_connection](#mdc_connection) `[MDC_CONNECTION_TYPE]` * [contrast](#contrast) `[CONTRAST]` * [brightness](#brightness) `[BRIGHTNESS]` * [sharpness](#sharpness) `[SHARPNESS]` * [color](#color) `[COLOR]` * [tint](#tint) `[TINT]` * [h_position](#h_position) `H_POSITION_MOVE_TO` * [v_position](#v_position) `V_POSITION_MOVE_TO` * [auto_power](#auto_power) `[AUTO_POWER_STATE]` * [clear_menu](#clear_menu) * [ir_state](#ir_state) `[IR_STATE]` * [rgb_contrast](#rgb_contrast) `[CONTRAST]` * [rgb_brightness](#rgb_brightness) `[BRIGHTNESS]` * [auto_adjustment_on](#auto_adjustment_on) * [color_tone](#color_tone) `[COLOR_TONE_STATE]` * [color_temperature](#color_temperature) `[HECTO_KELVIN]` * [standby](#standby) `[STANDBY_STATE]` * [auto_lamp](#auto_lamp) `[MAX_TIME MAX_LAMP_VALUE MIN_TIME MIN_LAMP_VALUE]` * [manual_lamp](#manual_lamp) `[LAMP_VALUE]` * [inverse](#inverse) `[INVERSE_STATE]` * [video_wall_mode](#video_wall_mode) `[VIDEO_WALL_MODE]` * [safety_lock](#safety_lock) `[LOCK_STATE]` * [panel_lock](#panel_lock) `[LOCK_STATE]` * [channel_change](#channel_change) `CHANGE_TO` * [volume_change](#volume_change) `CHANGE_TO` * [ticker](#ticker) `[ON_OFF START_TIME END_TIME POS_HORIZ POS_VERTI MOTION_ON_OFF MOTION_DIR MOTION_SPEED FONT_SIZE FOREGROUND_COLOR BACKGROUND_COLOR FOREGROUND_OPACITY BACKGROUND_OPACITY MESSAGE]` * [device_name](#device_name) `(DEVICE_NAME)` * [osd](#osd) `[OSD_ENABLED]` * [picture_mode](#picture_mode) `[PICTURE_MODE_STATE]` * [sound_mode](#sound_mode) `[SOUND_MODE_STATE]` * [all_keys_lock](#all_keys_lock) `[LOCK_STATE]` * [panel_on_time](#panel_on_time) `(MIN10)` * [video_wall_state](#video_wall_state) `[VIDEO_WALL_STATE]` * [video_wall_model](#video_wall_model) `[MODEL SERIAL]` * [model_name](#model_name) `(MODEL_NAME)` * [energy_saving](#energy_saving) `[ENERGY_SAVING_STATE]` * [reset](#reset) `RESET_TARGET` * [osd_type](#osd_type) `[OSD_TYPE OSD_ENABLED]` * [timer_13](#timer_13) `TIMER_ID [ON_TIME ON_ENABLED OFF_TIME OFF_ENABLED REPEAT MANUAL_WEEKDAY VOLUME INPUT_SOURCE_STATE HOLIDAY_APPLY]` * [timer_15](#timer_15) `TIMER_ID [ON_TIME ON_ENABLED OFF_TIME OFF_ENABLED ON_REPEAT ON_MANUAL_WEEKDAY OFF_REPEAT OFF_MANUAL_WEEKDAY VOLUME INPUT_SOURCE_STATE HOLIDAY_APPLY]` * [clock_m](#clock_m) `[DATETIME]` * [holiday_set](#holiday_set) `HOLIDAY_MANAGE START_MONTH START_DAY END_MONTH END_DAY` * [holiday_get](#holiday_get) `[INDEX]` * [virtual_remote](#virtual_remote) `KEY_CODE` * [network_standby](#network_standby) `[NETWORK_STANDBY_STATE]` * [dst](#dst) `[DST_STATE START_MONTH START_WEEK START_WEEKDAY START_TIME END_MONTH END_WEEK END_WEEKDAY END_TIME OFFSET]` * [auto_id_setting](#auto_id_setting) `[AUTO_ID_SETTING_STATE]` * [display_id](#display_id) `DISPLAY_ID_STATE` * [clock_s](#clock_s) `[DATETIME]` * [launcher_play_via](#launcher_play_via) `[PLAY_VIA_MODE]` * [launcher_url_address](#launcher_url_address) `[URL_ADDRESS]` * [osd_menu_orientation](#osd_menu_orientation) `[ORIENTATION_MODE_STATE]` * [osd_source_content_orientation](#osd_source_content_orientation) `[ORIENTATION_MODE_STATE]` * [osd_aspect_ratio](#osd_aspect_ratio) `[ASPECT_RATIO_STATE]` * [osd_pip_orientation](#osd_pip_orientation) `[ORIENTATION_MODE_STATE]` * [osd_menu_size](#osd_menu_size) `[MENU_SIZE_STATE]` * [auto_source_switch](#auto_source_switch) `[AUTO_SOURCE_SWITCH_STATE]` * [auto_source](#auto_source) `[PRIMARY_SOURCE_RECOVERY PRIMARY_SOURCE SECONDARY_SOURCE]` * [panel](#panel) `[PANEL_STATE]` * [screen_mute](#screen_mute) `[SCREEN_MUTE_STATUS]` * [script](#script) `[OPTIONS] SCRIPT_FILE` * [raw](#raw) `[OPTIONS] COMMAND [DATA]` #### status ``` Usage: samsung-mdc [OPTIONS] TARGET status Get the device various state like power, volume, sound mute, input source, picture aspect ratio. Note: For no audio models volume and mute returns 0xFF (255). N_TIME_NF, F_TIME_NF: OnTime/OffTime ON/OFF value (old type timer, now it's always 0x00). Data: POWER_STATE OFF | ON | REBOOT VOLUME int (0-100) MUTE_STATE OFF | ON | NONE INPUT_SOURCE_STATE NONE | S_VIDEO | COMPONENT | AV | AV2 | SCART1 | DVI | PC | BNC | DVI_VIDEO | MAGIC_INFO | HDMI1 | HDMI1_PC | HDMI2 | HDMI2_PC | DISPLAY_PORT_1 | DISPLAY_PORT_2 | DISPLAY_PORT_3 | RF_TV | HDMI3 | HDMI3_PC | HDMI4 | HDMI4_PC | TV_DTV | PLUG_IN_MODE | HD_BASE_T | OCM | MEDIA_MAGIC_INFO_S | WIDI_SCREEN_MIRRORING | INTERNAL_USB | URL_LAUNCHER | IWB | WEB_BROWSER | REMOTE_WORKSPACE PICTURE_ASPECT_STATE PC_16_9 | PC_4_3 | PC_ORIGINAL_RATIO | PC_21_9 | PC_CUSTOM | VIDEO_AUTO_WIDE | VIDEO_16_9 | VIDEO_ZOOM | VIDEO_ZOOM_1 | VIDEO_ZOOM_2 | VIDEO_SCREEN_FIT | VIDEO_4_3 | VIDEO_WIDE_FIT | VIDEO_CUSTOM | VIDEO_SMART_VIEW_1 | VIDEO_SMART_VIEW_2 | VIDEO_WIDE_ZOOM | VIDEO_21_9 N_TIME_NF int F_TIME_NF int ``` #### video ``` Usage: samsung-mdc [OPTIONS] TARGET video Data: CONTRAST int (0-100) BRIGHTNESS int (0-100) SHARPNESS int (0-100) COLOR int (0-100) TINT int (0-100) COLOR_TONE_STATE COOL_2 | COOL_1 | NORMAL | WARM_1 | WARM_2 | OFF COLOR_TEMPERATURE int _IGNORE int (0-0) ``` #### rgb ``` Usage: samsung-mdc [OPTIONS] TARGET rgb Data: CONTRAST int (0-100) BRIGHTNESS int (0-100) COLOR_TONE_STATE COOL_2 | COOL_1 | NORMAL | WARM_1 | WARM_2 | OFF COLOR_TEMPERATURE int _IGNORE int (0-0) RED_GAIN int GREEN_GAIN int BLUE_GAIN int ``` #### serial_number ``` Usage: samsung-mdc [OPTIONS] TARGET serial_number Data: SERIAL_NUMBER str ``` #### error_status ``` Usage: samsung-mdc [OPTIONS] TARGET error_status Data: LAMP_ERROR_STATE NORMAL | ERROR TEMPERATURE_ERROR_STATE NORMAL | ERROR BRIGHTNESS_SENSOR_ERROR_STATE NONE | ERROR | NORMAL INPUT_SOURCE_ERROR_STATE NORMAL | ERROR | INVALID TEMPERATURE int FAN_ERROR_STATE NORMAL | ERROR | NONE ``` #### software_version ``` Usage: samsung-mdc [OPTIONS] TARGET software_version Data: SOFTWARE_VERSION str ``` #### model_number ``` Usage: samsung-mdc [OPTIONS] TARGET model_number Data: MODEL_SPECIES PDP | LCD | DLP | LED | CRT | OLED MODEL_CODE int TV_SUPPORT SUPPORTED | NOT_SUPPORTED ``` #### power ``` Usage: samsung-mdc [OPTIONS] TARGET power [POWER_STATE] Data: POWER_STATE OFF | ON | REBOOT ``` #### volume ``` Usage: samsung-mdc [OPTIONS] TARGET volume [VOLUME] Data: VOLUME int (0-100) ``` #### mute ``` Usage: samsung-mdc [OPTIONS] TARGET mute [MUTE_STATE] Data: MUTE_STATE OFF | ON | NONE ``` #### input_source ``` Usage: samsung-mdc [OPTIONS] TARGET input_source [INPUT_SOURCE_STATE] Get/Set the device source which is shown on the screen. DVI_VIDEO, HDMI1_PC, HDMI2_PC, HDMI3_PC, HDMI4_PC: get only. URL_LAUNCHER, MAGIC_INFO, TV or some ports require support by model. On TIMER functions, Do not use WIDI_SCREEN_MIRRORING. Data: INPUT_SOURCE_STATE NONE | S_VIDEO | COMPONENT | AV | AV2 | SCART1 | DVI | PC | BNC | DVI_VIDEO | MAGIC_INFO | HDMI1 | HDMI1_PC | HDMI2 | HDMI2_PC | DISPLAY_PORT_1 | DISPLAY_PORT_2 | DISPLAY_PORT_3 | RF_TV | HDMI3 | HDMI3_PC | HDMI4 | HDMI4_PC | TV_DTV | PLUG_IN_MODE | HD_BASE_T | OCM | MEDIA_MAGIC_INFO_S | WIDI_SCREEN_MIRRORING | INTERNAL_USB | URL_LAUNCHER | IWB | WEB_BROWSER | REMOTE_WORKSPACE ``` #### picture_aspect ``` Usage: samsung-mdc [OPTIONS] TARGET picture_aspect [PICTURE_ASPECT_STATE] Get/Set the device picture size (aspect ratio). Working Condition: Will not work with VIDEO_WALL_STATE is ON. Note: Some of the image sizes are not supported depending on input signals. Data: PICTURE_ASPECT_STATE PC_16_9 | PC_4_3 | PC_ORIGINAL_RATIO | PC_21_9 | PC_CUSTOM | VIDEO_AUTO_WIDE | VIDEO_16_9 | VIDEO_ZOOM | VIDEO_ZOOM_1 | VIDEO_ZOOM_2 | VIDEO_SCREEN_FIT | VIDEO_4_3 | VIDEO_WIDE_FIT | VIDEO_CUSTOM | VIDEO_SMART_VIEW_1 | VIDEO_SMART_VIEW_2 | VIDEO_WIDE_ZOOM | VIDEO_21_9 ``` #### screen_mode ``` Usage: samsung-mdc [OPTIONS] TARGET screen_mode [SCREEN_MODE_STATE] Data: SCREEN_MODE_STATE MODE_16_9 | MODE_ZOOM | MODE_4_3 | MODE_WIDE_ZOOM ``` #### screen_size ``` Usage: samsung-mdc [OPTIONS] TARGET screen_size Data: INCHES int (0-255) ``` #### network_configuration ``` Usage: samsung-mdc [OPTIONS] TARGET network_configuration [IP_ADDRESS SUBNET_MASK GATEWAY_ADDRESS DNS_SERVER_ADDRESS] Data: IP_ADDRESS IP address SUBNET_MASK IP address GATEWAY_ADDRESS IP address DNS_SERVER_ADDRESS IP address ``` #### network_mode ``` Usage: samsung-mdc [OPTIONS] TARGET network_mode [NETWORK_MODE_STATE] Data: NETWORK_MODE_STATE DYNAMIC | STATIC ``` #### network_ap_config ``` Usage: samsung-mdc [OPTIONS] TARGET network_ap_config SSID PASSWORD Add new SSID info to device connection history with its password. Note: device may change network and response may not be received. Data: SSID str PASSWORD str ``` #### weekly_restart ``` Usage: samsung-mdc [OPTIONS] TARGET weekly_restart [WEEKDAY TIME] Data: WEEKDAY list(,) SUN | SAT | FRI | THU | WED | TUE | MON TIME time (format: %H:%M) ``` #### magicinfo_channel ``` Usage: samsung-mdc [OPTIONS] TARGET magicinfo_channel CHANNEL_NUMBER Set MagicInfo Channel by Direct Channel Number which is used by MagicInfo S Player. Data: CHANNEL_NUMBER int ``` #### magicinfo_server ``` Usage: samsung-mdc [OPTIONS] TARGET magicinfo_server [MAGICINFO_SERVER_URL] MagicInfo Server URL. Example: "http://example.com:80" Data: MAGICINFO_SERVER_URL str ``` #### magicinfo_content_orientation ``` Usage: samsung-mdc [OPTIONS] TARGET magicinfo_content_orientation [ORIENTATION_MODE_STATE] Data: ORIENTATION_MODE_STATE LANDSCAPE_0 | PORTRAIT_270 | LANDSCAPE_180 | PORTRAIT_90 ``` #### mdc_connection ``` Usage: samsung-mdc [OPTIONS] TARGET mdc_connection [MDC_CONNECTION_TYPE] Note: Depends on the product specification, if it is set as RJ45 then serial MDC will not work. Data: MDC_CONNECTION_TYPE RS232C | RJ45 ``` #### contrast ``` Usage: samsung-mdc [OPTIONS] TARGET contrast [CONTRAST] Data: CONTRAST int (0-100) ``` #### brightness ``` Usage: samsung-mdc [OPTIONS] TARGET brightness [BRIGHTNESS] Data: BRIGHTNESS int (0-100) ``` #### sharpness ``` Usage: samsung-mdc [OPTIONS] TARGET sharpness [SHARPNESS] Data: SHARPNESS int (0-100) ``` #### color ``` Usage: samsung-mdc [OPTIONS] TARGET color [COLOR] Data: COLOR int (0-100) ``` #### tint ``` Usage: samsung-mdc [OPTIONS] TARGET tint [TINT] Control the device tint. Adjust the ratio of green to red tint level. Red: TINT value, Green: ( 100 - TINT ) value. Note: Tint could only be set in 50 Steps (0, 2, 4, 6... 100). Data: TINT int (0-100) ``` #### h_position ``` Usage: samsung-mdc [OPTIONS] TARGET h_position H_POSITION_MOVE_TO Data: H_POSITION_MOVE_TO LEFT | RIGHT ``` #### v_position ``` Usage: samsung-mdc [OPTIONS] TARGET v_position V_POSITION_MOVE_TO Data: V_POSITION_MOVE_TO UP | DOWN ``` #### auto_power ``` Usage: samsung-mdc [OPTIONS] TARGET auto_power [AUTO_POWER_STATE] Data: AUTO_POWER_STATE OFF | ON ``` #### clear_menu ``` Usage: samsung-mdc [OPTIONS] TARGET clear_menu ``` #### ir_state ``` Usage: samsung-mdc [OPTIONS] TARGET ir_state [IR_STATE] Enables/disables IR (Infrared) receiving function (Remote Control). Working Condition: * Can operate regardless of whether power is ON/OFF. (If DPMS Situation in LFD, it operate Remocon regardless of set value). Data: IR_STATE DISABLED | ENABLED ``` #### rgb_contrast ``` Usage: samsung-mdc [OPTIONS] TARGET rgb_contrast [CONTRAST] Data: CONTRAST int (0-100) ``` #### rgb_brightness ``` Usage: samsung-mdc [OPTIONS] TARGET rgb_brightness [BRIGHTNESS] Data: BRIGHTNESS int (0-100) ``` #### auto_adjustment_on ``` Usage: samsung-mdc [OPTIONS] TARGET auto_adjustment_on ``` #### color_tone ``` Usage: samsung-mdc [OPTIONS] TARGET color_tone [COLOR_TONE_STATE] Data: COLOR_TONE_STATE COOL_2 | COOL_1 | NORMAL | WARM_1 | WARM_2 | OFF ``` #### color_temperature ``` Usage: samsung-mdc [OPTIONS] TARGET color_temperature [HECTO_KELVIN] Color temperature function. Unit is hectoKelvin (hK) (x*100 Kelvin) (example: 28 = 2800K). Supported values - 28, 30, 35, 40... 160. For older models: 0-10=(x*100K + 5000K), 253=2800K, 254=3000K, 255=4000K Data: HECTO_KELVIN int ``` #### standby ``` Usage: samsung-mdc [OPTIONS] TARGET standby [STANDBY_STATE] Data: STANDBY_STATE OFF | ON | AUTO ``` #### auto_lamp ``` Usage: samsung-mdc [OPTIONS] TARGET auto_lamp [MAX_TIME MAX_LAMP_VALUE MIN_TIME MIN_LAMP_VALUE] Auto Lamp function (backlight). Note: When Manual Lamp Control is on, Auto Lamp Control will automatically turn off. Data: MAX_TIME time (format: %H:%M) MAX_LAMP_VALUE int (0-100) MIN_TIME time (format: %H:%M) MIN_LAMP_VALUE int (0-100) ``` #### manual_lamp ``` Usage: samsung-mdc [OPTIONS] TARGET manual_lamp [LAMP_VALUE] Manual Lamp function (backlight). Note: When Auto Lamp Control is on, Manual Lamp Control will automatically turn off. Data: LAMP_VALUE int (0-100) ``` #### inverse ``` Usage: samsung-mdc [OPTIONS] TARGET inverse [INVERSE_STATE] Data: INVERSE_STATE OFF | ON ``` #### video_wall_mode ``` Usage: samsung-mdc [OPTIONS] TARGET video_wall_mode [VIDEO_WALL_MODE] Get/Set the device in aspect ratio of the video wall. FULL: stretch input source to fill display NATURAL: Keep aspect ratio of input source; do not fill display. Note: Needs VIDEO_WALL_STATE to be ON. Data: VIDEO_WALL_MODE NATURAL | FULL ``` #### safety_lock ``` Usage: samsung-mdc [OPTIONS] TARGET safety_lock [LOCK_STATE] Data: LOCK_STATE OFF | ON ``` #### panel_lock ``` Usage: samsung-mdc [OPTIONS] TARGET panel_lock [LOCK_STATE] Data: LOCK_STATE OFF | ON ``` #### channel_change ``` Usage: samsung-mdc [OPTIONS] TARGET channel_change CHANGE_TO Data: CHANGE_TO UP | DOWN ``` #### volume_change ``` Usage: samsung-mdc [OPTIONS] TARGET volume_change CHANGE_TO Data: CHANGE_TO UP | DOWN ``` #### ticker ``` Usage: samsung-mdc [OPTIONS] TARGET ticker [ON_OFF START_TIME END_TIME POS_HORIZ POS_VERTI MOTION_ON_OFF MOTION_DIR MOTION_SPEED FONT_SIZE FOREGROUND_COLOR BACKGROUND_COLOR FOREGROUND_OPACITY BACKGROUND_OPACITY MESSAGE] Get/Set the device ticker. (Show text message overlay on the screen) Note: POS_HORIZ or POS_VERT are NONE in GET response if unsupported by the display. Data: ON_OFF bool START_TIME time (format: %H:%M) END_TIME time (format: %H:%M) POS_HORIZ CENTER | LEFT | RIGHT | NONE POS_VERTI MIDDLE | TOP | BOTTOM | NONE MOTION_ON_OFF bool MOTION_DIR LEFT | RIGHT | UP | DOWN MOTION_SPEED NORMAL | SLOW | FAST FONT_SIZE STANDARD | SMALL | LARGE FOREGROUND_COLOR BLACK | WHITE | RED | GREEN | BLUE | YELLOW | MAGENTA | CYAN BACKGROUND_COLOR BLACK | WHITE | RED | GREEN | BLUE | YELLOW | MAGENTA | CYAN FOREGROUND_OPACITY FLASHING | FLASH_ALL | OFF BACKGROUND_OPACITY SOLID | TRANSPARENT | TRANSLUCENT | UNKNOWN MESSAGE str ``` #### device_name ``` Usage: samsung-mdc [OPTIONS] TARGET device_name It reads the device name which user set up in network. Shows the information about entered device name. Data: DEVICE_NAME str ``` #### osd ``` Usage: samsung-mdc [OPTIONS] TARGET osd [OSD_ENABLED] Turns OSD (On-screen display) on/off. Data: OSD_ENABLED bool ``` #### picture_mode ``` Usage: samsung-mdc [OPTIONS] TARGET picture_mode [PICTURE_MODE_STATE] Data: PICTURE_MODE_STATE DYNAMIC | STANDARD | MOVIE | CUSTOM_TV | NATURAL | CALIBRATION_TV | ENTERTAIN | INTERNET | TEXT | CUSTOM | ADVERTISEMENT | INFORMATION | CALIBRATION | SHOP_MALL_VIDEO | SHOP_MALL_TEXT | OFFICE_SCHOOL_VIDEO | OFFICE_SCHOOL_TEXT | TERMINAL_STATION_VIDEO | TERMINAL_STATION_TEXT | VIDEO_WALL_VIDEO | VIDEO_WALL_TEXT | HDR_PLUS | OFF | RESERVED_OTHER ``` #### sound_mode ``` Usage: samsung-mdc [OPTIONS] TARGET sound_mode [SOUND_MODE_STATE] Data: SOUND_MODE_STATE STANDARD | MUSIC | MOVIE | SPEECH | CUSTOM | AMPLIFY | OPTIMIZED ``` #### all_keys_lock ``` Usage: samsung-mdc [OPTIONS] TARGET all_keys_lock [LOCK_STATE] Turns both REMOCON and Panel Key Lock function on/off. Note: Can operate regardless of whether power is on/off. Data: LOCK_STATE OFF | ON ``` #### panel_on_time ``` Usage: samsung-mdc [OPTIONS] TARGET panel_on_time Get the device panel on total time. Return value increased every 10 mins. To get hours use "MIN10 / 6". Data: MIN10 int ``` #### video_wall_state ``` Usage: samsung-mdc [OPTIONS] TARGET video_wall_state [VIDEO_WALL_STATE] Get/Set the device in video wall state. This will split the primary input source into smaller N number of squares and display them instead. Note: The device needs to be capable of this operation. Usually a primary high resolution source signal is daisy chained to lower resolution displays in a video wall using HDMI/DP. Data: VIDEO_WALL_STATE OFF | ON ``` #### video_wall_model ``` Usage: samsung-mdc [OPTIONS] TARGET video_wall_model [MODEL SERIAL] Get/Set video wall model. MODEL: Size of the wall in (x, y) coordinates; ie. "2,2" or "4,1" SERIAL: Serial number - position of the display in the video wall, counting from the first display. Note: Needs VIDEO_WALL_STATE to be ON. Data: MODEL Video Wall model (format: X,Y eg. 4,5) SERIAL int (1-255) ``` #### model_name ``` Usage: samsung-mdc [OPTIONS] TARGET model_name Data: MODEL_NAME str ``` #### energy_saving ``` Usage: samsung-mdc [OPTIONS] TARGET energy_saving [ENERGY_SAVING_STATE] Data: ENERGY_SAVING_STATE OFF | LOW | MEDIUM | HIGH | PICTURE_OFF ``` #### reset ``` Usage: samsung-mdc [OPTIONS] TARGET reset RESET_TARGET Data: RESET_TARGET PICTURE | SOUND | SETUP | ALL | SCREEN_DISPLAY ``` #### osd_type ``` Usage: samsung-mdc [OPTIONS] TARGET osd_type [OSD_TYPE OSD_ENABLED] Turns OSD (On-screen display) specific message types on/off. Data: OSD_TYPE SOURCE | NOT_OPTIMUM_MODE | NO_SIGNAL | MDC | SCHEDULE_CHANNEL OSD_ENABLED bool ``` #### timer_13 ``` Usage: samsung-mdc [OPTIONS] TARGET timer_13 TIMER_ID [ON_TIME ON_ENABLED OFF_TIME OFF_ENABLED REPEAT MANUAL_WEEKDAY VOLUME INPUT_SOURCE_STATE HOLIDAY_APPLY] Integrated timer function (13 data-length version). Note: This depends on product and will not work on newer versions. Data: TIMER_ID int (1-7) ON_TIME time (format: %H:%M) ON_ENABLED bool OFF_TIME time (format: %H:%M) OFF_ENABLED bool REPEAT ONCE | EVERYDAY | MON_FRI | MON_SAT | SAT_SUN | MANUAL_WEEKDAY MANUAL_WEEKDAY list(,) SUN | MON | TUE | WED | THU | FRI | SAT VOLUME int (0-100) INPUT_SOURCE_STATE NONE | S_VIDEO | COMPONENT | AV | AV2 | SCART1 | DVI | PC | BNC | DVI_VIDEO | MAGIC_INFO | HDMI1 | HDMI1_PC | HDMI2 | HDMI2_PC | DISPLAY_PORT_1 | DISPLAY_PORT_2 | DISPLAY_PORT_3 | RF_TV | HDMI3 | HDMI3_PC | HDMI4 | HDMI4_PC | TV_DTV | PLUG_IN_MODE | HD_BASE_T | OCM | MEDIA_MAGIC_INFO_S | WIDI_SCREEN_MIRRORING | INTERNAL_USB | URL_LAUNCHER | IWB | WEB_BROWSER | REMOTE_WORKSPACE HOLIDAY_APPLY DONT_APPLY_BOTH | APPLY_BOTH | ON_TIMER_ONLY_APPLY | OFF_TIMER_ONLY_APPLY ``` #### timer_15 ``` Usage: samsung-mdc [OPTIONS] TARGET timer_15 TIMER_ID [ON_TIME ON_ENABLED OFF_TIME OFF_ENABLED ON_REPEAT ON_MANUAL_WEEKDAY OFF_REPEAT OFF_MANUAL_WEEKDAY VOLUME INPUT_SOURCE_STATE HOLIDAY_APPLY] Integrated timer function (15 data-length version). Note: This depends on product and will not work on older versions. ON_TIME/OFF_TIME: turn ON/OFF display at specific time of day ON_ACTIVE/OFF_ACTIVE: if timer is not active, values are ignored, so there may be only OFF timer, ON timer, or both. REPEAT: On which day timer is enabled (combined with HOLIDAY_APPLY and MANUAL_WEEKDAY) Data: TIMER_ID int (1-7) ON_TIME time (format: %H:%M) ON_ENABLED bool OFF_TIME time (format: %H:%M) OFF_ENABLED bool ON_REPEAT ONCE | EVERYDAY | MON_FRI | MON_SAT | SAT_SUN | MANUAL_WEEKDAY ON_MANUAL_WEEKDAY list(,) SUN | MON | TUE | WED | THU | FRI | SAT OFF_REPEAT ONCE | EVERYDAY | MON_FRI | MON_SAT | SAT_SUN | MANUAL_WEEKDAY OFF_MANUAL_WEEKDAY list(,) SUN | MON | TUE | WED | THU | FRI | SAT VOLUME int (0-100) INPUT_SOURCE_STATE NONE | S_VIDEO | COMPONENT | AV | AV2 | SCART1 | DVI | PC | BNC | DVI_VIDEO | MAGIC_INFO | HDMI1 | HDMI1_PC | HDMI2 | HDMI2_PC | DISPLAY_PORT_1 | DISPLAY_PORT_2 | DISPLAY_PORT_3 | RF_TV | HDMI3 | HDMI3_PC | HDMI4 | HDMI4_PC | TV_DTV | PLUG_IN_MODE | HD_BASE_T | OCM | MEDIA_MAGIC_INFO_S | WIDI_SCREEN_MIRRORING | INTERNAL_USB | URL_LAUNCHER | IWB | WEB_BROWSER | REMOTE_WORKSPACE HOLIDAY_APPLY DONT_APPLY_BOTH | APPLY_BOTH | ON_TIMER_ONLY_APPLY | OFF_TIMER_ONLY_APPLY ``` #### clock_m ``` Usage: samsung-mdc [OPTIONS] TARGET clock_m [DATETIME] Current time function (minute precision). Note: This is for models developed until 2013. For newer models see CLOCK_S function (seconds precision). Data: DATETIME datetime (format: %Y-%m-%dT%H:%M / %Y-%m-%d %H:%M) ``` #### holiday_set ``` Usage: samsung-mdc [OPTIONS] TARGET holiday_set HOLIDAY_MANAGE START_MONTH START_DAY END_MONTH END_DAY Add/Delete the device holiday schedule with the holiday schedule itself start month/day and end month/day. Note: On DELETE_ALL all parameters should be 0x00. Data: HOLIDAY_MANAGE ADD | DELETE | DELETE_ALL START_MONTH int (0-12) START_DAY int (0-31) END_MONTH int (0-12) END_DAY int (0-31) ``` #### holiday_get ``` Usage: samsung-mdc [OPTIONS] TARGET holiday_get [INDEX] Get the device holiday schedule. If INDEX is not specified, returns total number of Holiday Information. Data: INDEX int Response extra: START_MONTH int START_DAY int END_MONTH int END_DAY int ``` #### virtual_remote ``` Usage: samsung-mdc [OPTIONS] TARGET virtual_remote KEY_CODE This function support that MDC command can work same as remote control. Note: In a certain model, 0x79 CONTENT key works as HOME and 0x1F DISPLAY key works as INFO. Data: KEY_CODE KEY_SOURCE | KEY_POWER | KEY_1 | KEY_2 | KEY_3 | KEY_VOLUME_UP | KEY_4 | KEY_5 | KEY_6 | KEY_VOLUME_DOWN | KEY_7 | KEY_8 | KEY_9 | KEY_MUTE | KEY_CHANNEL_DOWN | KEY_0 | KEY_CHANNEL_UP | KEY_GREEN | KEY_YELLOW | KEY_CYAN | KEY_MENU | KEY_DISPLAY | KEY_DIGIT | KEY_PIP_TV_VIDEO | KEY_EXIT | KEY_MAGICINFO | KEY_REW | KEY_STOP | KEY_PLAY | KEY_FF | KEY_PAUSE | KEY_TOOLS | KEY_RETURN | KEY_MAGICINFO_LITE | KEY_CURSOR_UP | KEY_CURSOR_DOWN | KEY_CURSOR_RIGHT | KEY_CURSOR_LEFT | KEY_ENTER | KEY_RED | KEY_LOCK | KEY_CONTENT | DISCRET_POWER_OFF | KEY_3D ``` #### network_standby ``` Usage: samsung-mdc [OPTIONS] TARGET network_standby [NETWORK_STANDBY_STATE] Data: NETWORK_STANDBY_STATE OFF | ON ``` #### dst ``` Usage: samsung-mdc [OPTIONS] TARGET dst [DST_STATE START_MONTH START_WEEK START_WEEKDAY START_TIME END_MONTH END_WEEK END_WEEKDAY END_TIME OFFSET] Data: DST_STATE OFF | AUTO | MANUAL START_MONTH JAN | FEB | MAR | APR | MAY | JUN | JUL | AUG | SEP | OCT | NOV | DEC START_WEEK WEEK_1 | WEEK_2 | WEEK_3 | WEEK_4 | WEEK_LAST START_WEEKDAY SUN | MON | TUE | WED | THU | FRI | SAT START_TIME time (format: %H:%M) END_MONTH JAN | FEB | MAR | APR | MAY | JUN | JUL | AUG | SEP | OCT | NOV | DEC END_WEEK WEEK_1 | WEEK_2 | WEEK_3 | WEEK_4 | WEEK_LAST END_WEEKDAY SUN | MON | TUE | WED | THU | FRI | SAT END_TIME time (format: %H:%M) OFFSET PLUS_1_00 | PLUS_2_00 Response extra: TUNER_SUPPORT bool ``` #### auto_id_setting ``` Usage: samsung-mdc [OPTIONS] TARGET auto_id_setting [AUTO_ID_SETTING_STATE] Data: AUTO_ID_SETTING_STATE START | END ``` #### display_id ``` Usage: samsung-mdc [OPTIONS] TARGET display_id DISPLAY_ID_STATE Data: DISPLAY_ID_STATE OFF | ON ``` #### clock_s ``` Usage: samsung-mdc [OPTIONS] TARGET clock_s [DATETIME] Current time function (second precision). Note: This is for models developed after 2013. For older models see CLOCK_M function (minute precision). Data: DATETIME datetime (format: %Y-%m-%dT%H:%M:%S / %Y-%m-%d %H:%M:%S) ``` #### launcher_play_via ``` Usage: samsung-mdc [OPTIONS] TARGET launcher_play_via [PLAY_VIA_MODE] Data: PLAY_VIA_MODE MAGIC_INFO | URL_LAUNCHER | MAGIC_IWB ``` #### launcher_url_address ``` Usage: samsung-mdc [OPTIONS] TARGET launcher_url_address [URL_ADDRESS] Data: URL_ADDRESS str ``` #### osd_menu_orientation ``` Usage: samsung-mdc [OPTIONS] TARGET osd_menu_orientation [ORIENTATION_MODE_STATE] Data: ORIENTATION_MODE_STATE LANDSCAPE_0 | PORTRAIT_270 | LANDSCAPE_180 | PORTRAIT_90 ``` #### osd_source_content_orientation ``` Usage: samsung-mdc [OPTIONS] TARGET osd_source_content_orientation [ORIENTATION_MODE_STATE] Data: ORIENTATION_MODE_STATE LANDSCAPE_0 | PORTRAIT_270 | LANDSCAPE_180 | PORTRAIT_90 ``` #### osd_aspect_ratio ``` Usage: samsung-mdc [OPTIONS] TARGET osd_aspect_ratio [ASPECT_RATIO_STATE] Get/Set the device aspect ratio under portrait mode which set the rotated screen to be full or original. Data: ASPECT_RATIO_STATE FULL_SCREEN | ORIGINAL ``` #### osd_pip_orientation ``` Usage: samsung-mdc [OPTIONS] TARGET osd_pip_orientation [ORIENTATION_MODE_STATE] Data: ORIENTATION_MODE_STATE LANDSCAPE_0 | PORTRAIT_270 | LANDSCAPE_180 | PORTRAIT_90 ``` #### osd_menu_size ``` Usage: samsung-mdc [OPTIONS] TARGET osd_menu_size [MENU_SIZE_STATE] Data: MENU_SIZE_STATE ORIGINAL | MEDIUM | SMALL ``` #### auto_source_switch ``` Usage: samsung-mdc [OPTIONS] TARGET auto_source_switch [AUTO_SOURCE_SWITCH_STATE] Data: AUTO_SOURCE_SWITCH_STATE OFF | ON ``` #### auto_source ``` Usage: samsung-mdc [OPTIONS] TARGET auto_source [PRIMARY_SOURCE_RECOVERY PRIMARY_SOURCE SECONDARY_SOURCE] Data: PRIMARY_SOURCE_RECOVERY OFF | ON PRIMARY_SOURCE NONE | S_VIDEO | COMPONENT | AV | AV2 | SCART1 | DVI | PC | BNC | DVI_VIDEO | MAGIC_INFO | HDMI1 | HDMI1_PC | HDMI2 | HDMI2_PC | DISPLAY_PORT_1 | DISPLAY_PORT_2 | DISPLAY_PORT_3 | RF_TV | HDMI3 | HDMI3_PC | HDMI4 | HDMI4_PC | TV_DTV | PLUG_IN_MODE | HD_BASE_T | OCM | MEDIA_MAGIC_INFO_S | WIDI_SCREEN_MIRRORING | INTERNAL_USB | URL_LAUNCHER | IWB | WEB_BROWSER | REMOTE_WORKSPACE SECONDARY_SOURCE NONE | S_VIDEO | COMPONENT | AV | AV2 | SCART1 | DVI | PC | BNC | DVI_VIDEO | MAGIC_INFO | HDMI1 | HDMI1_PC | HDMI2 | HDMI2_PC | DISPLAY_PORT_1 | DISPLAY_PORT_2 | DISPLAY_PORT_3 | RF_TV | HDMI3 | HDMI3_PC | HDMI4 | HDMI4_PC | TV_DTV | PLUG_IN_MODE | HD_BASE_T | OCM | MEDIA_MAGIC_INFO_S | WIDI_SCREEN_MIRRORING | INTERNAL_USB | URL_LAUNCHER | IWB | WEB_BROWSER | REMOTE_WORKSPACE ``` #### panel ``` Usage: samsung-mdc [OPTIONS] TARGET panel [PANEL_STATE] Data: PANEL_STATE ON | OFF ``` #### screen_mute ``` Usage: samsung-mdc [OPTIONS] TARGET screen_mute [SCREEN_MUTE_STATUS] Data: SCREEN_MUTE_STATUS ON | OFF ``` #### script ``` Usage: samsung-mdc [OPTIONS] TARGET script [OPTIONS] SCRIPT_FILE Script file with commands to execute. Commands for multiple targets will be running async, but commands order is preserved for device (and is running on same connection), exit on first fail unless retry options provided. You may use jinja2 templating engine to {% include "other_script" %} or {{ VAR_KEY }} rendering in combination with --var VAR_KEY VAR_VALUE options. It's highly recommended to use sleep option for virtual_remote! Additional commands: sleep SECONDS (FLOAT, --sleep option for this command is ignored) disconnect Format: command1 [ARGS]... command2 [ARGS]... Example: samsung-mdc ./targets.txt script -s 3 -r 1 -v KEY enter ./commands.txt # commands.txt content power on sleep 5 clear_menu virtual_remote key_menu virtual_remote key_down virtual_remote {{ KEY }} clear_menu Arguments: script_file Text file with commands, separated by newline. Options: -s, --sleep FLOAT Pause between commands (seconds) --retry-command INTEGER Retry command if failed (count) --retry-command-sleep FLOAT Sleep before command retry (seconds) -r, --retry-script INTEGER Retry script if failed (count) --retry-script-sleep FLOAT Sleep before script retry (seconds) --ignore-nak Ignore negative acknowledgement errors -v, --var NAME VALUE Variable "{{ NAME }}" in script will be replaced by VALUE --help Show this message and exit. ``` #### raw ``` Usage: samsung-mdc [OPTIONS] TARGET raw [OPTIONS] COMMAND [DATA] Helper command to send raw data for test purposes. Arguments: command Command and (optionally) subcommand (example: a1 or a1:b2) data Data payload if any (example: a1:b2) ``` ## Troubleshooting ### Finding DISPLAY ID On most devices it's usually `0` or `1`. Some devices may use `255` (0xFF) or `254` (0xFE) as all/any display, but behavior in such cases for more than 1 display is undefined. Display id can be found using remote control: `Home` -> `ID Settings`. ### NAKError If you receive NAK errors on some commands, you may try to: * Ensure that device is powered on and completely loaded * Switch to input source HDMI1 * Reboot device * Reset all settings * Disable MagicINFO * Factory reset (using "Service Menu") ## Python example ```python3 import asyncio from samsung_mdc import MDC async def main(ip, display_id): async with MDC(ip, verbose=True) as mdc: # First argument of command is always display_id status = await mdc.status(display_id) print(status) # Result is always tuple if status[0] != MDC.power.POWER_STATE.ON: # Command arguments are always Sequence (tuple, list) await mdc.power(display_id, [MDC.power.POWER_STATE.ON]) await mdc.close() # Force reconnect on next command await asyncio.sleep(15) await mdc.display_id(display_id, [MDC.display_id.DISPLAY_ID_STATE.ON]) # You may also use names or values instead of enums await mdc.display_id(display_id, ['ON']) # same await mdc.display_id(display_id, [1]) # same # If you see "Connected" and timeout error, try other display_id (0, 1) asyncio.run(main('192.168.0.10', 1)) ``` ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1736112705.0 python-samsung-mdc-1.15.0/python_samsung_mdc.egg-info/SOURCES.txt0000644000175100002000000000101214736575101024205 0ustar00runnerdockerLICENSE README.md setup.cfg setup.py python_samsung_mdc.egg-info/PKG-INFO python_samsung_mdc.egg-info/SOURCES.txt python_samsung_mdc.egg-info/dependency_links.txt python_samsung_mdc.egg-info/entry_points.txt python_samsung_mdc.egg-info/requires.txt python_samsung_mdc.egg-info/top_level.txt samsung_mdc/__init__.py samsung_mdc/__main__.py samsung_mdc/cli.py samsung_mdc/command.py samsung_mdc/commands.py samsung_mdc/connection.py samsung_mdc/exceptions.py samsung_mdc/fields.py samsung_mdc/utils.py samsung_mdc/version.py././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1736112704.0 python-samsung-mdc-1.15.0/python_samsung_mdc.egg-info/dependency_links.txt0000644000175100002000000000000114736575100026374 0ustar00runnerdocker ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1736112704.0 python-samsung-mdc-1.15.0/python_samsung_mdc.egg-info/entry_points.txt0000644000175100002000000000006414736575100025624 0ustar00runnerdocker[console_scripts] samsung-mdc = samsung_mdc.cli:cli ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1736112704.0 python-samsung-mdc-1.15.0/python_samsung_mdc.egg-info/requires.txt0000644000175100002000000000017414736575100024730 0ustar00runnerdockerclick jinja2 pyserial-asyncio [all] pyserial-asyncio [serial] pyserial-asyncio [test] pytest pytest-asyncio nest-asyncio ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1736112704.0 python-samsung-mdc-1.15.0/python_samsung_mdc.egg-info/top_level.txt0000644000175100002000000000001414736575100025053 0ustar00runnerdockersamsung_mdc ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1736112705.0172837 python-samsung-mdc-1.15.0/samsung_mdc/0000755000175100002000000000000014736575101017234 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1736112699.0 python-samsung-mdc-1.15.0/samsung_mdc/__init__.py0000644000175100002000000000111514736575073021353 0ustar00runnerdockerfrom typing import Dict import inspect from .version import __version__ # noqa from .connection import MDCConnection from .command import Command from . import commands class MDC(MDCConnection): _commands: Dict[str, Command] = {} @classmethod def register_command(cls, command): cls._commands[command.name] = command setattr(cls, command.name, command) for name, cls in inspect.getmembers(commands, inspect.isclass): if (issubclass(cls, Command) and cls is not Command and not name.startswith('_')): MDC.register_command(cls()) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1736112699.0 python-samsung-mdc-1.15.0/samsung_mdc/__main__.py0000644000175100002000000000012514736575073021334 0ustar00runnerdockerif __name__ == '__main__': from .cli import cli cli(prog_name='samsung-mdc') ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1736112699.0 python-samsung-mdc-1.15.0/samsung_mdc/cli.py0000644000175100002000000007001514736575073020370 0ustar00runnerdocker# NOTE: This module is hacking some internals of "click" library # and may not work stable in case of click major changes in future versions. # Tested with click==7.1.2 # BEWARE: If you want something flexible and overridable for cli processing, # "click" just may not be your choice... from datetime import time, datetime from enum import Enum import asyncio import re import os.path from sys import argv as sys_argv import platform from traceback import print_exception as _print_exception import click from . import MDC, fields, __version__ from .utils import parse_hex, repr_hex from .exceptions import NAKError def print_exception(exc): # compatibility, not required after Python 3.10 _print_exception(type(exc), exc, exc.__traceback__) def _parse_int(x): return int(x, 16) if x.startswith('0x') else int(x) def _repr(val, root=True): if isinstance(val, list): # quickfix for script command repr # maybe this should better be fixed in create_mdc_call return ' '.join(_repr(x, root) for x in val) if isinstance(val, tuple): return (' ' if root else ',').join(_repr(x, False) for x in val) if isinstance(val, (datetime, time)): return val.isoformat() elif isinstance(val, Enum): return f'<{val.__class__.__name__}.{val.name}:{val.value}>' return str(val) def trim_docstring(docstring): # from https://www.python.org/dev/peps/pep-0257/ if not docstring: return '' # Convert tabs to spaces (following the normal Python rules) # and split into a list of lines: lines = docstring.expandtabs().splitlines() # Determine minimum indentation (first line doesn't count): indent = 1024 for line in lines[1:]: stripped = line.lstrip() if stripped: indent = min(indent, len(line) - len(stripped)) # Remove indentation (first line is special): trimmed = [lines[0].strip()] if indent < 1024: for line in lines[1:]: trimmed.append(line[indent:].rstrip()) # Strip off trailing and leading blank lines: while trimmed and not trimmed[-1]: trimmed.pop() while trimmed and not trimmed[0]: trimmed.pop(0) # Return a single string: return '\n'.join(trimmed) class EnumChoice(click.ParamType): # This is not part of a click because of... WTF? # https://github.com/pallets/click/issues/605 # All proposals in ticket are bad anyway, so impementing # another one... name = 'enum_choice' def __init__(self, enum): self.enum = enum def get_metavar(self, param): return f'{self.enum.__name__}' def get_missing_message(self, param): return "Choose from:\n\t{}.".format(",\n\t".join( self.enum.__members__)) def convert(self, value, param, ctx): if value.upper() in self.enum.__members__: return self.enum[value.upper()] else: # NOTE: Specific part for this project... # not sure how to make it more universal try: value = _parse_int(value) except ValueError: pass if value in [v.value for v in self.enum]: return value missing_message = self.get_missing_message(param) self.fail(f"Invalid choice: {value}\n{missing_message}") def __repr__(self): return f"EnumChoice({self.enum})" class EnumTuple(EnumChoice): name = "enum_tuple" def convert(self, value, param, ctx): if not value: return tuple() convert_ = super().convert return tuple(convert_(v, param, ctx) for v in value.split(',')) def __repr__(self): return f"EnumTuple({self.enum})" class Time(click.ParamType): name = "time" def convert(self, value, param, ctx): try: return time(*map(int, value.split(':'))) except ValueError: self.fail("{} is not a valid time".format(value), param, ctx) def __repr__(self): return "TIME" class ArgumentWithHelp(click.Argument): # Extends Argument with "help" parameter, # so they can be rendered in help same way as options # See ArgumentWithHelpCommandMixin def __init__(self, *args, help=None, **kwargs): super().__init__(*args, **kwargs) self.help = help class ArgumentWithHelpCommandMixin: def __init__(self, *args, help_arguments_label='Arguments', **kwargs): self.help_arguments_label = help_arguments_label super().__init__(*args, **kwargs) def format_arguments(self, ctx, formatter): args = [] for param in self.get_params(ctx): if isinstance(param, click.Argument): help = getattr(param, 'help', None) args.append((param.metavar or param.name, help or '')) if args: with formatter.section(self.help_arguments_label): formatter.write_dl(args) def format_options(self, ctx, formatter): # Override this to format ArgumentWithHelp before options self.format_arguments(ctx, formatter) super().format_options(ctx, formatter) class FixedSubcommand(ArgumentWithHelpCommandMixin, click.Command): # This mixin contains some common overrides and fixes for click # subcommand processing def parse_args(self, ctx, args): # Avoid "Try 'samsung-mdc {command} --help' for help." message # that renders in UsageError, # because it's wrong command because of required group command args # (maybe this will be fixed in future click versions?) try: super().parse_args(ctx, args) except click.UsageError as exc: exc.cmd = None raise def format_usage(self, ctx, formatter): # 1. Invoking this on subcommand doesn't show group required arguments # and options, so Usage is no valid in this case. # 2. Invoking this on wrong context mess things up completely, # but we need it from group context to be able to do # "samsung-mdc --help command1" # NOTE: this works only on 1-st level subcommand, for nesting # you may want to make root parts recursive root_path = ctx.command_path.split()[0] root_command = ctx.parent and ctx.parent.command or ctx.command pieces = click.Command.collect_usage_pieces(root_command, ctx) pieces.append(self.name) pieces.extend(self.collect_usage_pieces(ctx)) formatter.write_usage(root_path, " ".join(pieces)) class Group(click.Group): def get_help_option(self, ctx): # Override this to pass parameters to --help # This is needed to be able to do "--help COMMAND" # without group required arguments def show_help(ctx, param, value): if value and not ctx.resilient_parsing: # Match registered commands and show help for all of them commands = [ ctx.command.commands[name] for name in sys_argv[1:] if name in ctx.command.commands ] if commands: for i, command in enumerate(commands): if i: click.echo() click.echo(f'Command: {command.name}') click.echo(command.get_help(ctx), color=ctx.color) ctx.exit() else: # Show default help if no commands were matched click.echo(ctx.get_help(), color=ctx.color) ctx.exit() return click.Option( ['-h', '--help'], is_flag=True, is_eager=True, expose_value=False, callback=show_help, help="Show this message and exit.", ) def list_commands(self, ctx): # Avoid sorting commands by name, sort by CMD code instead # (as it goes in documentation) def key(c): # not mdc commands ("script") should go last return (hasattr(c, 'mdc_command') and c.mdc_command.get_order() or (1000,)) return [c.name for c in sorted(self.commands.values(), key=key)] class MDCClickCommand(FixedSubcommand): def __init__(self, name, mdc_command, **kwargs): self.mdc_command = mdc_command name = mdc_command.name kwargs['short_help'] = self._get_params_hint() if not mdc_command.SET: kwargs['short_help'] = f'({kwargs["short_help"]})' kwargs['help_arguments_label'] = 'Data' kwargs['help'] = trim_docstring(mdc_command.__doc__) # Registering params from DATA format kwargs.setdefault('params', []) if isinstance(mdc_command.CMD, fields.Field): kwargs['params'].append( self._get_argument_from_mdc_field(mdc_command.CMD, 'cmd')) for i, field in enumerate(mdc_command.DATA): kwargs['params'].append( self._get_argument_from_mdc_field(field, i)) super().__init__(name, **kwargs) def format_arguments(self, ctx, formatter): super().format_arguments(ctx, formatter) if getattr(self.mdc_command, 'RESPONSE_EXTRA', []): args = [ self._get_argument_from_mdc_field(field) for field in self.mdc_command.RESPONSE_EXTRA ] with formatter.section('Response extra'): formatter.write_dl(( param.metavar or param.name, getattr(param, 'help', '') ) for param in args) def _get_argument_from_mdc_field(self, field, ident=None): if isinstance(field, fields.Bitmask): type = EnumTuple(field.enum) help = ('list(,) ' + (' | '.join(field.enum.__members__.keys()))) elif isinstance(field, fields.Enum): type = EnumChoice(field.enum) help = ' | '.join(field.enum.__members__.keys()) elif isinstance(field, fields.DateTime): if field.seconds: formats = [ "%Y-%m-%dT%H:%M:%S", "%Y-%m-%d %H:%M:%S", ] else: formats = [ "%Y-%m-%dT%H:%M", "%Y-%m-%d %H:%M", ] type = click.DateTime(formats) help = f'datetime (format: {" / ".join(formats)})' elif isinstance(field, (fields.Time, fields.Time12H)): type = Time() if isinstance(field, fields.Time12H) or not field.seconds: help = 'time (format: %H:%M)' else: help = 'time (format: %H:%M:%S)' elif isinstance(field, fields.IPAddress): type = str help = 'IP address' elif isinstance(field, fields.VideoWallModel): type = str help = 'Video Wall model (format: X,Y eg. 4,5)' else: type = { fields.Str: str, fields.StrCoded: str, fields.Bool: bool, fields.Int: int, }[field.__class__] help = type.__name__.lower() if type is int and field.range: help += f' ({field.range.start}-{field.range.stop - 1})' return ArgumentWithHelp( [f'data_{ident}' if ident else field.name], metavar=field.name, type=type, help=help) def _get_params_hint(self): params = ' '.join([f.name for f in self.mdc_command.DATA]) if self.mdc_command.GET and self.mdc_command.SET and params: params = f'[{params}]' if isinstance(self.mdc_command.CMD, fields.Field): # parametrized CMD is always required argument params = f'{self.mdc_command.CMD.name} {params}' return params def collect_usage_pieces(self, ctx): # We need ALL arguments to be OR optional, OR required, # so we can't use required on arguments and need to show # it properly in usage string if self.mdc_command.SET: return [self._get_params_hint()] return [] def parse_args(self, ctx, args): # We need ALL arguments to be OR optional, OR required, # so if there is no arguments supplied - this is proper # GET command # Except parametrized CMD - special case for timer, # we have 14 almost identical commands otherwise... if isinstance(self.mdc_command.CMD, fields.Field): if self.mdc_command.GET and len(args) == 1: if '--help' in sys_argv: return super().parse_args(ctx, args) parser = self.make_parser(ctx) opts, args, param_order = parser.parse_args(args=args) for param in self.get_params(ctx): value, args = param.handle_parse_result( ctx, opts, args) break # after first argument ctx.args = args return elif not args and self.mdc_command.GET: ctx.args = args return args try: super().parse_args(ctx, args) except click.UsageError as exc: # Avoid parameters validation on GET-only commands if not self.mdc_command.SET: exc = click.UsageError('Readonly command doesn\'t accept ' 'any arguments', ctx) exc.cmd = None # see FixedSubcommand for reason raise exc raise def create_mdc_call(self, params): args = tuple(params.values()) if isinstance(self.mdc_command.CMD, fields.Field): args = args[0], args[1:] else: args = [args] if args else [] if args and not self.mdc_command.SET: raise click.UsageError('Readonly command doesn\'t accept ' 'any arguments') async def mdc_call(connection, display_id): try: print(f'{display_id}@{connection.target}', _repr(await self.mdc_command(connection, display_id, *args))) except Exception as exc: print(f'{display_id}@{connection.target}', f'{exc.__class__.__name__}: {exc}') raise mdc_call.name = self.name mdc_call.args = args return mdc_call class MDCTargetParamType(click.ParamType): name = 'mdc_target' _win_com_port_regexp = re.compile(r'COM\d+', re.IGNORECASE) # def get_missing_message(self, param): # return param.help def convert_target(self, value, param, ctx): if '@' not in value: self.fail('DISPLAY_ID required (try 0, 1)') display_id, addr = value.split('@') try: display_id = _parse_int(display_id) except ValueError: self.fail( f'Invalid DISPLAY_ID "{display_id}" ' '(int or hex, example: 1, 0x01, 254, 0xFE)') if ':' in addr: ip, port = addr.split(':') try: port = int(port) except ValueError: self.fail(f'Invalid PORT "{port}"') return 'tcp', f'{ip}:{port}', display_id elif ( '/' in addr or addr.startswith('.') or self._win_com_port_regexp.match(addr) ): return 'serial', addr, display_id return 'tcp', addr, display_id def convert(self, value, param, ctx): if '@' in value: return [self.convert_target(value, param, ctx)] elif ( self._win_com_port_regexp.match(value) or value.startswith('/dev/') ): self.fail('Looks like you want to use serial port, ' 'but DISPLAY_ID required (try 0, 1)') else: if not os.path.exists(value): self.fail(f'FILENAME "{value}" does not exist.') data = open(value).read() data = [ (i + 1, line.strip()) for i, line in enumerate(data.split('\n')) if line.strip() and not line.strip().startswith('#') ] targets = [] for lineno, line in data: try: targets.append(self.convert_target(line, param, ctx)) except click.UsageError as exc: exc.message = (f'{value}:{lineno}: "{line}": ' f'{exc.message}') raise if not targets: self.fail( f'FILENAME "{value} is empty.') return targets MAIN_HELP = """ Try 'samsung-mdc --help COMMAND' for command info\n For multiple targets commands will be running async, so result order may differ. TARGET may be: \b DISPLAY_ID@IP[:PORT] (default port: 1515, example: 0@192.168.0.10:1515) FILENAME with target list (separated by newline) \b For serial port connection: DISPLAY_ID@PORT_NAME for Windows (example: 1@COM1) DISPLAY_ID@PORT_PATH (example: 1@/dev/ttyUSB0) We're trying to make autodetection of connection mode by port name, but you may want to use --mode option. """ @click.group(cls=Group, help=MAIN_HELP) @click.version_option(version=__version__) @click.argument('target', metavar='TARGET', type=MDCTargetParamType()) @click.option('-v', '--verbose', is_flag=True, default=False, type=bool) @click.option('-m', '--mode', default='auto', help='default: auto', type=click.Choice(('auto', 'tcp', 'serial'), case_sensitive=False)) @click.option('-p', '--pin', default=None, type=int, help='4-digit PIN for secured TLS connection. ' 'If PIN provided, "Secured Protocol" must be enabled ' 'on remote device.') @click.option( '-t', '--timeout', default=5, type=float, help=( 'read/write/connect timeout in seconds (default: 5) ' '(connect can be overridden with separate option)')) @click.option('--connect-timeout', default=None, type=float) @click.pass_context def cli(ctx, target, verbose, mode, pin, **kwargs): ctx.ensure_object(dict) ctx.obj['targets'] = [( MDC(target, auto_mode if mode == 'auto' else mode, **{'verbose': verbose, 'pin': pin, **kwargs}), display_id ) for auto_mode, target, display_id in target] ctx.obj['verbose'] = verbose def asyncio_run(call, targets, verbose=False): if platform.system() == 'Windows': asyncio.set_event_loop_policy( asyncio.WindowsSelectorEventLoopPolicy()) try: loop = asyncio.get_running_loop() is_running_loop = True except RuntimeError: loop = asyncio.get_event_loop() is_running_loop = False loop.run_until_complete(asyncio.wait([ loop.create_task(call(*target)) for target in targets ])) async def close(connection): try: await connection.close() except Exception as exc: if verbose: print(f'{connection.target}', f'{exc.__class__.__name__}: {exc}') print_exception(exc) # Gracefully close connections connections = [target[0] for target in targets if target[0].is_opened] if connections: loop.run_until_complete(asyncio.wait([ loop.create_task(close(connection)) for connection in connections ])) if not is_running_loop: loop.close() def register_command(command): @click.pass_context def _cmd(ctx, **kwargs): mdc_call = ctx.command.create_mdc_call(kwargs) failed_targets = [] async def call(connection, display_id): try: await mdc_call(connection, display_id) except Exception as exc: failed_targets.append((connection, display_id, exc)) if ctx.obj['verbose']: print_exception(exc) asyncio_run(call, ctx.obj['targets'], ctx.obj['verbose']) if failed_targets: if len(ctx.obj['targets']) > 1: print('Failed targets:', len(failed_targets)) ctx.exit(1) cli.command(cls=MDCClickCommand, mdc_command=command)(_cmd) for command in MDC._commands.values(): register_command(command) SCRIPT_HELP = """ Script file with commands to execute. Commands for multiple targets will be running async, but commands order is preserved for device (and is running on same connection), exit on first fail unless retry options provided. You may use jinja2 templating engine to {% include "other_script" %} or {{ VAR_KEY }} rendering in combination with --var VAR_KEY VAR_VALUE options. It\'s highly recommended to use sleep option for virtual_remote! \b Additional commands: sleep SECONDS (FLOAT, --sleep option for this command is ignored) disconnect \b Format: command1 [ARGS]... command2 [ARGS]... \b Example: samsung-mdc ./targets.txt script -s 3 -r 1 -v KEY enter ./commands.txt # commands.txt content power on sleep 5 clear_menu virtual_remote key_menu virtual_remote key_down virtual_remote {{ KEY }} clear_menu """ @cli.command(help=SCRIPT_HELP, cls=FixedSubcommand) @click.option('-s', '--sleep', default=0, type=float, help='Pause between commands (seconds)') @click.option('--retry-command', default=0, type=int, help='Retry command if failed (count)') @click.option('--retry-command-sleep', default=0, type=float, help='Sleep before command retry (seconds)') @click.option('-r', '--retry-script', default=0, type=int, help='Retry script if failed (count)') @click.option('--retry-script-sleep', default=0, type=float, help='Sleep before script retry (seconds)') @click.option('--ignore-nak', is_flag=True, help='Ignore negative acknowledgement errors') @click.option('--var', '-v', multiple=True, nargs=2, type=(str, str), help='Variable "{{ NAME }}" in script will be replaced by VALUE', metavar='NAME VALUE') @click.argument('script_file', type=click.File(), help='Text file with commands, separated by newline.', cls=ArgumentWithHelp) @click.pass_context def script(ctx, script_file, sleep, retry_command, retry_command_sleep, retry_script, retry_script_sleep, ignore_nak, var): import shlex var = dict(var) retry_command_sleep = retry_command_sleep or sleep retry_script_sleep = retry_script_sleep or sleep script_content = script_file.read() if var or '{{' in script_content or '{%' in script_content: from jinja2 import Environment, FileSystemLoader, StrictUndefined class RelativeEnvironment(Environment): def join_path(self, template, parent): # Allowing include based on path relative to script return os.path.join(os.path.dirname(parent), template) env = RelativeEnvironment(loader=FileSystemLoader(['/', './']), undefined=StrictUndefined) template = env.get_template(script_file.name) script_content = template.render(**var) def fail(lineno, line, reason): raise click.UsageError( f'{script_file.name}:{lineno}:"{line}": {reason}') def create_disconnect(): async def disconnect(connection, display_id): await connection.close() return tuple() disconnect.name = 'disconnect' disconnect.args = [] return disconnect def create_sleep(seconds): async def sleep(connection, display_id): await asyncio.sleep(seconds) return tuple() sleep.name = 'sleep' sleep.args = [seconds] return sleep lines = [ (i + 1, line.strip()) for i, line in enumerate(script_content.splitlines()) ] calls = [] for lineno, line in lines: if not line or line.startswith('#'): continue command, *args = shlex.split(line) command = command.lower() if (command not in cli.commands and command not in ['sleep', 'disconnect']): fail(lineno, line, f'Unknown command: {command}') if command == 'sleep': if len(args) != 1: fail(lineno, line, 'Sleep command accept exactly one argument') try: seconds = float(args[0]) except ValueError as exc: fail(lineno, line, f'Sleep argument must be int/float: {exc}') calls.append(create_sleep(seconds)) elif command == 'disconnect': if len(args): fail(lineno, line, 'Disconnect command does not accept ' 'arguments') calls.append(create_disconnect()) else: ctx.params.clear() command = cli.commands[command] try: command.parse_args(ctx, args) except click.UsageError as exc: fail(lineno, line, str(exc)) calls.append(command.create_mdc_call(ctx.params)) failed_targets = [] async def call(connection, display_id): last_exc = None for retry_script_i in range(retry_script + 1): if retry_script_i and retry_script_sleep: await asyncio.sleep(retry_script_sleep) for command_i, call_ in enumerate(calls): if command_i and call_.name != 'sleep' and sleep: await asyncio.sleep(sleep) for retry_command_i in range(retry_command + 1): if retry_command_i and retry_command_sleep: await asyncio.sleep(retry_command_sleep) if ctx.obj['verbose']: print( f'{display_id}@{connection.target}', f'{retry_script_i}:{command_i}:{retry_command_i}', f'{call_.name} {_repr(call_.args)}') try: await call_(connection, display_id) except Exception as exc: if ignore_nak and isinstance(exc, NAKError): last_exc = None break last_exc = exc else: last_exc = None break if last_exc is not None: break if last_exc is None: break if last_exc is not None: failed_targets.append((connection, display_id, last_exc)) print(f'{display_id}@{connection.target}', f'Script failed indefinitely: {last_exc}') if ctx.obj['verbose']: print_exception(last_exc) asyncio_run(call, ctx.obj['targets'], ctx.obj['verbose']) if failed_targets: if len(ctx.obj['targets']) > 1: print('Failed targets:', len(failed_targets)) ctx.exit(1) @cli.command(help='Helper command to send raw data for test purposes.', cls=FixedSubcommand) @click.argument( 'command', type=str, cls=ArgumentWithHelp, help='Command and (optionally) subcommand (example: a1 or a1:b2)') @click.argument( 'data', type=str, default='', cls=ArgumentWithHelp, help='Data payload if any (example: a1:b2)') @click.pass_context def raw(ctx, command, data): failed_targets = [] async def call(connection, display_id): try: ack, rcmd, resp_data = await connection.send( tuple(parse_hex(command)), display_id, parse_hex(data)) print( f'{display_id}@{connection.target}', 'A' if ack else 'N', repr_hex(rcmd), repr_hex(resp_data) ) except Exception as exc: print(f'{display_id}@{connection.target}', f'{exc.__class__.__name__}: {exc}') failed_targets.append((connection, display_id, exc)) if ctx.obj['verbose']: print_exception(exc) asyncio_run(call, ctx.obj['targets'], ctx.obj['verbose']) if failed_targets: if len(ctx.obj['targets']) > 1: print('Failed targets:', len(failed_targets)) ctx.exit(1) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1736112699.0 python-samsung-mdc-1.15.0/samsung_mdc/command.py0000644000175100002000000000671314736575073021243 0ustar00runnerdockerfrom typing import List, Union, Type from functools import partial, partialmethod from enum import Enum from .fields import Field, Enum as EnumField from .exceptions import MDCResponseError, NAKError class CommandMcs(type): def __new__(mcs, name, bases, dict): if name.startswith('_') or name == 'Command': return type.__new__(mcs, name, bases, dict) if 'name' not in dict: dict['name'] = name.lower() if 'DATA' not in dict and bases: # allow naive DATA inheritance dict['DATA'] = bases[0].DATA if '__doc__' not in dict and bases and bases[0].__doc__: # doc is not inherited by default dict['__doc__'] = bases[0].__doc__ dict['DATA'] = [ # convert Enum to EnumField EnumField(x) if isinstance(x, type) and issubclass(x, Enum) else x for x in dict['DATA'] ] dict['RESPONSE_DATA'] = [ EnumField(x) if isinstance(x, type) and issubclass(x, Enum) else x for x in dict.get( 'RESPONSE_DATA', dict['DATA'] + dict.get('RESPONSE_EXTRA', [])) ] cls = type.__new__(mcs, name, bases, dict) if cls.GET: cls.__call__.__defaults__ = (b'',) if not cls.SET or not cls.DATA: cls.__call__ = partialmethod(cls.__call__, data=b'') return cls class Command(metaclass=CommandMcs): name: str CMD: Union[int, Field] SUBCMD: Union[int, None] = None GET: bool SET: bool DATA: List[Union[Type[Enum], Field]] RESPONSE_DATA: List[Union[Type[Enum], Field]] RESPONSE_EXTRA: List[Union[Type[Enum], Field]] async def __call__(self, connection, display_id, data): data = self.parse_response( await connection.send( (self.CMD, self.SUBCMD) if self.SUBCMD is not None else self.CMD, display_id, self.pack_payload_data(data) if data else [] ), ) return tuple(self.parse_response_data(data)) def __get__(self, connection, cls): # Allow Command to be bounded as instance method if connection is None: return self # bind to class return partial(self, connection) # bind to instance @staticmethod def parse_response(response): ack, rcmd, data = response if not ack: raise NAKError(data[0]) return data @classmethod def parse_response_data(cls, data, strict_enum=True): rv, cursor = [], 0 for field in cls.RESPONSE_DATA: try: value, cursor_shift = field.parse(data[cursor:]) except Exception as exc: raise MDCResponseError( f'Error parsing {field.name}: {exc}', data[cursor:]) from exc rv.append(value) cursor += cursor_shift if data[cursor:]: # Not consumed data left raise MDCResponseError('Unparsed data left', data[cursor:]) return tuple(rv) @classmethod def pack_payload_data(cls, data): rv = bytes() for i, field in enumerate(cls.DATA): rv += bytes(field.pack(data[i])) if cls.DATA and len(data[i+1:]): raise ValueError('Unpacked data left ' '(more data provided than needed)') return rv @classmethod def get_order(cls): return (cls.CMD, cls.SUBCMD) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1736112699.0 python-samsung-mdc-1.15.0/samsung_mdc/commands.py0000644000175100002000000007456714736575073021442 0ustar00runnerdockerfrom typing import TYPE_CHECKING from .command import Command from .fields import ( Enum as EnumField, Int, Bool, Str, StrCoded, Time12H, Time, DateTime, Bitmask, IPAddress, VideoWallModel) if TYPE_CHECKING: from enum import IntEnum else: try: from enum import IntEnum except ImportError: from enum import Enum class IntEnum(int, Enum): ... class _COMMON: # It's better to re-use enumerations from "main" related commands, # like INPUT_SOURCE.INPUT_SOURCE_STATE / PICTURE_ASPECT.PICTURE_ASPECT_STATE # Use _COMMON namespace if there is no obvious "main" command, # avoid using it for too simple definitions. class ORIENTATION_MODE_STATE(IntEnum): LANDSCAPE_0 = 0x00 PORTRAIT_270 = 0x01 LANDSCAPE_180 = 0x02 PORTRAIT_90 = 0x03 class SERIAL_NUMBER(Command): CMD = 0x0B GET, SET = True, False DATA = [Str('SERIAL_NUMBER')] class ERROR_STATUS(Command): CMD = 0x0D GET, SET = True, False class LAMP_ERROR_STATE(IntEnum): NORMAL = 0x00 ERROR = 0x01 class TEMPERATURE_ERROR_STATE(IntEnum): NORMAL = 0x00 ERROR = 0x01 class BRIGHTNESS_SENSOR_ERROR_STATE(IntEnum): NONE = 0x00 ERROR = 0x01 NORMAL = 0x02 class INPUT_SOURCE_ERROR_STATE(IntEnum): """ No_Sync Error Note: Invalid status will be replied with app source selected state. Error status will be replied with input signal of not supported resolution or no signal. """ NORMAL = 0x00 ERROR = 0x01 INVALID = 0x02 class FAN_ERROR_STATE(IntEnum): NORMAL = 0x00 ERROR = 0x01 NONE = 0x02 # Fan is not supported DATA = [ LAMP_ERROR_STATE, TEMPERATURE_ERROR_STATE, BRIGHTNESS_SENSOR_ERROR_STATE, INPUT_SOURCE_ERROR_STATE, Int('TEMPERATURE'), FAN_ERROR_STATE, ] class SOFTWARE_VERSION(Command): CMD = 0x0E GET, SET = True, False DATA = [Str('SOFTWARE_VERSION')] class MODEL_NUMBER(Command): CMD = 0x10 GET, SET = True, False class MODEL_SPECIES(IntEnum): PDP = 0x01 LCD = 0x02 DLP = 0x03 LED = 0x04 CRT = 0x05 OLED = 0x06 class TV_SUPPORT(IntEnum): SUPPORTED = 0x00 NOT_SUPPORTED = 0x01 # NOTE: Actually there is list of MODEL_CODE in specification, # but it's TOO long, and it's TOO old (newer models gets new code), # so better use MODEL_NAME for this case DATA = [MODEL_SPECIES, Int('MODEL_CODE'), TV_SUPPORT] class POWER(Command): CMD = 0x11 GET, SET = True, True class POWER_STATE(IntEnum): OFF = 0x00 ON = 0x01 REBOOT = 0x02 DATA = [POWER_STATE] class VOLUME(Command): CMD = 0x12 GET, SET = True, True VOLUME = Int('VOLUME', range(101)) DATA = [VOLUME] class MUTE(Command): CMD = 0x13 GET, SET = True, True class MUTE_STATE(IntEnum): OFF = 0x00 ON = 0x01 NONE = 0xFF # Unavailable DATA = [MUTE_STATE] class INPUT_SOURCE(Command): """ Get/Set the device source which is shown on the screen. DVI_VIDEO, HDMI1_PC, HDMI2_PC, HDMI3_PC, HDMI4_PC: get only. URL_LAUNCHER, MAGIC_INFO, TV or some ports require support by model. On TIMER functions, Do not use WIDI_SCREEN_MIRRORING. """ CMD = 0x14 GET, SET = True, True class INPUT_SOURCE_STATE(IntEnum): # not a valid value for INPUT_SOURCE, but valid for auto_source NONE = 0x00 S_VIDEO = 0x04 COMPONENT = 0x08 AV = 0x0C AV2 = 0x0D SCART1 = 0x0E DVI = 0x18 PC = 0x14 BNC = 0x1E DVI_VIDEO = 0x1F # get only MAGIC_INFO = 0x20 HDMI1 = 0x21 HDMI1_PC = 0x22 # get only HDMI2 = 0x23 HDMI2_PC = 0x24 # get only DISPLAY_PORT_1 = 0x25 DISPLAY_PORT_2 = 0x26 DISPLAY_PORT_3 = 0x27 RF_TV = 0x30 # deprecated HDMI3 = 0x31 HDMI3_PC = 0x32 # get only HDMI4 = 0x33 HDMI4_PC = 0x34 # get only TV_DTV = 0x40 PLUG_IN_MODE = 0x50 HD_BASE_T = 0x55 OCM = 0x56 MEDIA_MAGIC_INFO_S = 0x60 WIDI_SCREEN_MIRRORING = 0x61 INTERNAL_USB = 0x62 URL_LAUNCHER = 0x63 IWB = 0x64 WEB_BROWSER = 0x65 REMOTE_WORKSPACE = 0x66 DATA = [INPUT_SOURCE_STATE] class PICTURE_ASPECT(Command): """ Get/Set the device picture size (aspect ratio). Working Condition: Will not work with VIDEO_WALL_STATE is ON. Note: Some of the image sizes are not supported depending on input signals. """ CMD = 0x15 GET, SET = True, True class PICTURE_ASPECT_STATE(IntEnum): PC_16_9 = 0x10 PC_4_3 = 0x18 PC_ORIGINAL_RATIO = 0x20 PC_21_9 = 0x21 PC_CUSTOM = 0x22 VIDEO_AUTO_WIDE = 0x00 VIDEO_16_9 = 0x01 VIDEO_ZOOM = 0x04 VIDEO_ZOOM_1 = 0x05 VIDEO_ZOOM_2 = 0x06 VIDEO_SCREEN_FIT = 0x09 VIDEO_4_3 = 0x0B VIDEO_WIDE_FIT = 0x0C VIDEO_CUSTOM = 0x0D VIDEO_SMART_VIEW_1 = 0x0E VIDEO_SMART_VIEW_2 = 0x0F VIDEO_WIDE_ZOOM = 0x31 VIDEO_21_9 = 0x32 DATA = [PICTURE_ASPECT_STATE] class SCREEN_MODE(Command): CMD = 0x18 GET, SET = True, True class SCREEN_MODE_STATE(IntEnum): MODE_16_9 = 0x01 MODE_ZOOM = 0x04 MODE_4_3 = 0x0B MODE_WIDE_ZOOM = 0x31 DATA = [SCREEN_MODE_STATE] class SCREEN_SIZE(Command): CMD = 0x19 GET, SET = True, False DATA = [Int('INCHES', range(256))] class NETWORK_CONFIGURATION(Command): CMD = 0x1B SUBCMD = 0x82 GET, SET = True, True DATA = [ IPAddress('IP_ADDRESS'), IPAddress('SUBNET_MASK'), IPAddress('GATEWAY_ADDRESS'), IPAddress('DNS_SERVER_ADDRESS'), ] class NETWORK_MODE(Command): CMD = 0x1B SUBCMD = 0x85 GET, SET = True, True class NETWORK_MODE_STATE(IntEnum): DYNAMIC = 0x00 STATIC = 0x01 DATA = [NETWORK_MODE_STATE] class WEEKLY_RESTART(Command): CMD = 0x1B SUBCMD = 0xA2 GET, SET = True, True class WEEKDAY(IntEnum): # NOTE: codes differs from TIMER_15.WEEKDAY SUN = 0x00 SAT = 0x01 FRI = 0x02 THU = 0x03 WED = 0x04 TUE = 0x05 MON = 0x06 DATA = [Bitmask(WEEKDAY), Time()] class NETWORK_AP_CONFIG(Command): """ Add new SSID info to device connection history with its password. Note: device may change network and response may not be received. """ CMD = 0x1B SUBCMD = 0x8A GET, SET = False, True DATA = [StrCoded(0x00, 'SSID'), StrCoded(0x01, 'PASSWORD')] class MAGICINFO_CHANNEL(Command): """ Set MagicInfo Channel by Direct Channel Number which is used by MagicInfo S Player. """ CMD = 0x1C SUBCMD = 0x81 GET, SET = False, True DATA = [Int('CHANNEL_NUMBER', length=2, byteorder='big')] class MAGICINFO_SERVER(Command): """ MagicInfo Server URL. Example: "http://example.com:80" """ CMD = 0x1C SUBCMD = 0x82 GET, SET = True, True DATA = [Str('MAGICINFO_SERVER_URL')] class MAGICINFO_CONTENT_ORIENTATION(Command): CMD = 0x1C SUBCMD = 0x83 GET, SET = True, True DATA = [_COMMON.ORIENTATION_MODE_STATE] class MDC_CONNECTION(Command): """ Note: Depends on the product specification, if it is set as RJ45 then serial MDC will not work. """ CMD = 0x1D GET, SET = True, True class MDC_CONNECTION_TYPE(IntEnum): RS232C = 0x00 RJ45 = 0x01 DATA = [MDC_CONNECTION_TYPE] class CONTRAST(Command): CMD = 0x24 GET, SET = True, True DATA = [Int('CONTRAST', range(101))] class BRIGHTNESS(Command): CMD = 0x25 GET, SET = True, True DATA = [Int('BRIGHTNESS', range(101))] class SHARPNESS(Command): CMD = 0x26 GET, SET = True, True DATA = [Int('SHARPNESS', range(101))] class COLOR(Command): CMD = 0x27 GET, SET = True, True DATA = [Int('COLOR', range(101))] class TINT(Command): """ Control the device tint. Adjust the ratio of green to red tint level. Red: TINT value, Green: ( 100 - TINT ) value. Note: Tint could only be set in 50 Steps (0, 2, 4, 6... 100). """ CMD = 0x28 GET, SET = True, True DATA = [Int('TINT', range(101))] class H_POSITION(Command): CMD = 0x31 GET, SET = False, True class H_POSITION_MOVE_TO(IntEnum): LEFT = 0x00 RIGHT = 0x01 DATA = [H_POSITION_MOVE_TO] class V_POSITION(Command): CMD = 0x32 GET, SET = False, True class V_POSITION_MOVE_TO(IntEnum): UP = 0x00 DOWN = 0x01 DATA = [V_POSITION_MOVE_TO] class AUTO_POWER(Command): CMD = 0x33 GET, SET = True, True class AUTO_POWER_STATE(IntEnum): OFF = 0x00 ON = 0x01 DATA = [AUTO_POWER_STATE] class CLEAR_MENU(Command): CMD = 0x34 SUBCMD = 0x00 GET, SET = False, True DATA = [] class IR_STATE(Command): """ Enables/disables IR (Infrared) receiving function (Remote Control). Working Condition: * Can operate regardless of whether power is ON/OFF. (If DPMS Situation in LFD, it operate Remocon regardless of set value). """ CMD = 0x36 GET, SET = True, True class IR_STATE(IntEnum): DISABLED = 0x00 ENABLED = 0x01 DATA = [IR_STATE] class RGB_CONTRAST(Command): CMD = 0x37 GET, SET = True, True DATA = [Int('CONTRAST', range(101))] class RGB_BRIGHTNESS(Command): CMD = 0x38 GET, SET = True, True DATA = [Int('BRIGHTNESS', range(101))] class AUTO_ADJUSTMENT_ON(Command): CMD = 0x3D SUBCMD = 0x00 GET, SET = False, True DATA = [] class COLOR_TONE(Command): CMD = 0x3E GET, SET = True, True class COLOR_TONE_STATE(IntEnum): COOL_2 = 0x00 COOL_1 = 0x01 NORMAL = 0x02 WARM_1 = 0x03 WARM_2 = 0x04 OFF = 0x50 DATA = [COLOR_TONE_STATE] class COLOR_TEMPERATURE(Command): """ Color temperature function. Unit is hectoKelvin (hK) (x*100 Kelvin) (example: 28 = 2800K). Supported values - 28, 30, 35, 40... 160. For older models: 0-10=(x*100K + 5000K), 253=2800K, 254=3000K, 255=4000K """ CMD = 0x3F GET, SET = True, True DATA = [Int('HECTO_KELVIN')] class STANDBY(Command): CMD = 0x4A GET, SET = True, True class STANDBY_STATE(IntEnum): OFF = 0x00 ON = 0x01 AUTO = 0x02 DATA = [STANDBY_STATE] class AUTO_LAMP(Command): """ Auto Lamp function (backlight). Note: When Manual Lamp Control is on, Auto Lamp Control will automatically turn off. """ CMD = 0x57 GET, SET = True, True DATA = [ Time12H('MAX_TIME'), Int('MAX_LAMP_VALUE', range(101)), Time12H('MIN_TIME'), Int('MIN_LAMP_VALUE', range(101)), ] class MANUAL_LAMP(Command): """ Manual Lamp function (backlight). Note: When Auto Lamp Control is on, Manual Lamp Control will automatically turn off. """ CMD = 0x58 GET, SET = True, True DATA = [Int('LAMP_VALUE', range(101))] class INVERSE(Command): CMD = 0x5A GET, SET = True, True class INVERSE_STATE(IntEnum): OFF = 0x00 ON = 0x01 DATA = [INVERSE_STATE] class SAFETY_LOCK(Command): CMD = 0x5D GET, SET = True, True class LOCK_STATE(IntEnum): OFF = 0x00 ON = 0x01 DATA = [LOCK_STATE] class PANEL_LOCK(Command): CMD = 0x5F GET, SET = True, True class LOCK_STATE(IntEnum): OFF = 0x00 ON = 0x01 DATA = [LOCK_STATE] class CHANNEL_CHANGE(Command): CMD = 0x61 GET, SET = False, True class CHANGE_TO(IntEnum): UP = 0x00 DOWN = 0x01 DATA = [CHANGE_TO] class VOLUME_CHANGE(Command): CMD = 0x62 GET, SET = False, True class CHANGE_TO(IntEnum): UP = 0x00 DOWN = 0x01 DATA = [CHANGE_TO] class TICKER(Command): """ Get/Set the device ticker. (Show text message overlay on the screen) Note: POS_HORIZ or POS_VERT are NONE in GET response if unsupported by the display. """ CMD = 0x63 GET, SET = True, True class POS_HORIZ(IntEnum): CENTER = 0x00 LEFT = 0x01 RIGHT = 0x02 NONE = 0xFF class POS_VERTI(IntEnum): MIDDLE = 0x00 TOP = 0x01 BOTTOM = 0x02 NONE = 0xFF class MOTION_DIR(IntEnum): LEFT = 0x00 RIGHT = 0x01 UP = 0x02 DOWN = 0x03 class MOTION_SPEED(IntEnum): NORMAL = 0x00 SLOW = 0x01 FAST = 0x02 class FONT_SIZE(IntEnum): STANDARD = 0x00 SMALL = 0x01 LARGE = 0x02 class FOREGROUND_COLOR(IntEnum): BLACK = 0x00 WHITE = 0x01 RED = 0x02 GREEN = 0x03 BLUE = 0x04 YELLOW = 0x05 MAGENTA = 0x06 CYAN = 0x07 class BACKGROUND_COLOR(IntEnum): BLACK = 0x00 WHITE = 0x01 RED = 0x02 GREEN = 0x03 BLUE = 0x04 YELLOW = 0x05 MAGENTA = 0x06 CYAN = 0x07 class FOREGROUND_OPACITY(IntEnum): FLASHING = 0x03 FLASH_ALL = 0x04 OFF = 0x05 class BACKGROUND_OPACITY(IntEnum): SOLID = 0x00 TRANSPARENT = 0x01 TRANSLUCENT = 0x02 UNKNOWN = 0x03 DATA = [ Bool('ON_OFF'), Time12H('START_TIME'), Time12H('END_TIME'), POS_HORIZ, POS_VERTI, Bool('MOTION_ON_OFF'), MOTION_DIR, MOTION_SPEED, FONT_SIZE, FOREGROUND_COLOR, BACKGROUND_COLOR, FOREGROUND_OPACITY, BACKGROUND_OPACITY, Str('MESSAGE') ] class DEVICE_NAME(Command): """ It reads the device name which user set up in network. Shows the information about entered device name. """ CMD = 0x67 GET, SET = True, False DATA = [Str('DEVICE_NAME')] class OSD(Command): """ Turns OSD (On-screen display) on/off. """ CMD = 0x70 GET, SET = True, True DATA = [Bool('OSD_ENABLED')] class PICTURE_MODE(Command): CMD = 0x71 GET, SET = True, True class PICTURE_MODE_STATE(IntEnum): DYNAMIC = 0x00 STANDARD = 0x01 MOVIE = 0x02 CUSTOM_TV = 0x03 NATURAL = 0x04 CALIBRATION_TV = 0x05 ENTERTAIN = 0x10 INTERNET = 0x11 TEXT = 0x12 CUSTOM = 0x13 ADVERTISEMENT = 0x14 INFORMATION = 0x15 CALIBRATION = 0x16 SHOP_MALL_VIDEO = 0x20 SHOP_MALL_TEXT = 0x21 OFFICE_SCHOOL_VIDEO = 0x22 OFFICE_SCHOOL_TEXT = 0x23 TERMINAL_STATION_VIDEO = 0x24 TERMINAL_STATION_TEXT = 0x25 VIDEO_WALL_VIDEO = 0x26 VIDEO_WALL_TEXT = 0x27 HDR_PLUS = 0x30 OFF = 0x50 RESERVED_OTHER = 0x90 DATA = [PICTURE_MODE_STATE] class SOUND_MODE(Command): CMD = 0x72 GET, SET = True, True class SOUND_MODE_STATE(IntEnum): STANDARD = 0x00 MUSIC = 0x01 MOVIE = 0x02 SPEECH = 0x03 CUSTOM = 0x04 AMPLIFY = 0x05 OPTIMIZED = 0x06 DATA = [SOUND_MODE_STATE] class ALL_KEYS_LOCK(Command): """ Turns both REMOCON and Panel Key Lock function on/off. Note: Can operate regardless of whether power is on/off. """ # TODO: REMOCON? Remote Control? CMD = 0x77 GET, SET = True, True class LOCK_STATE(IntEnum): OFF = 0x00 ON = 0x01 DATA = [LOCK_STATE] class MODEL_NAME(Command): CMD = 0x8A GET, SET = True, False DATA = [Str('MODEL_NAME')] class PANEL_ON_TIME(Command): """ Get the device panel on total time. Return value increased every 10 mins. To get hours use "MIN10 / 6". """ CMD = 0x83 GET, SET = True, False DATA = [Int('MIN10', length=2, byteorder='big')] @classmethod def parse_response_data(cls, data): # looks like they increased length on newer models, # so we're trying to decoce anyway return (int.from_bytes(data, byteorder='big'),) class ENERGY_SAVING(Command): CMD = 0x92 GET, SET = True, True class ENERGY_SAVING_STATE(IntEnum): OFF = 0x00 LOW = 0x01 MEDIUM = 0x02 HIGH = 0x03 PICTURE_OFF = 0x04 DATA = [ENERGY_SAVING_STATE] class RESET(Command): CMD = 0x9F GET, SET = False, True class RESET_TARGET(IntEnum): PICTURE = 0x00 SOUND = 0x01 SETUP = 0x02 # (System reset) ALL = 0x03 SCREEN_DISPLAY = 0x04 DATA = [RESET_TARGET] class OSD_TYPE(Command): """ Turns OSD (On-screen display) specific message types on/off. """ CMD = 0xA3 GET, SET = True, True class OSD_TYPE(IntEnum): SOURCE = 0x00 NOT_OPTIMUM_MODE = 0x01 NO_SIGNAL = 0x02 MDC = 0x03 SCHEDULE_CHANNEL = 0x04 DATA = [OSD_TYPE, Bool('OSD_ENABLED')] RESPONSE_DATA = [Bitmask(OSD_TYPE, 'OSD_STATUS')] class TIMER_15(Command): """ Integrated timer function (15 data-length version). Note: This depends on product and will not work on older versions. ON_TIME/OFF_TIME: turn ON/OFF display at specific time of day ON_ACTIVE/OFF_ACTIVE: if timer is not active, values are ignored, so there may be only OFF timer, ON timer, or both. REPEAT: On which day timer is enabled (combined with HOLIDAY_APPLY and MANUAL_WEEKDAY) """ CMD = Int('TIMER_ID', range(1, 8)) _TIMER_ID_CMD = [0xA4, 0xA5, 0xA6, 0xAB, 0xAC, 0xAD, 0xAE] GET, SET = True, True class TIMER_REPEAT(IntEnum): ONCE = 0x00 EVERYDAY = 0x01 MON_FRI = 0x02 MON_SAT = 0x03 SAT_SUN = 0x04 MANUAL_WEEKDAY = 0x05 class WEEKDAY(IntEnum): SUN = 0x00 MON = 0x01 TUE = 0x02 WED = 0x03 THU = 0x04 FRI = 0x05 SAT = 0x06 # ignore_bit_7 = 7 class HOLIDAY_APPLY(IntEnum): DONT_APPLY_BOTH = 0x00 APPLY_BOTH = 0x01 ON_TIMER_ONLY_APPLY = 0x02 OFF_TIMER_ONLY_APPLY = 0x03 DATA = [ Time12H('ON_TIME'), Bool('ON_ENABLED'), Time12H('OFF_TIME'), Bool('OFF_ENABLED'), EnumField(TIMER_REPEAT, 'ON_REPEAT'), Bitmask(WEEKDAY, 'ON_MANUAL_WEEKDAY'), EnumField(TIMER_REPEAT, 'OFF_REPEAT'), Bitmask(WEEKDAY, 'OFF_MANUAL_WEEKDAY'), VOLUME.VOLUME, INPUT_SOURCE.INPUT_SOURCE_STATE, HOLIDAY_APPLY, ] async def __call__(self, connection, display_id, timer_id, data): cmd = self._TIMER_ID_CMD[timer_id - 1] data = self.parse_response( await connection.send( cmd, display_id, self.pack_payload_data(data) if data else [] ), ) return self.parse_response_data(data) @classmethod def parse_response_data(cls, data, *args, _timer_version_check=True, **kwargs): if _timer_version_check and len(data) == 13: raise RuntimeError('13 data-length version of timer received, ' 'use timer_13 instead') return super().parse_response_data(data, *args, **kwargs) @classmethod def get_order(cls): return (0xA4, cls.name) class TIMER_13(TIMER_15): """ Integrated timer function (13 data-length version). Note: This depends on product and will not work on newer versions. """ DATA = [ Time12H('ON_TIME'), Bool('ON_ENABLED'), Time12H('OFF_TIME'), Bool('OFF_ENABLED'), EnumField(TIMER_15.TIMER_REPEAT, 'REPEAT'), Bitmask(TIMER_15.WEEKDAY, 'MANUAL_WEEKDAY'), VOLUME.VOLUME, INPUT_SOURCE.INPUT_SOURCE_STATE, TIMER_15.HOLIDAY_APPLY, ] @classmethod def parse_response_data(cls, data, *args, **kwargs): if len(data) == 15: raise RuntimeError('15 data-length version of timer received, ' 'use timer_15 instead') return super().parse_response_data( data, *args, _timer_version_check=False, **kwargs) class CLOCK_S(Command): """ Current time function (second precision). Note: This is for models developed after 2013. For older models see CLOCK_M function (minute precision). """ GET, SET = True, True CMD = 0xC5 DATA = [DateTime()] class CLOCK_M(CLOCK_S): """ Current time function (minute precision). Note: This is for models developed until 2013. For newer models see CLOCK_S function (seconds precision). """ CMD = 0xA7 DATA = [DateTime(seconds=False)] class HOLIDAY_SET(Command): """ Add/Delete the device holiday schedule with the holiday schedule itself start month/day and end month/day. Note: On DELETE_ALL all parameters should be 0x00. """ CMD = 0xA8 GET, SET = False, True class HOLIDAY_MANAGE(IntEnum): ADD = 0x00 DELETE = 0x01 DELETE_ALL = 0x02 DATA = [ HOLIDAY_MANAGE, Int('START_MONTH', range(13)), Int('START_DAY', range(32)), Int('END_MONTH', range(13)), Int('END_DAY', range(32)), ] class HOLIDAY_GET(Command): """ Get the device holiday schedule. If INDEX is not specified, returns total number of Holiday Information. """ CMD = 0xA9 GET, SET = True, True DATA = [ Int('INDEX'), ] RESPONSE_EXTRA = [ Int('START_MONTH'), Int('START_DAY'), Int('END_MONTH'), Int('END_DAY'), ] @classmethod def parse_response_data(cls, data): if data[1:] == bytes([0, 0, 0, 0]): # index was not specified, # so it's total number of holiday information return (int(data[0]),) return super().parse_response_data(data) class VIRTUAL_REMOTE(Command): """ This function support that MDC command can work same as remote control. Note: In a certain model, 0x79 CONTENT key works as HOME and 0x1F DISPLAY key works as INFO. """ CMD = 0xB0 GET, SET = False, True class KEY_CODE(IntEnum): KEY_SOURCE = 0x01 KEY_POWER = 0x02 KEY_1 = 0x04 KEY_2 = 0x05 KEY_3 = 0x06 KEY_VOLUME_UP = 0x07 KEY_4 = 0x08 KEY_5 = 0x09 KEY_6 = 0x0A KEY_VOLUME_DOWN = 0x0B KEY_7 = 0x0C KEY_8 = 0x0D KEY_9 = 0x0E KEY_MUTE = 0x0F KEY_CHANNEL_DOWN = 0x10 KEY_0 = 0x11 KEY_CHANNEL_UP = 0x12 KEY_GREEN = 0x14 KEY_YELLOW = 0x15 KEY_CYAN = 0x16 KEY_MENU = 0x1A KEY_DISPLAY = 0x1F # or KEY_INFO KEY_DIGIT = 0x23 KEY_PIP_TV_VIDEO = 0x24 KEY_EXIT = 0x2D KEY_MAGICINFO = 0x30 # limited support KEY_REW = 0x45 KEY_STOP = 0x46 KEY_PLAY = 0x47 KEY_FF = 0x48 KEY_PAUSE = 0x4A KEY_TOOLS = 0x4B KEY_RETURN = 0x58 KEY_MAGICINFO_LITE = 0x5B # limited support KEY_CURSOR_UP = 0x60 KEY_CURSOR_DOWN = 0x61 KEY_CURSOR_RIGHT = 0x62 KEY_CURSOR_LEFT = 0x65 KEY_ENTER = 0x68 KEY_RED = 0x6C KEY_LOCK = 0x77 KEY_CONTENT = 0x79 # HOME DISCRET_POWER_OFF = 0x98 KEY_3D = 0x9F # # Corresponding to MediaKey API # # https://docs.tizen.org/application/web/api/latest/device_api/mobile/tizen/mediakey.html # noqa # class MEDIA_KEY_CODE(IntEnum): # PLAY = KEY_CODE.KEY_PLAY # STOP = KEY_CODE.KEY_STOP # PAUSE = KEY_CODE.KEY_PAUSE # # PREVIOUS = ?? # # NEXT = ?? # FAST_FORWARD = KEY_CODE.KEY_FF # REWIND - KEY_CODE.KEY_REW # # PLAY_PAUSE = ?? DATA = [KEY_CODE] class NETWORK_STANDBY(Command): CMD = 0xB5 GET, SET = True, True class NETWORK_STANDBY_STATE(IntEnum): OFF = 0x00 ON = 0x01 DATA = [NETWORK_STANDBY_STATE] class DST(Command): CMD = 0xB6 GET, SET = True, True class DST_STATE(IntEnum): OFF = 0x00 AUTO = 0x01 MANUAL = 0x02 class MONTH(IntEnum): JAN = 0x00 FEB = 0x01 MAR = 0x02 APR = 0x03 MAY = 0x04 JUN = 0x05 JUL = 0x06 AUG = 0x07 SEP = 0x08 OCT = 0x09 NOV = 0x0A DEC = 0x0B class WEEK(IntEnum): WEEK_1 = 0x00 WEEK_2 = 0x01 WEEK_3 = 0x02 WEEK_4 = 0x03 WEEK_LAST = 0x04 class WEEKDAY(IntEnum): # NOTE: same as TIMER_15.WEEKDAY SUN = 0x00 MON = 0x01 TUE = 0x02 WED = 0x03 THU = 0x04 FRI = 0x05 SAT = 0x06 class OFFSET(IntEnum): PLUS_1_00 = 0x00 PLUS_2_00 = 0x01 DATA = [ DST_STATE, EnumField(MONTH, 'START_MONTH'), EnumField(WEEK, 'START_WEEK'), EnumField(WEEKDAY, 'START_WEEKDAY'), Time('START_TIME'), EnumField(MONTH, 'END_MONTH'), EnumField(WEEK, 'END_WEEK'), EnumField(WEEKDAY, 'END_WEEKDAY'), Time('END_TIME'), OFFSET, ] RESPONSE_EXTRA = [ Bool('TUNER_SUPPORT'), ] class AUTO_ID_SETTING(Command): CMD = 0xB8 GET, SET = True, True class AUTO_ID_SETTING_STATE(IntEnum): START = 0x00 END = 0x01 DATA = [AUTO_ID_SETTING_STATE] class DISPLAY_ID(Command): CMD = 0xB9 GET, SET = False, True class DISPLAY_ID_STATE(IntEnum): OFF = 0x00 ON = 0x01 DATA = [DISPLAY_ID_STATE] class AUTO_SOURCE_SWITCH(Command): CMD = 0xCA SUBCMD = 0x81 GET, SET = True, True class AUTO_SOURCE_SWITCH_STATE(IntEnum): OFF = 0x00 ON = 0x01 DATA = [AUTO_SOURCE_SWITCH_STATE] class AUTO_SOURCE(Command): CMD = 0xCA SUBCMD = 0x82 GET, SET = True, True class PRIMARY_SOURCE_RECOVERY(IntEnum): OFF = 0x00 ON = 0x01 DATA = [ PRIMARY_SOURCE_RECOVERY, EnumField(INPUT_SOURCE.INPUT_SOURCE_STATE, 'PRIMARY_SOURCE'), EnumField(INPUT_SOURCE.INPUT_SOURCE_STATE, 'SECONDARY_SOURCE') ] class LAUNCHER_PLAY_VIA(Command): CMD = 0xC7 SUBCMD = 0x81 GET, SET = True, True class PLAY_VIA_MODE(IntEnum): MAGIC_INFO = 0x00 URL_LAUNCHER = 0x01 MAGIC_IWB = 0x02 DATA = [PLAY_VIA_MODE] class LAUNCHER_URL_ADDRESS(Command): CMD = 0xC7 SUBCMD = 0x82 GET, SET = True, True DATA = [Str('URL_ADDRESS')] class OSD_MENU_ORIENTATION(Command): CMD = 0xC8 SUBCMD = 0x81 GET, SET = True, True DATA = [_COMMON.ORIENTATION_MODE_STATE] class OSD_SOURCE_CONTENT_ORIENTATION(Command): CMD = 0xC8 SUBCMD = 0x82 GET, SET = True, True DATA = [_COMMON.ORIENTATION_MODE_STATE] class OSD_ASPECT_RATIO(Command): """ Get/Set the device aspect ratio under portrait mode which set the rotated screen to be full or original. """ CMD = 0xC8 SUBCMD = 0x83 GET, SET = True, True class ASPECT_RATIO_STATE(IntEnum): FULL_SCREEN = 0x00 ORIGINAL = 0x01 DATA = [ASPECT_RATIO_STATE] class OSD_PIP_ORIENTATION(Command): CMD = 0xC8 SUBCMD = 0x84 GET, SET = True, True DATA = [_COMMON.ORIENTATION_MODE_STATE] class OSD_MENU_SIZE(Command): CMD = 0xC8 SUBCMD = 0x85 GET, SET = True, True class MENU_SIZE_STATE(IntEnum): ORIGINAL = 0x00 MEDIUM = 0x01 SMALL = 0x02 DATA = [MENU_SIZE_STATE] class PANEL(Command): CMD = 0xF9 GET, SET = True, True class PANEL_STATE(IntEnum): ON = 0x00 OFF = 0x01 DATA = [PANEL_STATE] class SCREEN_MUTE(Command): # Undocumented (as of Ver. 15 2020-11-06) # see https://github.com/vgavro/samsung-mdc/issues/19 CMD = 0xFE SUBCMD = 0x51 GET, SET = True, True class SCREEN_MUTE_STATUS(IntEnum): ON = 0x00 OFF = 0xFF DATA = [SCREEN_MUTE_STATUS] class STATUS(Command): """ Get the device various state like power, volume, sound mute, input source, picture aspect ratio. Note: For no audio models volume and mute returns 0xFF (255). N_TIME_NF, F_TIME_NF: OnTime/OffTime ON/OFF value (old type timer, now it's always 0x00). """ CMD = 0x00 GET, SET = True, False DATA = [ POWER.POWER_STATE, VOLUME.VOLUME, MUTE.MUTE_STATE, INPUT_SOURCE.INPUT_SOURCE_STATE, PICTURE_ASPECT.PICTURE_ASPECT_STATE, Int('N_TIME_NF'), Int('F_TIME_NF') ] class VIDEO(Command): CMD = 0x04 GET, SET = True, False DATA = [ Int('CONTRAST', range(101)), Int('BRIGHTNESS', range(101)), Int('SHARPNESS', range(101)), Int('COLOR', range(101)), Int('TINT', range(101)), COLOR_TONE.COLOR_TONE_STATE, Int('COLOR_TEMPERATURE'), Int('_IGNORE', range(1)), ] class RGB(Command): CMD = 0x06 GET, SET = True, False DATA = [ Int('CONTRAST', range(101)), Int('BRIGHTNESS', range(101)), COLOR_TONE.COLOR_TONE_STATE, Int('COLOR_TEMPERATURE'), Int('_IGNORE', range(1)), Int('RED_GAIN'), Int('GREEN_GAIN'), Int('BLUE_GAIN'), ] class VIDEO_WALL_STATE(Command): """ Get/Set the device in video wall state. This will split the primary input source into smaller N number of squares and display them instead. Note: The device needs to be capable of this operation. Usually a primary high resolution source signal is daisy chained to lower resolution displays in a video wall using HDMI/DP. """ CMD = 0x84 GET, SET = True, True class VIDEO_WALL_STATE(IntEnum): OFF = 0x00 ON = 0x01 DATA = [VIDEO_WALL_STATE] class VIDEO_WALL_MODE(Command): """ Get/Set the device in aspect ratio of the video wall. FULL: stretch input source to fill display NATURAL: Keep aspect ratio of input source; do not fill display. Note: Needs VIDEO_WALL_STATE to be ON. """ CMD = 0x5C GET, SET = True, True class VIDEO_WALL_MODE(IntEnum): NATURAL = 0x00 FULL = 0x01 DATA = [VIDEO_WALL_MODE] class VIDEO_WALL_MODEL(Command): """ Get/Set video wall model. MODEL: Size of the wall in (x, y) coordinates; ie. "2,2" or "4,1" SERIAL: Serial number - position of the display in the video wall, counting from the first display. Note: Needs VIDEO_WALL_STATE to be ON. """ CMD = 0x89 GET, SET = True, True DATA = [VideoWallModel('MODEL'), Int('SERIAL', range(1, 256))] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1736112699.0 python-samsung-mdc-1.15.0/samsung_mdc/connection.py0000644000175100002000000002340514736575073021761 0ustar00runnerdockerfrom typing import Union, Sequence, Tuple from functools import partial from enum import Enum import asyncio from .exceptions import MDCResponseError, MDCReadTimeoutError, \ MDCTimeoutError, MDCTLSRequired, MDCTLSAuthFailed from .utils import repr_hex HEADER_CODE = 0xAA RESPONSE_CMD = 0xFF ACK_CODE = ord('A') # 0x41 65 NAK_CODE = ord('N') # 0x4E 78 def get_checksum(payload): # payload should be without HEADER_CODE return sum(payload) % 256 def _normalize_cmd( cmd: Union[int, Tuple[int], Tuple[int, Union[int, None]]] ) -> Tuple[int, Union[int, None]]: """ Returns (cmd, subcmd) tuple """ if isinstance(cmd, int): return cmd, None elif len(cmd) == 1: return int(cmd[0]), None elif not len(cmd) == 2: raise ValueError('cmd tuple should contain only (cmd, subcmd)') return int(cmd[0]), None if cmd[1] is None else int(cmd[1]) def pack_payload( cmd: Union[int, Tuple[int], Tuple[int, Union[int, None]]], display_id: int, data: Union[bytes, Sequence] = b'' ): cmd, subcmd = _normalize_cmd(cmd) data = bytes(data) if subcmd is not None: data = bytes([subcmd]) + data payload = ( bytes([HEADER_CODE, cmd, display_id, len(data)]) + bytes(data) ) payload += bytes([get_checksum(payload[1:])]) return payload def pack_response( cmd: Union[int, Tuple[int], Tuple[int, int]], display_id: int, ack: bool, data: Union[bytes, Sequence] = b'' ): cmd, subcmd = _normalize_cmd(cmd) if not ack and len(data) != 1: raise ValueError( 'Data should contain only error code for NAK response', data) data = bytes(data) if subcmd is not None and ack: # subcmd is not sent on NAK in response data = bytes([subcmd]) + data return pack_payload( RESPONSE_CMD, display_id, bytes([ACK_CODE if ack else NAK_CODE, cmd]) + data ) async def wait_for(aw, timeout, reason): try: return await asyncio.wait_for(aw, timeout) except asyncio.TimeoutError as exc: raise MDCTimeoutError(reason) from exc async def wait_for_read(reader, count, timeout, reason): try: return await asyncio.wait_for(reader.read(count), timeout) except asyncio.TimeoutError as exc: raise MDCReadTimeoutError(reason, bytes(reader._buffer)) from exc class CONNECTION_MODE(Enum): TCP = 'tcp' SERIAL = 'serial' class MDCConnection: reader, writer = None, None def __init__(self, target, mode=CONNECTION_MODE.TCP, timeout=5, connect_timeout=None, verbose=False, **connection_kwargs): self.target = target self.mode = CONNECTION_MODE(mode) self.connection_kwargs = connection_kwargs self.timeout = timeout self.connect_timeout = connect_timeout or timeout self.verbose = ( partial(print, self.target) if verbose is True else verbose) async def open(self): connection_kwargs = self.connection_kwargs.copy() pin = connection_kwargs.pop('pin', None) if self.mode == CONNECTION_MODE.TCP: if isinstance(self.target, (list, tuple)): # make target be compatible with socket.__init__ target, port = self.target else: target, *port = self.target.split(':') port = port and int(port[0]) or 1515 connection_kwargs.setdefault('port', port) self.reader, self.writer = \ await wait_for( asyncio.open_connection(target, **connection_kwargs), self.connect_timeout, 'Connect timeout') if self.verbose: self.verbose('Connected') else: # Make this package optional from serial_asyncio import ( # type: ignore[import-untyped] open_serial_connection ) self.reader, self.writer = \ await wait_for( open_serial_connection( url=self.target, **connection_kwargs), self.connect_timeout, 'Connect timeout') if self.verbose: self.verbose('Connected') if pin is not None: try: await self._start_tls(pin) except Exception: await self.close() raise async def _start_tls(self, pin): if isinstance(pin, int): pin = str(pin).rjust(4, '0').encode() elif isinstance(pin, str): pin = pin.rjust(4, '0').encode() assert self.is_opened import ssl resp = await wait_for_read(self.reader, 15, self.timeout, 'TLS header read timeout') if not resp == b'MDCSTART<>': raise MDCResponseError('Unexpected TLS header', resp + self.reader._buffer) transport = self.writer.transport protocol = transport.get_protocol() loop = asyncio.get_event_loop() ssl_ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) ssl_ctx.minimum_version = ssl.TLSVersion.MINIMUM_SUPPORTED ssl_ctx.check_hostname = False ssl_ctx.verify_mode = ssl.VerifyMode.CERT_NONE ssl_transport = await loop.start_tls( transport, protocol, ssl_ctx) self.writer._transport = ssl_transport self.reader._transport = ssl_transport if self.verbose: self.verbose('TLS established') self.writer.write(pin) await wait_for(self.writer.drain(), self.timeout, 'Write pin timeout') resp = await wait_for_read(self.reader, 15, self.timeout, 'TLS auth read timeout') if not resp == b'MDCAUTH<>': if resp[:14] == b'MDCAUTH<>': raise MDCResponseError('Unexpected TLS auth fail response', resp + self.reader._buffer) try: fail_code = int(resp[-6:-2], 16) except ValueError: raise MDCResponseError('Unexpected TLS auth fail code', resp + self.reader._buffer) raise MDCTLSAuthFailed(fail_code) raise MDCResponseError('Unexpected TLS auth response', resp + self.reader._buffer) if self.verbose: self.verbose('TLS authentication passed') @property def is_opened(self): return self.writer is not None @property def is_tls_started(self): return (self.writer and self.writer.transport.__class__.__name__ == '_SSLProtocolTransport') async def send( self, cmd: Union[int, Tuple[int], Tuple[int, int]], display_id: int, data: Union[bytes, Sequence] = b'' ): cmd, subcmd = _normalize_cmd(cmd) payload = pack_payload((cmd, subcmd), display_id, data) if not self.is_opened: await self.open() assert (self.reader is not None and self.writer is not None) self.writer.write(payload) await wait_for(self.writer.drain(), self.timeout, 'Write timeout') if self.verbose: self.verbose('Sent', repr_hex(payload)) resp = await wait_for_read(self.reader, 4, self.timeout, 'Response header read timeout') if not resp: raise MDCResponseError('Empty response', resp) if resp[0] != HEADER_CODE: if (resp + self.reader._buffer) == b'MDCSTART<>': raise MDCTLSRequired(resp + self.reader._buffer) raise MDCResponseError('Unexpected header', resp + self.reader._buffer) if resp[1] != RESPONSE_CMD: raise MDCResponseError('Unexpected cmd', resp + self.reader._buffer) if resp[2] != display_id: raise MDCResponseError('Unexpected display_id', resp + self.reader._buffer) length = resp[3] resp += await wait_for_read(self.reader, length + 1, self.timeout, 'Response data read timeout') if self.verbose: self.verbose('Recv', repr_hex(resp)) checksum = get_checksum(resp[1:-1]) if checksum != int(resp[-1]): raise MDCResponseError('Checksum failed', resp) ack, rcmd, data = resp[4], resp[5], resp[6:-1] if ack not in (ACK_CODE, NAK_CODE): raise MDCResponseError('Unexpected ACK/NAK', resp) if subcmd and ack == ACK_CODE: # rsubcmd is not sent on NAK rsubcmd = data[0] data = data[1:] else: rsubcmd = None return ( ack == ACK_CODE, (rcmd,) if rsubcmd is None else (rcmd, rsubcmd), data ) async def close(self): if self.is_tls_started: # FIX warning # "returning true from eof_received() has no effect when using ssl" self.writer._protocol.eof_received = lambda: None writer = self.writer self.reader, self.writer = None, None writer.close() await wait_for(writer.wait_closed(), self.timeout, 'Close timeout') async def __aenter__(self): if not self.is_opened: await self.open() return self async def __aexit__(self, *args): if self.is_opened: await self.close() def __await__(self): return self.__aenter__().__await__() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1736112699.0 python-samsung-mdc-1.15.0/samsung_mdc/exceptions.py0000644000175100002000000000150214736575073021775 0ustar00runnerdockerfrom asyncio import TimeoutError class MDCError(Exception): pass class MDCTLSRequired(Exception): pass class MDCTLSAuthFailed(Exception): def __init__(self, code): self.code = code super().__init__(code) def __str__(self): if self.code == 1: return 'Wrong pin' elif self.code == 2: return 'Blocked' else: return f'Unknown code: {self.code}' class MDCTimeoutError(MDCError, TimeoutError): pass class MDCReadTimeoutError(MDCTimeoutError): pass class MDCResponseError(MDCError): pass class NAKError(MDCError): def __init__(self, error_code): self.error_code = error_code super().__init__(error_code) def __str__(self): return f'Negative Acknowledgement [error_code {self.error_code}]' ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1736112699.0 python-samsung-mdc-1.15.0/samsung_mdc/fields.py0000644000175100002000000001236414736575073021072 0ustar00runnerdockerfrom typing import Sequence, Optional from datetime import datetime, time from .utils import ( parse_mdc_time, pack_mdc_time, parse_enum_bitmask, pack_bitmask, pack_videowall_model, parse_videowall_model) class Field: def __init__(self, name=None): self.name = name or self.__class__.__name__.upper() def parse(self, data): # returns data and cursor shift return data, len(data) def pack(self, value): return [value] class Int(Field): range: Optional[range] = None def __init__(self, name=None, range=None, length=1, byteorder='big'): super().__init__(name) self.range = range self.length = length self.byteorder = byteorder def pack(self, value): if self.range and value not in self.range: raise ValueError('Field not in range', self.name, self.range) return int(value).to_bytes(self.length, byteorder=self.byteorder) def parse(self, data): return int.from_bytes(data[:self.length], self.byteorder), self.length class Bool(Int): range = range(2) def parse(self, data): return bool(data[0]), 1 class Enum(Field): def __init__(self, enum, name=None): self.enum = enum super().__init__(name or enum.__name__) def parse(self, data): return self.enum(data[0]), 1 def pack(self, value): if isinstance(value, str): value = self.enum[value] return [self.enum(value).value] class Str(Field): def __init__(self, name=None, length=None): self.length = length super().__init__(name) def parse(self, data): length = self.length or len(data) return data[:length].decode('utf8').rstrip('\x00'), length def pack(self, value): if self.length is not None and len(value) > self.length: raise ValueError('Field length exceeded', len(value), self.length) return value.encode('utf8') class StrCoded(Field): def __init__(self, code, name=None): super().__init__(name) self.code = code def parse(self, data): if self.code != data[0]: raise ValueError('Expected code not matched', data[0]) length = data[1] if not length: return '', 2 return data[2:length + 2].decode('utf8'), length + 2 def pack(self, value): encoded = value.encode('utf8') return bytes([self.code, len(encoded)]) + encoded class Time12H(Field): def parse(self, data): return parse_mdc_time(data[2], data[0], data[1]), 3 def pack(self, data): day_part, hour, minute, second = pack_mdc_time(data) return (hour, minute, day_part) class Time(Field): def __init__(self, name=None, seconds=False): self.seconds = seconds super().__init__(name) def parse(self, data): return ( time(data[0], data[1], data[3] if self.seconds else 0), 3 if self.seconds else 2 ) def pack(self, data): if self.seconds: return (data.hour, data.minute, data.second) return (data.hour, data.minute) class DateTime(Field): name = 'datetime' def __init__(self, name=None, seconds=True): self.seconds = seconds super().__init__(name) def parse(self, data): if self.seconds: time = parse_mdc_time(data[7], data[1], data[2], data[3]) return datetime( int.from_bytes(data[5:7], 'big'), # year data[4], data[0], # month, day time.hour, time.minute, time.second ), 8 time = parse_mdc_time(data[6], data[1], data[2]) return datetime( int.from_bytes(data[4:6], 'big'), # year data[3], data[0], # month, day time.hour, time.minute, time.second ), 7 def pack(self, value): day_part, hour, minute, second = pack_mdc_time(value.time()) return ( bytes([value.day, hour, minute]) + (self.seconds and bytes([second]) or b'') + bytes([value.month]) + int.to_bytes(value.year, 2, 'big') + bytes([day_part])) class Bitmask(Enum): def parse(self, data): return parse_enum_bitmask(self.enum, data[0]), 1 def pack(self, data): if not isinstance(data, Sequence): raise ValueError('Bitmask values must be sequence') return [pack_bitmask(data)] class IPAddress(Field): def parse(self, data): return '.'.join(str(int(x)) for x in data[:4]), 4 def pack(self, data): rv = tuple(int(x) for x in data.split('.')) if not len(rv) == 4 or not all(0 <= x < 256 for x in rv): raise ValueError('Invalid IP address', data) return rv class VideoWallModel(Field): def parse(self, data): return parse_videowall_model(data[0]), 1 def pack(self, data): if isinstance(data, str): rv = tuple(int(x) for x in data.split(',')) elif not isinstance(data, (tuple, list)): raise TypeError('Video wall model must be Tuple[int, int] or' 'comma-separated string') if not len(rv) == 2: raise ValueError('Invalid video wall model', data) return pack_videowall_model(rv) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1736112699.0 python-samsung-mdc-1.15.0/samsung_mdc/utils.py0000644000175100002000000000310314736575073020753 0ustar00runnerdockerfrom enum import Enum from datetime import datetime def _bit_unmask(val, length=None): rv = tuple(reversed(tuple(int(x) for x in tuple('{0:0b}'.format(val))))) if length and len(rv) < length: return rv + ((0,) * (length - len(rv))) return rv def parse_enum_bitmask(enum, value): """ Returns tuple of enum values, which was set to 1 in bitmask """ return tuple( enum(i) for i, x in enumerate( _bit_unmask(value, length=len(enum))) if x ) def pack_bitmask(values): rv = 0 for val in values: if isinstance(val, Enum): val = val.value rv |= (1 << val) return rv def parse_mdc_time(day_part, hour, minute, second=0): """ PM = 0x00 AM = 0x01 """ return datetime.strptime( f'{day_part and "AM" or "PM"} {hour} {minute} {second}', '%p %I %M %S').time() def pack_mdc_time(time): time = time.strftime('%p %I %M %S').split() return int(time[0] == 'AM'), int(time[1]), int(time[2]), int(time[3]) def repr_hex(value): # return ' '.join(f'{x:02x}/{x}' for x in value) return ':'.join(f'{x:02x}' for x in value) def parse_hex(value): return value and bytes(int(x, 16) for x in value.split(':')) or b'' def parse_videowall_model(value): """ Splits coordinates byte (with y, x representation) to (x, y) tuple """ return divmod(value, 1 << 4)[::-1] def pack_videowall_model(value): """ Converts (x, y) tuple to one coordinates byte (with y, x representation) """ return [(value[1] * 16) + value[0]] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1736112699.0 python-samsung-mdc-1.15.0/samsung_mdc/version.py0000644000175100002000000000002714736575073021302 0ustar00runnerdocker__version__ = '1.15.0' ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1736112705.0172837 python-samsung-mdc-1.15.0/setup.cfg0000644000175100002000000000015114736575101016552 0ustar00runnerdocker[flake8] max-line-length = 80 exclude = .git,__pycache__,env,venv [egg_info] tag_build = tag_date = 0 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1736112699.0 python-samsung-mdc-1.15.0/setup.py0000644000175100002000000000354514736575073016465 0ustar00runnerdockerfrom setuptools import setup, find_packages exec(open('samsung_mdc/version.py').read()) requires = [ 'click', # tested: click >=7,<=8 'jinja2', ] serial_requires = [ 'pyserial-asyncio' # tested: pyserial==3.5; pyserial-asyncio==0.5 ] # TODO: leaving serial in default dependencies # just not to make README and pipx usage too complicated requires += serial_requires test_requires = [ 'pytest', 'pytest-asyncio', 'nest-asyncio', ] setup( name='python-samsung-mdc', version=__version__, # noqa description=('Samsung Multiple Display Control (MDC) ' 'protocol implementation (asyncio library + CLI interface)'), long_description=open('README.md').read(), long_description_content_type='text/markdown', license='BSD-3-Clause', # https://spdx.org/licenses/BSD-3-Clause.html license_files=['LICENSE'], classifiers=[ 'License :: OSI Approved :: BSD License', 'Intended Audience :: Developers', 'Intended Audience :: System Administrators', 'Intended Audience :: Telecommunications Industry', 'Programming Language :: Python', 'Programming Language :: Python :: 3', 'Development Status :: 5 - Production/Stable', 'Operating System :: OS Independent', 'Topic :: Multimedia :: Video :: Display', 'Topic :: Home Automation', 'Topic :: Utilities', ], python_requires='>=3.7,<4.0', author='Victor Gavro', author_email='vgavro@gmail.com', url='http://github.com/vgavro/samsung-mdc', keywords=['samsung', 'mdc'], packages=find_packages(), install_requires=requires, extras_require={ 'test': test_requires, 'serial': serial_requires, 'all': serial_requires, }, entry_points={ 'console_scripts': [ 'samsung-mdc=samsung_mdc.cli:cli', ], }, )