Package: nicett6-pp81381
nicett6An asyncio based package to talk to a Nice TT6 control unit for tubular motors using the RS-232 protocolThe Nice TT6 control unit is used to control projector screens, garage doors, awnings and blinds.   It is white labelled by Screen Research as the MCS-N-12V-RS232 projector screen controller and by Beamax as the 11299 projector screen controller.Seethis documentfor the protocol definitionKnown to work with a GANA USB to RS-232 DB9 cable on Windows 10 and on Raspbian StretchContentsBasic Control APIHigh Level Cover APIHigh Level CIW API(to manage a Constant Image Width configuration with a screen and a mask)EmulatorExamplesNotesBasic Control APIOpening a connectionnicett6.tt6_connection.open([serial_port])opens a connection to the TT6 controlled connected toserial_portnicett6.tt6_connection.open_connection([serial_port])opens a connection and acts as an async context managerIfserial_portis not supplied or isNonethen an intelligent guess will be made as to the right parameter depending on the platformThe serial_port parameter can be anything that can be passed toserial.serial_for_url().  E.g./dev/ttyUSB0(Linux)COM3(Windows)socket://192.168.0.100:50000(if you are using a TCP/IP to serial  converter)Returns aTT6ConnectionExample:asyncwithopen_connection(serial_port)asconn:tt_addr=TTBusDeviceAddress(0x02,0x04)writer=conn.get_writer()awaitwriter.send_hex_move_command(tt_addr,0xE0)TT6ConnectionA class that allows multiple readers and writers to a single serial connection.The lifecycle of the connection is managed by the client as opposed to closing upon receipt of an EOF from the device.
If the connection is disconnected by either end then readers wait for the next message and writers will discard any messages sent.
Once the connection is re-connected then normal service resumes.  The client can terminate the connection by callingclose().SeeOpening a connectionfor information on opening a connection.PropertyDescriptionis_connectedIndicates whether the connection is connectedMethodDescriptionconnect()Establish or re-establish the connection.disconnect()Break the connection but do not stop any readers or writers.close()Stop and remove all readers (they will stop iterating).   Disconnect.add_reader()Returns a new reader object.If the connection was created byopen_connectionthen this will be aTT6Readerobject.The serial connection retains a weak reference to the reader in order to keep it updated.  A reader that is no longer needed can either be dereferenced or explicitly removed.remove_reader(reader)Stops thereaderobject from receiving any further messagesget_writer()Returns a new writer object.   If the connection was created byopen_connectionthen this will be aTT6Writerobject.The base class manages contention between multiple potential clients of the same connection.Writer objects do not take any resources and can simply be dereferenced when finished withprocess_request(coro, [time_window])Send a command and collect the response messages that arrive in time_windowTTBusDeviceAddressA simple class that represents the address of a TTBus device - to be used fortt_addrparamtersSupports comparison with other objects of the same classCan be used as a key in a mapping typePropertyDescriptionaddressthe address of the device on the TTBusnodethe device node (usually 0x04)as_tuplea tuple of(address, node)Example:tt_addr=TTBusDeviceAddress(0x02,0x04)TT6ReaderA reader that will collect all decoded messages received on theTT6Connectionin a queue until it is removedATT6Readeris an asynchronous iterator returningresponse message objectsUsage:asyncformsginreader:# Do something with msgResponse message classesAckResponseSent by the controller to acknowledge receipt of a simple commandPropertyDescriptiontt_addrtheTTBusDeviceAddressof the TTBus devicecmd_codethe command being acknowledgedHexPosResponseSent by the controller in response to aREAD_POScommandPropertyDescriptiontt_addrtheTTBusDeviceAddressof the TTBus devicecmd_codethe command being acknowledgedhex_posthe position as a value between 0x00 (fully down) and 0xFF (fully up)PctPosResponseSent by the controller in response to a "web position request"PropertyDescriptiontt_addrtheTTBusDeviceAddressof the TTBus deviceposthe position as a value between 0 (fully down) and 1000 (fully up)InformationalResponseAn informational response from the controllerTypically used to acknowledge a non-device-specific command such asWEB_ONorWEB_OFFPropertyDescriptioninfothe informational messageErrorResponseAn error response from the controllerPropertyDescriptionerrorthe error messageTT6WriterMethodDescriptionsend_web_on()Send the WEB_ON command to the controller to enable web commands and to instruct the controller to send the motor positions as they movesend_web_off()Send the WEB_OFF command to the controller to disable web commands and to instruct the controller not to send the motor positions as they movesend_simple_command(tt_addr, cmd_name)Sendcmd_nameto the TTBus device attt_addrSee the table below for a list of all validcmd_namevaluessend_hex_move_command(tt_addr, hex_pos)Instruct the controller to move the TTBus device attt_addrtohex_poshex_posis a value between 0x00 (fully down) and 0xFF (fully up)send_web_move_command(tt_addr, pos)Instruct the controller to move the TTBus device attt_addrtoposposis a value between 0 (fully down) and 1000 (fully up)Out of range values forposwill be rounded up or down accordinglyWeb commands must be enabled for this command to worksend_web_pos_request(tt_addr)Send a request to the controller to send the position of the TTBus device attt_addrWeb commands must be enabled for this command to workCommand CodesCommand codes forsend_simple_command:CodeMeaningREAD_POSRequest the current positionController will send a value between 0x00 (fully down) and 0xFF (fully up)STOPStopMOVE_DOWNMove downMOVE_UPMove upMOVE_POS_<n>Move to presetnwherenis between 1 and 6STORE_POS_<n>Store current position to presetnwherenis between 1 and 6DEL_POS_<n>Clear presetnwherenis between 1 and 6MOVE_DOWN_STEPMove down the smallest possible step (web pos is not reported by controller)MOVE_UP_STEPMove up the smallest possible step (web pos is not reported by controller)Usage:writer=conn.get_writer()writer.send_web_move_command(1.0)Usage ofprocess_request():coro=writer.send_simple_command(tt_addr,"READ_POS")messages=awaitwriter.process_request(coro)Note that there could be unrelated messages received if web commands are enabled or if another command has just been submittedHigh level Cover APIA set of components to provide a high level interface to manage a Cover.    Could be used to control a retractable projector screen or a garage door.  Designed for use with Home Assistant.ComponentDescriptionCoverManagerA class that manages the controller connection and a set of coversCan be used as an async context managerCoverA sensor class that can be used to monitor the position of a coverTT6CoverClass that sends commands to aCoverthat is connected to the TTBusPostMovementNotifierHelper class that resets a cover to idle after movement has stoppedExample (also seeexample3.pybelow):asyncdeflog_cover_state(cover):try:whilecover.is_moving:_LOGGER.info(f"drop:{cover.drop}; "f"is_going_up:{cover.is_going_up}; "f"is_going_down:{cover.is_going_down}; ")awaitasyncio.sleep(1.0)exceptasyncio.CancelledError:passasyncdefexample(serial_port):tt_addr=TTBusDeviceAddress(0x02,0x04)max_drop=2.0asyncwithCoverManager(serial_port)asmgr:tt6_cover=awaitmgr.add_cover(tt_addr,Cover("Cover",max_drop))message_tracker_task=asyncio.create_task(mgr.message_tracker())logger_task=asyncio.create_task(log_cover_state(tt6_cover.cover))awaittt6_cover.send_pos_command(900)awaitwait_for_motion_to_complete([tt6_cover.cover])awaittt6_cover.send_simple_command("MOVE_UP")awaitwait_for_motion_to_complete([tt6_cover.cover])logger_task.cancel()awaitlogger_taskawaitmessage_tracker_taskCoverManagerA class that manages the connection and a set of coversCan be used as an async context managerConstructor parameters:ParameterDescriptionserial_portThe serial port to use.  SeeOpening a connectionfor the valid values.PropertyDescriptionserial_portThe serial port in usett6_coversAll of theTT6Coverobjects that have been added (the returned object is aValuesViewonto the internal dict)MethodDescriptionopen()Open the connectionCalled automatically if the object is used as a context managerclose()Close the connectionCalled automatically if the object is used as a context managermessage_tracker()A coroutine that must be running in the background for the manager to be able to track cover positionsadd_cover(tt_addr, cover)Add a cover to be managedtt_addr is the TTBus address of the coverThe connection must be open so that the initial position can be requestedremove_covers()Remove all covers and clean upCoverA sensor class that can be used to monitor the position of a cover.  Could be used to monitor a retractable projector screen or a garage door.  Designed for use with Home Assistant.Cover is anAsyncObservableand will notify any attached objects of typeAsyncObserverif the position is changedConstructor parameters:ParameterDescriptionnamename of cover (for logging purposes)max_dropmaximum drop of cover in metresExample:cover=Cover("Screen",2.0)Has the following properties and methods:PropertyDescriptionposthe cover position (0 = fully down, 1000 = fully up)dropdrop in metres (0.0 = fully up, max_drop = fully down)is_movingreturns True if the cover has moved recentlyis_fully_upreturns True if the cover is fully upis_fully_downreturns True if the cover is fully downis_going_upreturns True if the cover is going upwill only be meaningful after the position has been set by the first POS message coming back from the cover for a movementis_going_downreturns True if the cover is going downwill only be meaningful after the position has been set by the first POS message coming back from the cover for a movementMethodDescriptionset_posSet the position (0 = fully down, 1000 = fully up) - asyncWill notify observers of the state changemoved()Called to indicate movementWhen initiating movement, callmoved()so thatis_movingwill be meaningful in the interval before the first POS message comes back from the coverWill notify observers of the state changeset_idle()Called to indicate that the cover is idleAfter detecting that the cover is idle, callset_idle()so that the next movement direction will be correctly inferredWill notify observers of the state changeHelperDescriptionwait_for_motion_to_complete(covers)Waits for motion of a list of covers to completeHas side effect of notifying observers of the cover when it goes idleTT6CoverClass that sends commands to aCoverthat is connected to the TTBusIntended to be constructed byCoverManager.add_cover()PropertyDescriptiontt_addrthe TTBus address of the CovercovertheCoverhelperwriterthe low levelTT6WriterMethodDescriptionsend_pos_request()Send a POS request to the controllersend_pos_command(pos)Send a POS command to the controller to set the position of the Cover toposposshould be between 0 (fully down) and 1000 (fully up)Out of range values forposwill be rounded up/down accordinglysend_hex_move_command()Send a POS command to the controller to set the position of the Cover tohex_poshex_posis a value between 0x00 (fully down) and 0xFF (fully up)send_simple_command(cmd_name)Send asimple commandto the controller for the CoverPostMovementNotifierHelper class that resets a cover to idle after movement has stoppedDocumented here for completeness but intended to be constructed by and internal to theCoverMost state changes of aCoverwill be triggered by the receipt of a POS message.  TheCoverinfers that there is movement when a message is received and infers the direction from the current and previous message.   However, there is no notification that theCoveris idle so thePostMovementNotifierclass detects that there has been no movement for a period and then callsCover.set_idle().  TheCoverwill then notify its observers that it is idle.Whenever theCovermoves, there is a call toCover.moved()which callsPostMovementNotifier.moved().  A task is created that will wait for a period and then set theCoverto idle.   If a task was already running when the movement notification is received then the task will be cancelled and restarted.The task must sleep forCover.MOVEMENT_THRESHOLD_INTERVAL + PostMovementNotifier.POST_MOVEMENT_ALLOWANCEseconds without being cancelled for theCoverto be considered idle.Projector Screen HelpersHelper classes to manage a projector screen composed of multiple covers such as a screen with a maskComponentDescriptionImageDefA class that describes where the image area is located on a cover that is a screenCIWHelperA sensor class that tracks the positions of a screen and maskHas properties to represent the visible image areaImageDefA class that describes where the image area is located on a cover that is a screenConstructor parameters:ParameterDescriptionbottom_border_heightgap in metres between bottom of image and bottom of coverheightheight of imageaspect_ratioaspect ratio of imageExample:image_def=ImageDef(0.05,2.0,16/9)Has the following properties and methods:PropertyDescriptionwidthimplied image widthMethodDescriptionimplied_image_height(target_aspect_ratio)implied height fortarget_aspect_ratioif the width is held constantCIWHelperA sensor class that represents the positions of a screen and maskConstructor parameters:ParameterDescriptionscreenACoversensor object representing the screenmaskACoversensor object representing the maskimage_defAnImageDefobject describing where the image area on the screen cover isProperties:PropertyDescriptionimage_widththe width of the visible image in metresimage_heightthe height of the visible image in metres orNoneif the image is not visibleimage_diagonalthe diagonal of the visible image in metres orNoneif the image is not visibleimage_areathe area of the visible image in square metres orNoneif the image is not visibleimage_is_visibleTrue if the image area is visible orNoneif the image is not visibleaspect_ratioThe aspect ratio of the visible image orNoneif the image is not visibleEmulatorThe package also includes an emulator that can be used for demonstration or testing purposesExample:python -m nicett6.emulatorUsage:usage: python -m nicett6.emulator [-h] [-f FILENAME] [-p PORT] [-w] [-W]
                   [-i cover_name initial_pos]

