Example Python Driver
The client that connects to the created server can be anything that can open and communicate over a network connection via sockets. The API that communicates commands back and forth in the C++ USB serial microcontroller code and over the network is identical. You are welcome to build your own network client if needed, using the API documentation provided.
However, Bottango also provides a fully functional example implementation of a network client that can connect to the network server in Python. If you’re trying to just get up and going fast, the example Python code is fully functional and will be supported for future features.
Example Client
Section titled “Example Client”The example Python network client is located in the .zip installer for Bottango.
- The main entry Python file is located at “/AdvancedFeatures/Bottango Networked Driver/BottangoNetworkDriver.py”
- There are additional required files located at “/AdvancedFeatures/Bottango Networked Driver/src/“
CallbacksAndConfiguration.py
Section titled “CallbacksAndConfiguration.py”The most important file, if you want to quickly modify the Python code to meet your needs is the “CallbacksAndConfiguration.py” file, located in the “src” folder. Though you are welcome to explore the entire implementation, for a lot of use cases, this is the only file you will need to edit.
The first section is where you set basic configuration such as the server IP address and port, firmware version to report, etc.
import src.MainLoop
address = '127.0.0.1' # The server's hostname or IP addressport = 59225 # The port used by the serverlog = True # enable loggingroundSignalToInt = True # treat signal as an int (true) or as a float (false)apiVersion = "CUSTOM9" # API version to send in handshake responseCommand Callbacks
Section titled “Command Callbacks”After the initial configuration variables are a series of convenient callbacks. As stated earlier, though the example driver fully responds to the commands from the desktop app, it doesn’t actually do anything meaningful out of the box with those commands.
As an example, when requested to register a pin servo, it will acknowledge and send the appropriate response back to the desktop app. However, it will not actually try to drive or connect to a PWM servo, as it has no ability to do so. Instead, it calls the related lifecycle callbacks triggered by the commands along with the parameters of the command, and lets you expand what concrete actions to take from there.
The first callback is the callback when an effector is registered:
def handleEffectorRegistered(effectorType, identifier, minSignal, maxSignal, startingSignal): ## !!! put your effector enable / turn on code here (if needed) !!! ## if log: print ("Register " + effectorType + " " + str(identifier))You can see that you are passed parameters such as effectorType, identifier, signal ranges, etc. But the only action it takes out of the box is to log that action if logging is enabled. If you wanted to do your own custom registration actions, this would be the place to do it. You can take advantage of the guarantee that each effector will have a unique identifier for how you create your logic.
There then are additional callbacks for the remaining lifecycle and commanded events for an effector:
def handleEffectorDeregistered(identifier): ## !!! put your effector stop moving / turn off code here (if needed) !!! ## if log: print ("Deregister " + str(identifier))
def handleEffectorSetSignal(effectorType, identifier, signal): ## !!! put your motor driving logic here. Signal will !!! ## ## !!! Only called when the expected signal changes!!! ## if log: print ("Set signal on " + effectorType + " " + str(identifier) + ": " + str(signal))
def handleEffectorSetColor(effectorType, identifier, color): ## !!! put your color/light callback logic here. !!! ## r = color[0] g = color[1] b = color[2] if log: print ("Set color on " + effectorType + " " + str(identifier) + ": (" + str(r) + "," + str(g) +"," + str(b) + ")")
def handleEffectorSetOnOff(effectorType, identifier, on): ## !!! put your on / off event callback logic here. Will !!! ## ## !!! Only called when the expected on / off changes!!! ## if log: print ("Set on/off event on " + effectorType + " " + str(identifier) + ": " + str(on))
def handleEffectorSetTrigger(effectorType, identifier): ## !!! put your trigger event callback logic here. !!! ## if log: print ("Set trigger event on " + effectorType + " " + str(identifier))The callback “handleEffectorSetSignal” will likely be of particular interest. The network driver implements the animation engine, and responds and executes commanded curves. This callback is called anytime an effector’s target signal changes.
Main Loop
Section titled “Main Loop”You can also hook into the main loop, and add your own timing-based effects, or wait on external signals you provide to take an action.
def onMainLoop(): ## !! convenient method if you want to do other things in the main loop ## pass # delete this line if you add something to this callback # if you want to get the time on server, import src.SocketDriverTime # src.SocketDriverTime.getTimeOnServer() returns either time in MS on server or None if disconnected # # # to request stop and disconnect in app: # src.MainLoop.requestQueue.put('\nreqStop\n') # # # to request pause current playing animation: # src.MainLoop.requestQueue.put('\nreqPause\n') # # # to request start playing animation by index (index 2 in example), at time in MS (500ms from start in example): # src.MainLoop.requestQueue.put('\nreqPlay,2,500\n') # # # to request start playing current animation at current time (-1 for index is current animation, -1 at time is current time in app): # src.MainLoop.requestQueue.put('\nreqPlay,-1,-1\n')Examples in the comments show getting the current animation time on the server, or show how to request actions from the network driver back to the desktop app.