Jump to content

Gunners TekZone

Eurobricks Citizen
  • Posts

    210
  • Joined

Everything posted by Gunners TekZone

  1. Ah, yes... Seems I totally missed that post. Sorry.
  2. Thank You! Your work made it all possible. FYI, after some experimenting, I found that with having both my script and your .dll in the same folder, all I needed for the clr path is: clr.AddReference(r'LegoClassB') # LegoClassB.dll needs to be in same folder as this script And as a test, I them renamed the folder... Worked. I then copied it over to my Windows 7 PC (just for kicks... BUT! It worked there as well!!) So this MQTT script has been tested as fully functional on Windows 10(64) and Windows 7(32) I likey! I updated my prior post with a zip file containing my script and your .dll
  3. AH HA!... Could this be the first time a LEGO DACTA Interface-B has been used to communication over a network via MQTT??? Why, YES!!... Thanks to @Bliss's enormous efforts developing the Python Interface code, another individual's efforts on Reddit helping me with a MQTT publishing issue on another LEGO project, and my sacrificing hours of precious sleep for some rair, focused, neuron activity (I'll pay for it later) pleading for this Interface-B to work, darnit, just work will ya... ah... er... anyhow... Again, YES... It just might be so Here is the script that I have tested on Windows10(64) and Windows7(32) NOTE: You MUST have the LegoClassB.dll, file from @Bliss's fabulous work, inside the same folder as this script. That file is really the heart of this project! I have put both in this link: MQTT Interface-B import random import time from timeloop import Timeloop from datetime import timedelta from paho.mqtt import client as mqtt_client import clr clr.AddReference(r'LegoClassB') # LegoClassB.dll needs to be in same folder as this script from LegoClassB import LegoInterfaceB ############################################################################## # GLOBAL VARIABLES / SETUP # ############################################################################## # -----------------------# # MQTT Configuration # # -----------------------# broker = 'xxx.xxx.xxx.xxx' # IP of your broker port = 1883 # Port for MQTT (default is 1883) topic = "test/relay" # Topic to subscribe/publish to # We create a random ID just so multiple clients on the same broker # don't stomp on each other: client_id = f'subscribe-{random.randint(0, 100)}' # Username / password for the broker (if needed) username = 'YOURUSERNAME' password = 'YORPASSWORD' # --------------------------# # Interface-B Configuration # # --------------------------# print("LEGO config") Lego1=LegoInterfaceB() Lego1.ComPort="COM1" Lego1.StartLego() time.sleep(1) Lego1.Out[1].Pow = 7 Lego1.Out[1].Dir = False print("Lego 1 Running: ", Lego1.IsRunning) # This will indicate the relay state at startup: buttonFlag = 0 # -----------------------# # The Big Global Client # # -----------------------# # We declare 'client' here. We'll fill it in `connect_mqtt()`. # This is so that any function that needs it can do `global client`. client = None ############################################################################## # MQTT SETUP FUNCTIONS # ############################################################################## def connect_mqtt(): # Connects to the MQTT broker exactly once and assigns the resulting # mqtt_client.Client instance to the global 'client'. # Returns the same client, but we also store it in the global variable. global client # Tells Python we're referring to the global variable above. def on_connect(client, userdata, flags, rc): #Callback that fires when a connection to the broker is established. if rc == 0: print("Connected to MQTT Broker!") else: print(f"Failed to connect. Return code={rc}") # Actually create the client object client = mqtt_client.Client(mqtt_client.CallbackAPIVersion.VERSION1, client_id) # May trigger a depreciation warning #client = mqtt_client.Client(client_id) # Assign credentials client.username_pw_set(username, password) # Assign the on_connect callback client.on_connect = on_connect # Connect to the broker client.connect(broker, port) return client def subscribe(): # Sets up subscription to a topic and defines how incoming messages are handled. global client # We'll need the client to call subscribe on it def on_message(client, userdata, msg): """ Callback that fires when a message arrives on a topic we subscribed to. """ print(f"Received `{msg.payload.decode()}` from `{msg.topic}` topic") # If the received payload is '1', turn the port ON else OFF # (Adjust logic if your messages are strings other than '1'/'0') if msg.payload.decode() == '1': Lego1.Out[1].On = True print("Light ON") else: Lego1.Out[1].On = False print("Light OFF") # Subscribe to the desired topic client.subscribe(topic) # Assign the on_message callback client.on_message = on_message ############################################################################## # FUNCTIONS TO PUBLISH MESSAGES # ############################################################################## def publish_message(): # Publishes the current state of 'buttonFlag' to the MQTT broker. #global client # We'll need the global client print("Publishing message:", buttonFlag) client.publish(topic, buttonFlag) ############################################################################## # BUTTON-RELATED CODE # ############################################################################## # Setup a periodic check for button press ButtonCheck = Timeloop() @ButtonCheck.job(interval=timedelta(seconds=1)) def sample_job_every_1s(): # Will cycle if left pressed # Called automatically when the button on port '1' is pressed. # We'll flip the buttonFlag from 1 to 0 or 0 to 1 and then publish. global buttonFlag # Tells Python to use the global variable if Lego1.Inp[1].On: print("Button Pressed!") # Flip the state of buttonFlag if buttonFlag == 1: buttonFlag = 0 Lego1.Out[1].On = True print("Send ON") else: buttonFlag = 1 Lego1.Out[1].On = False print("Send OFF") # Now publish our updated buttonFlag via MQTT publish_message() ############################################################################## # MAIN LOOP (run) # ############################################################################## def run(): # Main entry-point for our script. Connect to MQTT, subscribe, # and start the loop forever. global client # We'll store the client in the global variable # 1. Connect once connect_mqtt() # 2. Subscribe once subscribe() # 3. Start an infinite loop to process messages and keep the connection open client.loop_forever() ############################################################################## # START THE PROGRAM HERE # ############################################################################## if __name__ == '__main__': ButtonCheck.start(block=False) # This calls the timed button check function run()
  4. I parked your LegoExample_01.py and LegoClassB.dll in a separate folder. Adjusted for my device settings and proper path... And that Worked!!... Thanks! Words just don't have the same impact So many tricks and quirks to remember, based on Language, version, OS, PEBKAC, cats interrupting train of thought (Yes, I will even blame my beloved furbabies for my issues, Hah!)... It is a wonder I like modern programming at all Now... MQTT... Dare I try that again??
  5. I installed Python 3.8.6 on my Win7 system. I tried to run this... import clr clr.AddReference(r"C:\Users\Gunner\Desktop\LegoClassB\bin\Release\LegoClassB") from LegoClassB import LegoInterfaceB Lego1 = LegoInterfaceB() Lego1.ComPort="COM1" Lego1.StartLego() Lego1.IsRunning Lego1.Out[1].On=True And the error I ran into on the Win7, Thonny, setup was a long ramble about ... something... Same error when trying to run in the CLI System.NotSupportedException: An attempt was made to load an assembly from a network location which would have caused the assembly to be sandboxed in previous versions of the .NET Framework. This release of the .NET Framework does not enable CAS policy by default, so this load may be dangerous. If this load is not intended to sandbox the assembly, please enable the loadFromRemoteSources switch. See http://go.microsoft.com/fwlink/?LinkId=155569 for more information. The above exception was the direct cause of the following exception: Traceback (most recent call last): File "C:\Users\Gunner\Downloads\LegoExample_01.py", line 2, in <module> clr.AddReference(r"C:\Users\Gunner\Desktop\LegoClassB\bin\Release\LegoClassB") System.IO.FileLoadException: Could not load file or assembly 'file:///C:\Users\Gunner\Desktop\LegoClassB\bin\Release\LegoClassB.dll' or one of its dependencies. Operation is not supported. (Exception from HRESULT: 0x80131515) File name: 'file:///C:\Users\Gunner\Desktop\LegoClassB\bin\Release\LegoClassB.dll' ---> System.NotSupportedException: An attempt was made to load an assembly from a network location which would have caused the assembly to be sandboxed in previous versions of the .NET Framework. This release of the .NET Framework does not enable CAS policy by default, so this load may be dangerous. If this load is not intended to sandbox the assembly, please enable the loadFromRemoteSources switch. See http://go.microsoft.com/fwlink/?LinkId=155569 for more information. at System.Reflection.RuntimeAssembly._nLoad(AssemblyName fileName, String codeBase, Evidence assemblySecurity, RuntimeAssembly locationHint, StackCrawlMark& stackMark, IntPtr pPrivHostBinder, Boolean throwOnFileNotFound, Boolean forIntrospection, Boolean suppressSecurityChecks) at System.Reflection.RuntimeAssembly.nLoad(AssemblyName fileName, String codeBase, Evidence assemblySecurity, RuntimeAssembly locationHint, StackCrawlMark& stackMark, IntPtr pPrivHostBinder, Boolean throwOnFileNotFound, Boolean forIntrospection, Boolean suppressSecurityChecks) at System.Reflection.RuntimeAssembly.InternalLoadAssemblyName(AssemblyName assemblyRef, Evidence assemblySecurity, RuntimeAssembly reqAssembly, StackCrawlMark& stackMark, IntPtr pPrivHostBinder, Boolean throwOnFileNotFound, Boolean forIntrospection, Boolean suppressSecurityChecks) at System.Reflection.RuntimeAssembly.InternalLoadFrom(String assemblyFile, Evidence securityEvidence, Byte[] hashValue, AssemblyHashAlgorithm hashAlgorithm, Boolean forIntrospection, Boolean suppressSecurityChecks, StackCrawlMark& stackMark) at System.Reflection.Assembly.LoadFrom(String assemblyFile) at Python.Runtime.AssemblyManager.LoadAssemblyPath(String name) at Python.Runtime.CLRModule.AddReference(String name)
  6. Oh my... do I feel like a dodo... I like having a tight focus when I can, but sometimes a laser sharp focus is not always good. Of course, since CLI works, then there is nothing wrong with my path or the files. Doh!! Basically, since the CLI method only works line by line. I did briefly look at .bat files with Python scripts... but I am not successful there yet either. Thus my fixation on Thonny... But I did try briefly with IDLE (I don't like using it), but it has the exact same failure. So it doesn't appear IDE related. What oh what is remaining?
  7. Usually I found Thonny throwing an error in that case. But I double checked, I have redownload the file, parked it at the root of C: to make the path simpler. And I even had a snack before attempting again... But it is Just. Not. Working. For. Me. :( import clr clr.AddReference(r'C:\LegoClassB\bin\Release\LegoClassB') from LegoClassB import LegoInterfaceB >>> %Run LegoExample_01.py Traceback (most recent call last): File "C:\Users\Gunner\Downloads\LegoExample_01.py", line 3, in <module> from LegoClassB import LegoInterfaceB ImportError: cannot import name 'LegoInterfaceB' from 'LegoClassB' (unknown location) >>> I have tried " ' / \ r" r' \\ etc. adnausm... It doesn't appear to be syntax related. It has to be something with my file system??? I even tried to start over with a different Python, Thonny, etc. install on my Windows 7 PC... But that turned into its own unmitigated disaster... Due being "Too old, go away!" (I paraphrase) ANYHOW... Again... I need to take a break. I am learning, but not sure how much fun I am having, heh. ====================================================================================================================================== OK... one last ditch attempt to understand... and a random @rs guess I opened both the LegoClassB files in Notepad++... mostly nonsense. But I do see path references to your setup. It is possible the code in whatever of these programs get "imported" is getting fouled on that???? Or is this grasping at straws?
  8. I think, just putting in the correct path to the python.exe worked for that... since now I stall out here instead: import time import clr clr.AddReference(r"C:\Users\Gunner\Desktop\LegoClassB\bin\Release\LegoClassB") from LegoClassB import LegoInterfaceB Lego1 = LegoInterfaceB() Lego1.ComPort="COM1" Lego1.StartLego() Lego1.IsRunning Lego1.Out[1].On=True time.sleep(2) Lego1.Out[1].On=False time.sleep(2) Lego1.Out[1].On=True time.sleep(2) Lego1.Out[1].On=False Lego1.StopLego() >>> %Run LEGO_IntB_IDE_Test.py Traceback (most recent call last): File "C:\Users\Gunner\Desktop\LEGO_IntB_IDE_Test.py", line 4, in <module> from LegoClassB import LegoInterfaceB ImportError: cannot import name 'LegoInterfaceB' from 'LegoClassB' (unknown location) >>> And, Yes... that is the correct path, as it works in the CLI (well, not as a coherent program, but line by line. And the sleep parts are meaningless when waiting for the next command anyhow :P )
  9. Ah Ha... I missed that part... I took Local Python 3 as literally the one Windows recognised. I'll try that. Thanks
  10. Hah... I deal with ME/CFS... Thus living on disability income and mostly homebound. When I am not sleeping most of my hours away, and feeling well enough to think on the wakening ones... ALL I tend to do is work with LEGO, particularly Mindstorms/Technic/Trains etc. as it seems to help me keep some mental focus. I like static LEGO builds, but my preference is things that do something, and help learn me some smrt tech skillz (as the kids never say). Better than endless YouTube or Netflix... But that sometimes happens.
  11. FYI... Using the CLI, I run into times when nothing happens. But I have found simply typing Lego1.IsRunning without the print() will show True or False. When false, it seems though only way to get it back running is to close down the CLI and start over. Ideas?
  12. Thanks for the info. FYI, so far I haven't been able to make anything run in Thonny, thanks to NOT being able to import clr (as I understand it, it allows .net to play with Python). I only have the one version of Python installed, and windows path point correctly to it... Traceback (most recent call last): File "C:\Users\Gunner\Desktop\LEGO_IntB_IDE_Test.py", line 1, in <module> import clr ModuleNotFoundError: No module named 'clr' The Googling shall continue...
  13. WORKED PERFECTLY!!! In CLI. I will see if I can get it running in Thonny...
  14. Too funny... That is what I have been wanting to suggest to you, since I discovered you were using Python in your scripting application. But I didn't want to detract from your vision, or whatever motivates you. That said, Yes, I really want to see a proper importable Python module for use with vintage LEGO over a users choice of IDE. And in this case the Interface-B seems the best primary option, but possibly something that will also work (perhaps in parallel with the Interface-B??) to remotely control RCX, possibly (if needed) with modified firmware that can act to identify individual units that communicate to Python via IR. I did look at that Shamlian code myself, many weeks ago. The module does seem to have all the input/output code, and seems well commented, but I still wasn't able to make enough headway to understanding it, without some functional examples to work off of that supplied context & syntax for commands, ext. The one included example seems mostly useless, and doesn't stay online anyhow. I didn't bother pursuing it as it seems like it was a one-shot attempt and hasn't been in development for almost 10 years, so probably lots of issues with more current versions of Python. Thanks for this... But you are far ahead of myself :) ... I will definitely see what I can do with your DLL (DLL, class, module?? See I don't even understand the proper terminology, Hah!)
  15. It was working with your prior version Out of a combo of curiosity and a niggling of self doubt :P I restored the zipped "prior version" from my recycle bin. And YES!!! I am not totally crazy... That path and loading of all the modules worked again. I don't know what else to say? The timestamp on the LegoScriptB.exe in that script folder shows January ‎3, ‎2025, ‏‎3:51:26 PM if that helps narrow down the one I am referring too. Meanwhile... I realised that I could make the same change on my technically functional Thonny version and see what, if any, errors it makes. File "C:\Users\Gunner\Desktop\Interface-B MQTT Test.py", line 73, in connect_mqtt client = mqtt_client.Client(mqtt_client.CallbackAPIVersion.VERSION1, client_id) NameError: name 'mqtt_client' is not defined Line 73 being: client = mqtt_client.Client(mqtt_client.CallbackAPIVersion.VERSION1, client_id) I don't know why from paho.mqtt import client as mqtt_client throws this error: future feature is not defined:annotations in IronPython, but it is messing up my brain cells with confusion And... Off to sleep... It has been a long many hours learning me some Python... Partial results are better than nothing I guess.
  16. Nice. It worked as is, and without the hardware to test on The light values were arbitrary anyhow, it worked as long as there was a significant change between "blocked from external light source" and "not blocked from external light source".
  17. I don't think so... After getting the random module to work, I still needed to get the paho.mqtt module to import. And that ended up having me uninstall all older versions of Python on my PC, reinstall the latest version, along with the paho.mqtt, and find the path that worked for that new version as well. That, and I most likely had shut down your program at least once, while shuffling stuff around over the next few hours of troubleshooting :) Anyhow... I needed a break from trying to figure out if it wasn't connecting to my broker because... A) Needing to make the change from: from paho.mqtt import client as mqtt_client (which didn't work on your program) to import paho.mqtt (which did work). But who knows how that changed the client code as written. B) Or if the script simply can't link to the networking through your program... But can't supply any error, as you had stated, and I discovered, is currently limited to minimal, or simply nothing. I suspected A) but without errors to guide, I was at a loss to proceed further.
  18. That is the correct path, and did work on your prior (to the syntax changes, etc.) version. As per your solution to the original "No module named 'random'" issue. But no worries... I realised that trying to do something rather fansnazy like MQTT with the Interface-B, while you are still developing and making changes, might not be a good idea yet :D So I will go back to the semi-autonomous train port, from TC LOGO to python code... No fancy stuff there, that I can't understand without hours of Googling... Hah!
  19. No. That is the solution to this error: File "C:\Users\Gunner\Desktop\Interface-B MQTT Test.py", line 3 sys.path.append("C:\Users\Gunner\AppData\Local\Programs\Python\Python313\Lib\site-packages") ^ SyntaxError: (unicode error) 'unicodeescape' codec can't decode bytes in position 2-3: truncated \UXXXXXXXX escape It was working with your prior version... I chose that path because all other modules I might use, like paho.mqtt, is also there for when working with other Python editors.
  20. Um... unless I am getting too tired... this latest version no longer allows... import sys sys.path.append(...) It seems to just ignore it and claim it can't find a module (in my case random, again, and so on) print("Start script") import sys sys.path.append("C:\\Users\Gunner\AppData\Local\Programs\Python\Python313\Lib\site-packages") import random import paho.mqtt Output Log: No module named 'random' Start script
  21. Ah, thanks... I eventually saw the IronPython bit, but turns out the version didn't seem to be the issue. I edited my prior post explaining such...
  22. Allright... I deleted the prior contents of this post as it was getting too long as I made discoveries. Long story still long... and ongoing... I am finding issues and workarounds "porting" functional code from Thonny to Interface B Scripting. Both running on my Win10 PC. BUT... it is such a tedious job, discovering issues, blindly guessing at solutions (some even worked) and generally embedding keycaps into my forehead, that I will just wait until I either figure it out and post the resulting Interface-B MQTT code... or give up :P Meanwhile... this is the code I have running in Thonny (but obviously not doing much of anything in the physical/LEGO world). print("Start script") import sys sys.path.append("C:\\Users\Gunner\AppData\Local\Programs\Python\Python313\Lib\site-packages") import random from paho.mqtt import client as mqtt_client ############################################################################## # GLOBAL VARIABLES / SETUP # ############################################################################## print("MQTT Start") # -----------------------# # MQTT Configuration # # -----------------------# broker = '192.168.0.17' # IP of your broker port = 1883 # Port for MQTT (default is 1883) topic = "test/relay" # Topic to subscribe/publish to # We create a random ID just so multiple clients on the same broker # don't stomp on each other: client_id = f'subscribe-{random.randint(0, 100)}' # Username / password for the broker (if needed) username = 'YOURUSERNAME' password = 'YOURPASSWORD' # -----------------------# # Interface-B Configuration # # -----------------------# #print("LEGO config") #Lego1.ComPort="COM1" #Lego1.StartLego() #Lego1.SetPow[out.D] = 7 # This will indicate the relay state: buttonFlag = 0 # -----------------------# # The Big Global Client # # -----------------------# # # We declare 'client' here. We'll fill it in `connect_mqtt()`. # This is so that any function that needs it can do `global client`. client = None ############################################################################## # MQTT SETUP FUNCTIONS # ############################################################################## def connect_mqtt(): """ Connects to the MQTT broker exactly once and assigns the resulting mqtt_client.Client instance to the global 'client'. Returns the same client, but we also store it in the global variable. """ global client # Tells Python we're referring to the global variable above. def on_connect(client, userdata, flags, rc): """ Callback that fires when a connection to the broker is established. """ if rc == 0: print("Connected to MQTT Broker!") # As an example, we publish a greeting on a different topic: client.publish("test/time", "HI from PI!") else: print(f"Failed to connect. Return code={rc}") # Actually create the client object client = mqtt_client.Client(mqtt_client.CallbackAPIVersion.VERSION1, client_id) #client = mqtt_client.Client(client_id) # Assign credentials client.username_pw_set(username, password) # Assign the on_connect callback client.on_connect = on_connect # Connect to the broker client.connect(broker, port) return client def subscribe(): """ Sets up subscription to a topic and defines how incoming messages are handled. """ global client # We'll need the client to call subscribe on it def on_message(client, userdata, msg): """ Callback that fires when a message arrives on a topic we subscribed to. """ print(f"Received `{msg.payload.decode()}` from `{msg.topic}` topic") # If the received payload is '1', turn the port ON else OFF # (Adjust logic if your messages are strings other than '1'/'0') if msg.payload.decode() == '1': #Lego1.SetOn[out.D] = True print("Light ON") else: #Lego1.SetOn[out.D] = False print("Light OFF") # Subscribe to the desired topic client.subscribe(topic) # Assign the on_message callback client.on_message = on_message ############################################################################## # FUNCTIONS TO PUBLISH MESSAGES # ############################################################################## def publish_message(): """ Publishes the current state of 'buttonFlag' to the MQTT broker. """ global client # We'll need the global client print("Publishing message:", buttonFlag) client.publish(topic, buttonFlag) ############################################################################## # BUILDHAT BUTTON-RELATED CODE # ############################################################################## def handle_pressed(): """ Called automatically when the button on port '1' is pressed. We'll flip the buttonFlag from 1 to 0 or 0 to 1 and then publish. """ global buttonFlag # Tells Python to use the global variable #if Lego1.InOn[1]: print("Button Pressed!") # Flip the state of buttonFlag if buttonFlag == 1: buttonFlag = 0 #Lego1.SetOn[out.D] = True print("Light ON") else: buttonFlag = 1 #Lego1.SetOn[out.D] = False print("Light OFF") # Now publish our updated buttonFlag via MQTT publish_message() ############################################################################## # MAIN LOOP (run) # ############################################################################## def run(): """ Main entry-point for our script. Connect to MQTT, subscribe, and start the loop forever. """ global client # We'll store the client in the global variable # 1. Connect once connect_mqtt() # 2. Subscribe once subscribe() # 3. Start an infinite loop to process messages and keep the connection open client.loop_forever() ############################################################################## # START THE PROGRAM HERE # ############################################################################## if __name__ == '__main__': run() And these are the issues and solutions I have found so far... future feature is not defined:annotations is "fixed" but this change: from paho.mqtt import client as mqtt_client to import paho.mqtt Who knows if this causes other issues later on... I don't :P One of the many occurances of "No error, but script just stalls" is fixed by this change: if __name__ == '__main__': run() to this while not cancellationToken.IsCancellationRequested: run() And finally... so far... This is where I (and the script, without error) have stalled out: I tried both versions... the uncommented one is a fix due a "Paho MQTT 'Unsupported callback API version' error"... Google is my only friend :D # Actually create the client object client = mqtt_client.Client(mqtt_client.CallbackAPIVersion.VERSION1, client_id) #client = mqtt_client.Client(client_id) I don't know if this stalling out issue is because of the initial import "fix" or perhaps the Interface B Scripting program is not allowing internet access?? Anyhow, that's all for now folks!
  23. Hello. I am attempting to convert a MQTT script I have for use on my BuildHat (a LEGO Powered Up adapter for the Raspberry Pi) to the Interface-B (using its inputs/outputs instead of the BuildHat ones)... Because why not :D I have all the required modules already installed on my PC (and Python 3.10.4)... And tested good using Thonny. However, I instantly ran into import issues... Lego1: Opening Serial Port COM1... Lego1: Serial Port COM1 Opened. Lego1: Sending initialization messsage on COM1... Lego1: Reading initialization answer from COM1... Lego1: Running on COM1 No module named 'random' I also require paho.mqtt ... but as random should already be part of the base Python, and still not loading, I suspect there is more going on.
  24. Hello and Yes. They are blocking commands. But you got me curious, so I started paging through the Reference Guide again (the list of "primitives" are alphabetical) and found a couple that reference parallel processes. The one you are probably interested in is launch This test will run both the sound and lamp at the same time for 5 seconds to test launch [tto "SoundC setleft onfor 50] launch [tto "LampB setleft onfor 50] Despite owning 10 of these little yellow bricks (enough for short yellow brick road to the wizard of LEGO) I really haven't done anything with them (I really didn't like the blocky method, and get bogged down in all the alternatives, of which I can never seem to find the right firmware for an example I am interested in). Anyhow, I digress... I believe the RCX can follow both internal and remote commands at same time. I know the IR remote control (and tower) can start/stop programs and directly control outputs. They can also communicate between themselves via IR (but not sure if that requires alternate firmware or not).
×
×
  • Create New...