optional arguments:
  -h, --help            show this help message and exit
  -f FILENAME, --filename FILENAME
                        config filename
  -p PORT, --port PORT  port to serve on
  -w, --web_on          emulator starts up in web_on mode
  -W, --web_off         emulator starts up in web_off mode
  -i cover_name initial_pos, --initial_pos cover_name initial_pos
                        override the initial position for coverA sampleconfig.jsonfile is provided in theemulator/configfolderSample config:{"web_on":false,"covers":[{"address":2,"node":4,"name":"screen","step_len":0.01,"max_drop":1.77,"speed":0.08},{"address":3,"node":4,"name":"mask","step_len":0.01,"max_drop":0.6,"speed":0.08}]}ExamplesThe following examples can be used in conjunction with theEmulatorexample2.py- shows how to use theBasic Control APIexample3.py- shows how to use theHigh Level Cover APIUtilitiesMovement Timing LoggerThe scriptmovement_timing_logger.pycan be used to see how often the controller publishes POS messages as it moves.   It will move the specified Cover down and then back up and log the time between messages.   This can be used to tuneCover.MOVEMENT_THRESHOLD_INTERVALso thatCover.is_movingis accurate.usage: movement_timing_logger.py [-h] [-s SERIAL_PORT] [-a {2,3}]

optional arguments:
  -h, --help            show this help message and exit
  -s SERIAL_PORT, --serial_port SERIAL_PORT
                        serial port
  -a {2,3}, --address {2,3}
                        device addressNotesEnd of Line (EOL) charactersThe protocol definition specifies that all messages end in a carriage return character but in practice the controller seems to use carriage return plus line feed.For convenience the API can handle either.The API will write to the controller with messages ending in carriage returnThe API will handle messages from the controller with either line endingThe emulator will handle inbound commands with either line endingThe emulator will send responses ending in carriage return and line feedMeasurement unitsThis document refers to metres as the unit of measurement for all absolute measurements but you can use mm, cm, inches or feet as long as you are consistent