├── .gitignore ├── examples ├── strobe │ ├── README.md │ └── strobe.py ├── setAddress │ ├── README.md │ └── setAddress.py ├── pulse │ ├── README.md │ └── pulse.py ├── simple │ ├── README.md │ └── simple.py └── shellControl │ ├── shellControl.py │ └── README.md ├── credits.md ├── LICENSE.md ├── defines.py ├── README.md ├── dali.py └── daliMaster.py /.gitignore: -------------------------------------------------------------------------------- 1 | __*__ 2 | -------------------------------------------------------------------------------- /examples/strobe/README.md: -------------------------------------------------------------------------------- 1 | # strobe.py 2 | 3 | **Disco strobe** 4 | -------------------------------------------------------------------------------- /examples/setAddress/README.md: -------------------------------------------------------------------------------- 1 | # setAddress.py 2 | 3 | Follow the instruction to set custom DALI address to your ballast. 4 | -------------------------------------------------------------------------------- /examples/pulse/README.md: -------------------------------------------------------------------------------- 1 | # pulse.py 2 | 3 | Be careful! This example is cool but puts the voltage regulator of your [daliMaster](https://www.ebay.it/itm/DALI-Master-hat-for-Raspberry-pi/254220148656?hash=item3b30b39fb0:g:LmwAAOSwNZxczZC9) under stress (in fact this code is used to stress the board). Consider adding an heatsink on LM317. 4 | 5 | But now..**let's try it!** 6 | -------------------------------------------------------------------------------- /credits.md: -------------------------------------------------------------------------------- 1 | CREDITS 2 | 3 | daliMaster library for Raspberry Pi hat by [Loba](mailto:davide.loba@gmail.com) 4 | 5 | Apr 2019 6 | 7 | 8 | daliMaster library for Arduino™ by [Loba](mailto:davide.loba@gmail.com) 9 | 10 | Dec 2017 11 | 12 | _____________________________________________________________________ 13 | 14 | Original Rasberry Pi library by Code Mercenaries 15 | https://www.codemercs.com/en/33-led-warrior/270-led-warrior14-2 16 | 17 | Code Mercenaries Hard- und Software GmbH 18 | Karl-Marx-Strasse 147A 19 | 12529 Schönefeld 20 | Deutschland 21 | 22 | Dec 2016 23 | -------------------------------------------------------------------------------- /examples/simple/README.md: -------------------------------------------------------------------------------- 1 | # simple.py 2 | 3 | Simple program that set one lamp with a direct arc command and query its actual level to confirm the right receiving of the message. Remember to change *DALISA* and *LEVEL* definitions according with your setup. 4 | 5 | Note the use of those methods: 6 | 7 | * **master.clean()** 8 | Read "command" register just to reset "status" register bits of incoming messages. 9 | 10 | * **master.waitForReady()** 11 | Wait for bit *LW14_STATUS_BUSY* and bit *LW14_STATUS_BUS_FAULT* of "status" register to be '0'. 12 | 13 | * **master.waitForTelegram_1()** 14 | Wait for bit *LW14_STATUS_VALID* and bit *LW14_STATUS_1BYTE* of "command" to be '1'. After that, a new message is available into "command" register. 15 | -------------------------------------------------------------------------------- /examples/strobe/strobe.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import os 4 | import sys 5 | sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__))))) 6 | 7 | import daliMaster 8 | import dali 9 | import defines 10 | import time 11 | 12 | master = daliMaster.daliMaster() 13 | 14 | if master.begin() == defines.ERROR : 15 | quit() 16 | 17 | daliAddress = master.getBroadcastAddress(defines.LW14_MODE_DACP) 18 | 19 | while 1: 20 | 21 | 22 | if (master.waitForReady() == defines.ERROR) or (master.indirectCmd(daliAddress, dali.DALI_OFF) == defines.ERROR) : 23 | print("Error..") 24 | time.sleep(0.2) 25 | 26 | 27 | if (master.waitForReady() == defines.ERROR) or (master.indirectCmd(daliAddress, dali.DALI_MAX_LEVEL) == defines.ERROR) : 28 | print("Error..") 29 | time.sleep(0.2) 30 | -------------------------------------------------------------------------------- /examples/pulse/pulse.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import os 4 | import sys 5 | sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__))))) 6 | 7 | import daliMaster 8 | import defines 9 | import time 10 | 11 | master = daliMaster.daliMaster() 12 | 13 | if master.begin() == defines.ERROR : 14 | quit() 15 | 16 | daliAddress = master.getBroadcastAddress(defines.LW14_MODE_DACP) 17 | 18 | while 1: 19 | 20 | for i in range(254, 150, -1): 21 | if (master.waitForReady() == defines.ERROR) or (master.directCmd(daliAddress, i) == defines.ERROR) : 22 | print("Error..") 23 | time.sleep(0.02) 24 | 25 | for i in range(150, 254): 26 | if (master.waitForReady() == defines.ERROR) or (master.directCmd(daliAddress, i) == defines.ERROR) : 27 | print("Error..") 28 | time.sleep(0.02) 29 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Dave Loba 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /examples/simple/simple.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import os 4 | import sys 5 | sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__))))) 6 | 7 | import daliMaster 8 | import defines 9 | import dali 10 | import time 11 | 12 | 13 | DALISA = 7 #set your ballast address 14 | LEVEL = 254 #set level 15 | 16 | master = daliMaster.daliMaster() 17 | 18 | if master.begin() == defines.ERROR : 19 | quit() 20 | 21 | print("\n##### Set lamp {0} to {1} #####".format(DALISA, LEVEL)) 22 | 23 | shortAddress = master.getShortAddress(DALISA, defines.LW14_MODE_DACP); 24 | 25 | if master.waitForReady() == defines.ERROR or master.directCmd(shortAddress, LEVEL) == defines.ERROR : 26 | quit('error') 27 | 28 | print("\n##### Now ask lamp level #####") 29 | time.sleep(1) #wait a moment 30 | 31 | shortAddress = master.getShortAddress(DALISA, defines.LW14_MODE_CMD); 32 | 33 | if master.clean() == defines.ERROR : 34 | quit('error') 35 | 36 | if master.waitForReady() == defines.ERROR or master.queryCmd(shortAddress, dali.DALI_QUERY_ACTUAL_LEVEL) == defines.ERROR : 37 | quit('error') 38 | 39 | if master.waitForTelegram_1() == defines.ERROR : 40 | quit('error') 41 | 42 | res = master.read("command") 43 | 44 | if res == defines.ERROR : 45 | quit('error') 46 | 47 | print("\n##### Actual lamp level is {0}. Done. #####".format(res)) 48 | -------------------------------------------------------------------------------- /defines.py: -------------------------------------------------------------------------------- 1 | # default I2C address 2 | LW14_I2C_ADDRESS = 0x23 3 | 4 | # registers addresses 5 | LW14_REGISTERS = { 6 | "status" : {"name": "status", "address" : 0x00, "mode" : "r", "length" : 1}, 7 | "command" : {"name": "command", "address" : 0x01, "mode" : "rw", "length" : 2}, 8 | "config" : {"name": "config", "address" : 0x02, "mode" : "w", "length" : 1}, 9 | "signature" : {"name": "signature", "address" : 0xF0, "mode" : "r", "length" : 6}, 10 | "address" : {"name": "address", "address" : 0xFE, "mode" : "w", "length" : 2}, 11 | } 12 | 13 | #Answers of 'status' register (see further for specification) 14 | LW14_STATUS_BUS_FAULT = 0x80 #1000 0000 15 | LW14_STATUS_BUSY = 0x40 #0100 0000 16 | LW14_STATUS_OVERRUN = 0x20 #0010 0000 17 | LW14_STATUS_FRAMEERROR = 0x10 #0001 0000 18 | LW14_STATUS_VALID = 0x08 #0000 1000 19 | LW14_STATUS_TIMEFRAME = 0x04 #0000 0100 20 | LW14_STATUS_2BYTE = 0x02 #0000 0010 21 | LW14_STATUS_1BYTE = 0x01 #0000 0001 22 | LW14_STATUS_NONE = 0x00 #0000 0000 23 | 24 | #Special bits for DALI address 25 | LW14_MODE_DACP = 0x00 26 | LW14_MODE_CMD = 0x01 27 | LW14_ADRR_SINGLE = 0x00 28 | LW14_ADRR_GROUP = 0x80 29 | 30 | BUS_TIMEOUT = 2 #sec 31 | DALI_YES = 255 32 | ERROR = -1 33 | 34 | #DALI default values 35 | DALI_DEFAULT_MAX = 254 36 | DALI_DEFAULT_MIN = 1 37 | DALI_DEFAULT_SYSTEM_FAIL = 254 38 | DALI_DEFAULT_POWER_ON = 254 39 | DALI_DEFAULT_FADE_RATE = 7 40 | DALI_DEFAULT_FADE_TIME = 0 41 | 42 | # 0x00 STATUS REGISTER bits: 43 | # 7 - Bus Error Status, 0 = OK, 1 = Bus fault 44 | # 6 - Busy, 0 = ready, 1 = Busy 45 | # 5 - Overrun 46 | # 4 - Frame Error 47 | # 3 - Valid REPLY (reset by reading 0x01) 48 | # 2 - Reply Timeframe, <22 Te since last command 49 | # 1 - 2 Bytes telegram received (reset by reading 0x01) 50 | # 0 - 1 Byte telegram received (reset by reading 0x01) 51 | -------------------------------------------------------------------------------- /examples/setAddress/setAddress.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import os 4 | import sys 5 | sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__))))) 6 | 7 | import time 8 | import daliMaster 9 | import defines 10 | import dali 11 | 12 | 13 | master = daliMaster.daliMaster() 14 | 15 | if master.begin() == defines.ERROR : 16 | quit() 17 | 18 | try: 19 | userSA = int(input('Digit dali address to set (0-63):')) 20 | master.checkRange(userSA, 0, 63) 21 | except ValueError as e: 22 | print(e.args) 23 | quit() 24 | 25 | print("This command will assign {0} as new address with a broadcasted command.".format(userSA)) 26 | print("Be sure that just one ballast will receive it.".format(userSA)) 27 | 28 | res = input('Continue? (digit "Y" for YES)') 29 | 30 | if res == "Y" or res == "y" : 31 | 32 | # 60929 © IEC:2006 33 | # Only one ballast connected separately to the control unit for a simplified addressing method: 34 | # Send first the new short address (0AAA AAA1) by command 257 "DATA TRANSFER REGISTER (DTR)", 35 | # verify the content of the DTR and send command 128 "STORE DTR AS SHORT ADDRESS" two times. 36 | 37 | dtr = master.getShortAddress(userSA, defines.LW14_MODE_CMD) #DTR structure 0AAA AAA1 38 | broadcastAddr = master.getBroadcastAddress(defines.LW14_MODE_CMD); 39 | 40 | 41 | print("\n##### Store new address into DTR #####") 42 | 43 | if master.clean() == defines.ERROR or master.specialCmd(dali.DALI_STORE_TO_DTR, dtr) == defines.ERROR : 44 | quit("unable to set DTR!") 45 | 46 | if master.waitForIdle() == defines.ERROR : 47 | quit("Idle timeout!") 48 | 49 | 50 | print("\n##### Read DTR back and check it again #####") 51 | 52 | if master.clean() == defines.ERROR or master.queryCmd(broadcastAddr, dali.DALI_QUERY_CONTENT_DTR)== defines.ERROR : 53 | quit("unable to read DTR back!") 54 | 55 | if master.waitForTelegram_1() == defines.ERROR : 56 | quit() 57 | 58 | regVal = master.read("command") 59 | if not regVal == dtr : 60 | quit("DTR does not match! {0}:{1}").format(regVal, dtr) 61 | 62 | 63 | print("\n##### DTR and Address match! Now save it as new address #####") 64 | 65 | if master.configCmd(broadcastAddr, dali.DALI_DTR_AS_SHORT_ADDRESS) == defines.ERROR : 66 | quit() 67 | 68 | if master.waitForIdle() == defines.ERROR : 69 | quit("Idle timeout!") 70 | 71 | 72 | print("\n##### Ask if there is a ballast with the given address that is able to communicate #####") 73 | 74 | shortAddress = master.getShortAddress(userSA, defines.LW14_MODE_CMD) 75 | if master.clean() == defines.ERROR or master.queryCmd(shortAddress, dali.DALI_QUERY_BALLAST) == defines.ERROR : 76 | quit() 77 | 78 | if master.waitForTelegram_1() == defines.ERROR : 79 | quit("response timeout!") 80 | 81 | if master.read("command") != defines.DALI_YES : 82 | quit() 83 | 84 | 85 | print("\n##### Well, now make it flash (just for fun)! #####") 86 | 87 | if master.indirectCmd(shortAddress, dali.DALI_OFF) == defines.ERROR : 88 | quit() 89 | 90 | if master.waitForReady() == defines.ERROR : 91 | quit() 92 | 93 | time.sleep(0.2) 94 | 95 | if master.indirectCmd(shortAddress, dali.DALI_MAX_LEVEL) == defines.ERROR : 96 | quit() 97 | 98 | 99 | print("\n##### New address assigned and verified. Done. #####") 100 | else : 101 | quit('bye') 102 | -------------------------------------------------------------------------------- /examples/shellControl/shellControl.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import os 4 | import sys 5 | sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__))))) 6 | 7 | import daliMaster 8 | import defines 9 | import time 10 | 11 | master = daliMaster.daliMaster() 12 | 13 | masterAddress = defines.LW14_I2C_ADDRESS 14 | if len(sys.argv) > 1 : 15 | masterAddress = sys.argv[1] 16 | 17 | if master.begin(masterAddress) == defines.ERROR : 18 | quit() 19 | 20 | while True : 21 | 22 | while True : 23 | userCommand = input('Digit your command (press "Enter" to see all options, "q" to exit):') 24 | commands = userCommand.split() 25 | if len(commands) == 0 : 26 | print('choose one of these commands:') 27 | print('\t-a, set new I2C address') 28 | print('\t-r, read a register') 29 | print('\t-d, make a direct arc power conmmand') 30 | print('\t-i, make a indirect arc power conmmand') 31 | print('\t-i, make a configuration conmmand') 32 | print('\t-q, make a query to ballast') 33 | print('\t-x, make a special command') 34 | elif commands[0] == "q" : 35 | quit("exit") 36 | else : 37 | break 38 | 39 | print("received commands: ", end = '') 40 | for command in commands: 41 | print("[{0}]".format(command), end = '') 42 | print('') 43 | 44 | res = True 45 | 46 | if len(commands) > 4 : 47 | res = defines.ERROR 48 | print("command too long") 49 | 50 | if res != defines.ERROR and commands[0] == "-a" : 51 | 52 | if len(commands) < 2 : 53 | res = defines.ERROR 54 | print("error: command too short, specify new address.") 55 | else : 56 | res = master.setNewAddress(command) 57 | 58 | elif res != defines.ERROR and commands[0] == "-r" : 59 | 60 | if len(commands) < 2 : 61 | res = defines.ERROR 62 | print('error: command too short, specify register: "status", "command" or "signature".') 63 | else : 64 | res = master.read(commands[1]); 65 | if not res == defines.ERROR : 66 | print("read:{0}({0:08b})".format(res)) 67 | 68 | elif res != defines.ERROR : 69 | 70 | if commands[0] == "-d" : 71 | mode = defines.LW14_MODE_DACP 72 | else : 73 | mode = defines.LW14_MODE_CMD 74 | 75 | if not commands[0] == "-x" : 76 | 77 | if commands[1] == "-s" : 78 | if len(commands) < 4 : 79 | res = defines.ERROR 80 | print("error: command too short.") 81 | else : 82 | daliAddress = master.getShortAddress(int(commands[2]), mode) 83 | daliCmd = int(commands[3]) 84 | elif commands[1] == "-b" : 85 | if len(commands) < 3 : 86 | res = defines.ERROR 87 | print("error: command too short.") 88 | else : 89 | daliAddress = master.getBroadcastAddress(mode) 90 | daliCmd = int(commands[2]) 91 | elif commands[1] == "-g" : 92 | if len(commands) < 4 : 93 | res = defines.ERROR 94 | print("error: command too short.") 95 | else : 96 | daliAddress = master.getGroupAddress(int(commands[2]), mode) 97 | daliCmd = int(commands[3]) 98 | else : 99 | res = defines.ERROR 100 | print("error: wrong recipient type.") 101 | 102 | if not res == defines.ERROR : 103 | if commands[0] == "-d" : 104 | res = master.directCmd(daliAddress, daliCmd) 105 | elif commands[0] == "-i" : 106 | res = master.indirectCmd(daliAddress, daliCmd) 107 | elif commands[0] == "-c" : 108 | res = master.configCmd(daliAddress, daliCmd) 109 | elif commands[0] == "-q" : 110 | res = master.queryCmd(daliAddress, daliCmd) 111 | if not res == defines.ERROR : 112 | print("(now you should read command register to see the response)") 113 | elif commands[0] == "-x" : 114 | res = master.directCmd(int(commands[1]), int(commands[2])) 115 | else : 116 | res = defines.ERROR 117 | print("error: wrong command.") 118 | 119 | 120 | if not res == defines.ERROR : 121 | print("done.") 122 | 123 | print("") 124 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # daliMaster 2 | 3 | This is a Python3 library to control your DALI lamps with the brand new [daliMaster](https://www.ebay.it/itm/DALI-Master-hat-for-Raspberry-pi/254220148656?hash=item3b30b39fb0:g:LmwAAOSwNZxczZC9) hat for Raspberry Pi, with built-in DALI bus power supply system. B:boom::boom:m! 4 | 5 | Are you looking for Arduino™ library and DALI shield? See [here](https://github.com/davideloba/daliMaster). 6 | 7 | ## Description 8 | 9 | ### What is DALI? 10 | 11 | DALI (Digital Addressable Lighting Interface) is a powerful protocol to control lighting. Through DALI you can dimmer your led lamps, ask them status, recall a predefined scenario and so on. If you want more information about DALI you can find many useful links to the bottom of this page. 12 | 13 | ### Can I use DALI with my Raspberry Pi? 14 | 15 | Well, the answer is YES. 16 | 17 | ### How? 18 | 19 | With [daliMaster](https://www.ebay.it/itm/DALI-Master-hat-for-Raspberry-pi/254220148656?hash=item3b30b39fb0:g:LmwAAOSwNZxczZC9) hat! As the name suggests, that hat transforms your Raspberry Pi in a DALI master, acting as a bridge between I2C interface and DALI bus. Let's make an example to explain how it works. 20 | 21 | ## Getting Started 22 | 23 | ### Hardware stuff 24 | 25 | * Turn your Raspberry Pi off. 26 | 27 | * Fit [daliMaster](https://www.ebay.it/itm/DALI-Master-hat-for-Raspberry-pi/254220148656?hash=item3b30b39fb0:g:LmwAAOSwNZxczZC9) hat on your Raspberry Pi 28 | 29 | * Make connections (you can find an example [here](https://www.ebay.it/itm/DALI-Master-hat-for-Raspberry-pi/254220148656?hash=item3b30b39fb0:g:LmwAAOSwNZxczZC9)) 30 | * Connect your lamps to their ballasts 31 | * Connect your ballasts to mains..be careful! 32 | * Connect your ballasts and [daliMaster](https://www.ebay.it/itm/DALI-Master-hat-for-Raspberry-pi/254220148656?hash=item3b30b39fb0:g:LmwAAOSwNZxczZC9) hat to DALI bus 33 | * Connect your 24V DC power supply to mains and to [daliMaster](https://www.ebay.it/itm/DALI-Master-hat-for-Raspberry-pi/254220148656?hash=item3b30b39fb0:g:LmwAAOSwNZxczZC9)..again, be careful! 34 | 35 | * If I'm right, now you should have all lamps on. 36 | 37 | * Turn your Raspberry Pi on. 38 | 39 | ### Software stuff 40 | 41 | Enable I2C interface of your Raspberry Pi 42 | ``` 43 | sudo raspi-config 44 | ``` 45 | Select "Interfacing option">"I2C">"Yes" to enable the interface. 46 | Then install the I2C utilities: 47 | ``` 48 | sudo apt-get update 49 | sudo apt-get install python3-smbus i2c-tools 50 | ``` 51 | Now digit 52 | ``` 53 | sudo i2cdetect -y 1 54 | ``` 55 | and you should see something like that: 56 | ``` 57 | 0 1 2 3 4 5 6 7 8 9 a b c d e f 58 | 00: -- -- -- -- -- -- -- -- -- -- -- -- -- 59 | 10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 60 | 20: -- -- -- 23 -- -- -- -- -- -- -- -- -- -- -- -- 61 | 30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 62 | 40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 63 | 50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 64 | 60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 65 | 70: -- -- -- -- -- -- -- -- 66 | ``` 67 | If you see '23' (I2C address 0x23), your [daliMaster](https://www.ebay.it/itm/DALI-Master-hat-for-Raspberry-pi/254220148656?hash=item3b30b39fb0:g:LmwAAOSwNZxczZC9) is online: well done! 68 | 69 | 70 | ### Let's do something 71 | 72 | Download this library and digit: 73 | ``` 74 | sudo python3 examples/shellControl/shellControl.py 75 | ``` 76 | Remember: "sudo" is needed because we are trying to access system resources such as I2C. Now you should see something like that: 77 | ``` 78 | I2C DALI master(0x23) begin.. 79 | PING I2C device 0x23..ok 80 | device 0x23 is ready 81 | Digit your command (press "Enter" to see all options): 82 | ``` 83 | Well, write and send this command: 84 | ``` 85 | -d -b 0 86 | ``` 87 | If everything went well your lamps now are off. But we don't like darkness, so let's switch them on to the minimum: 88 | ``` 89 | -d -b 1 90 | ``` 91 | Cool! Let's push them to maximum: 92 | ``` 93 | -d -b 254 94 | ``` 95 | Easy, isn't it? Now you can modulate all lamps from 0 up to 254 with those simple commands. :thumbsup: 96 | 97 | ## Next 98 | 99 | See more informations about shell commands [here](/examples/shellControl/README.MD). See other examples to play with your lamps (try [Pulse.py](/examples/pulse)). See also the following links to know more about [daliMaster](https://www.ebay.it/itm/DALI-Master-hat-for-Raspberry-pi/254220148656?hash=item3b30b39fb0:g:LmwAAOSwNZxczZC9) and DALI. 100 | 101 | ## Useful links 102 | 103 | ### Raspberry Pi and I2C Interface 104 | * [raspberry-projects.com](https://raspberry-projects.com/pi/programming-in-python/i2c-programming-in-python/using-the-i2c-interface-2) 105 | 106 | ### DALI 107 | * [main commands](https://www.acmesystems.it/www_raspberry/openhab_dali/dali_commands.pdf) 108 | * DALI international standard (English/French) [60929 © IEC:2006](http://jnhb.fszjzx.com/upload/biaozhun/pdf/IEC60929Y2006.PDF) 109 | 110 | ### LW14 111 | * [LW14 datasheet](https://www.codemercs.com/downloads/ledwarrior/LW14_Datasheet.pdf) 112 | 113 | ## This library is built With 114 | 115 | * [Atom](https://atom.io/) 116 | 117 | ## Versioning 118 | 119 | * v.1 First release April 2019 120 | 121 | ## Credits 122 | 123 | See [credits.md](credits.md) file for details. 124 | Raspberry Pi is a trademark of the Raspberry Pi Foundation. 125 | 126 | ## License 127 | 128 | This project is licensed under the MIT License - see the [LICENSE.md](LICENSE.md) file for details 129 | -------------------------------------------------------------------------------- /examples/shellControl/README.md: -------------------------------------------------------------------------------- 1 | # shellControl.py 2 | 3 | Here there is a list of commands that you can send to your Raspberry Pi by shell in order to control daliMaster Hat. 4 | 5 | ## Getting started 6 | Launch *shellControl.py* under *examples/shellControl* folder. Digit your custom daliMaster address as program argument if it is different from default. 7 | ``` 8 | sudo python3 examples/shellControl/shellControl.py [custom I2C address if any] 9 | ``` 10 | ### Hello world 11 | ``` 12 | I2C DALI master(0x23) begin.. 13 | PING I2C device 0x23..ok 14 | device 0x23 is ready 15 | Digit your command (press "Enter" to see all options): 16 | ``` 17 | At this point, you are asked to send command. Write your command and press return to send it. Remember that you can send up to 4 arguments each time. The program will echo the received command and if it is correct it will be execute. 18 | 19 | ### Set new I2C address 20 | **-a [new address]** 21 | Set another I2C address to the daliMaster chip. You can use hex or decimal address. Values of 128(0x80) and more are not accepted. 22 | #### example 23 | ``` 24 | -a 39 25 | ``` 26 | response: 27 | ``` 28 | received commands: [-a][39] 29 | Setting 39(0x27) as new I2C address 30 | Ping I2C device 39(0x27)..ok 31 | done. 32 | ``` 33 | ### Read register 34 | **-r [register name]** 35 | 36 | Read daliMaster register and echo result. Available registers are: 37 | * "status" : 38 | The status register is one byte that contains the bus status and command status flags (see further). 39 | * "command" : 40 | The command register has two bytes which directly represent the DALI command. Please refer to the DALI specification for details on the commands. 41 | * "signature" : 42 | The signature register can be used to identify the I2C chip and get the revision information for the firmware. 43 | 44 | #### example 45 | In this example we will ask ballast its physically minimum level and read the response. 46 | First of all do a dummy reading to free previous message on "command" register. Do not mind output. 47 | ``` 48 | -r command 49 | ``` 50 | Query lamp with Short Address 8 with DALI_QUERY_PHYSICAL_MIN_LEVEL code (154). 51 | ``` 52 | -q -s 8 154 53 | ``` 54 | response: 55 | ``` 56 | received commands: [-q][-s][8][154] 57 | query command function called 58 | (now you should read command register to see the response) 59 | done. 60 | ``` 61 | Now if we read the "status" register we will find that a reply is available. 62 | ``` 63 | -r status 64 | ``` 65 | response: 66 | ``` 67 | received commands: [-r][status] 68 | BUS FAULT 0 69 | BUSY 0 70 | OVERRUN 0 71 | FRAM ERROR 0 72 | VALID REPLY 1 73 | REPLY TIMEFRAME 0 74 | 2 BYTE TELEGRAM 0 75 | 1 BYTE TELEGRAM 1 76 | read:9(00001001) 77 | done. 78 | ``` 79 | This register has changed quickly after the query in this way. 80 | ``` 81 | BUS FAULT 0 82 | BUSY 1 83 | OVERRUN 0 84 | FRAM ERROR 0 85 | VALID REPLY 0 86 | REPLY TIMEFRAME 0 87 | 2 BYTE TELEGRAM 0 88 | 1 BYTE TELEGRAM 0 89 | ``` 90 | BUSY = 1 indicates that the last command has not yet been transmitted. Any new command sent to register 1 will be ignored until the last command has been transmitted and the busy bit is cleared. 91 | ``` 92 | BUS FAULT 0 93 | BUSY 0 94 | OVERRUN 0 95 | FRAM ERROR 0 96 | VALID REPLY 0 97 | REPLY TIMEFRAME 1 98 | 2 BYTE TELEGRAM 0 99 | 1 BYTE TELEGRAM 0 100 | ``` 101 | REPLY TIMEFRAME = 1 indicates that the time frame for a reply from the last addressed device has not yet timed out and is reset to zero after 22 Te (see DALI specification) or on bus activity. 102 | ``` 103 | BUS FAULT 0 104 | BUSY 0 105 | OVERRUN 0 106 | FRAM ERROR 0 107 | VALID REPLY 1 108 | REPLY TIMEFRAME 0 109 | 2 BYTE TELEGRAM 0 110 | 1 BYTE TELEGRAM 1 111 | ``` 112 | Valid Reply = 1 if a telegram has been received within 22 Te (see DALI specification) of sending a command. 1Tel = 1 means that 1 byte telegram has been received. The bit is reset on reading command register. 113 | So we have a Valid reply and it is a one byte telegram. So read the "command" register to get this telegram. 114 | ``` 115 | -r command 116 | ``` 117 | response: 118 | ``` 119 | received commands: [-r][command] 120 | read:170(10101010) 121 | done. 122 | ``` 123 | So, ballast physical minimum is 170. Notice that even if DALI permits 254 levels, that ballast cannot dim light under this value. Now if you read the status register, you should have all bits equal to 0, nothing left to do. 124 | ``` 125 | received commands: [-r][status] 126 | BUS FAULT 0 127 | BUSY 0 128 | OVERRUN 0 129 | FRAM ERROR 0 130 | VALID REPLY 0 131 | REPLY TIMEFRAME 0 132 | 2 BYTE TELEGRAM 0 133 | 1 BYTE TELEGRAM 0 134 | read:0(00000000) 135 | done. 136 | ``` 137 | ### Address 138 | There are 3 types of addresses: 139 | * **-s [NUMBER]** short addresses: as DALI specification, each ballast can be reached with a single address from 0 up to 63. When this short address is not already set, ballast reacts only to broadcast commands. To assign this address see the example. 140 | * **-g [NUMBER]** group addresses: from 0 up to 15. *Please refer to the DALI specification for details on the commands* 141 | * **-b** broadcast: commands sent with a broadcast address will reach every ballast. 142 | 143 | ### DALI forward telegram 144 | #### direct ARC command 145 | **-d [ADDRESS] [LEVEL]** 146 | ``` 147 | -d -s 8 200 148 | ``` 149 | Command ballast with address 8 to 200 arc power level. 150 | ``` 151 | -d -b 0 152 | ``` 153 | Command all ballast to switch to 0 (off). 154 | #### indirect command 155 | **-i [ADDRESS] [COMMAND]** 156 | ``` 157 | -i -b 5 158 | ``` 159 | Set all ballast arc power levels to the "MAX LEVEL" without fading. 160 | #### configuration command 161 | **-c [ADDRESS] [COMMAND]** 162 | ``` 163 | -c -s 8 128 164 | ``` 165 | Tell ballast 8 to store DTR as its short address. 166 | #### query command 167 | **-q [ADDRESS] [COMMAND]** 168 | ``` 169 | -q -s 8 160 170 | ``` 171 | Ballast will response with actual arc power level. To read the response, see register reading specifications. 172 | #### special command 173 | **-x [COMMAND] [COMMAND]** *Please refer to the DALI specification for details on the commands* 174 | -------------------------------------------------------------------------------- /dali.py: -------------------------------------------------------------------------------- 1 | # DALI TELEGRAM 2 | # 3 | # 1 start bit 4 | # 8 address bits: 1 individual or group address bit (Y) , 6 address bits, 1 select bit (S) 5 | # 8 data bits 6 | # 2 stop bits 7 | # 8 | # ADDRESS BYTE: 9 | # Short or group address YAAAAAAS 10 | # Short addresses (from 0 to 63) 0AAAAAAS 11 | # Group addresses (from 0 to 15) 100AAAAS 12 | # Broadcast 1111111S 13 | # 14 | # Special command 101CCCC1 15 | # Special command 110CCCC1 16 | # 17 | # 18 | # S: selector bit: 19 | # S = ‘0’ direct arc power level following 20 | # S = ‘1’ command following (indirect) 21 | # Y = ‘0’ short address 22 | # Y = ‘1’ group address or broadcast 23 | # 24 | # A: significant address bit 25 | # C: significant command bit 26 | # 27 | # examples: 28 | # 29 | # Direct arc power control command 30 | # YAAA AAA0 XXXX XXXX 31 | # 32 | # indirect arc power control commands 33 | # YAAA AAA1 XXXX XXXX (16-31) 34 | # 35 | # configuration commands 36 | # YAAA AAA1 XXXX XXXX (32-128) 37 | # 38 | # query commands 39 | # YAAA AAA1 XXXX XXXX (144-196) 40 | # 41 | # special commands 42 | # 101CCCC1 XXXX XXXX or 43 | # 110CCCC1 XXXX XXXX 44 | 45 | 46 | # Indirect commands (0 ... 31) 47 | DALI_OFF = 0 #Switches off the light immediately 48 | DALI_UP = 1 #200 ms dimming up 49 | DALI_DOWN = 2 #200 ms dimming down 50 | DALI_STEP_UP = 3 #Increases the brightness by one step 51 | DALI_STEP_DOWN = 4 #Decreases the brightness by one step 52 | DALI_MAX_LEVEL = 5 #Maximum brightness 53 | DALI_MIN_LEVEL = 6 #Minimum brightness 54 | DALI_STEP_DOWN_OFF = 7 #Decrease brightness by one step (including switching off) 55 | DALI_ON_STEP_UP = 8 #Increase brightness by one step (including switching on) 56 | DALI_ENABLE_DAPC_SEQUENCE = 9 #Commence DACP sequence 57 | #10 - 15 RESERVED 58 | DALI_GO_TO_SCENE = 16 #16 - 31, Enables scene 0 to 15 59 | 60 | # Configuration commands (32 ... 41) 61 | DALI_RESET = 32 #Resets nonvolatile memory 62 | DALI_DTR_ACTUAL_LEVEL = 33 #Reads out the current power level 63 | #34 - 41 RESERVED 64 | 65 | # DALI Save DTR value (42 ... 63) 66 | DALI_DTR_MAX_LEVEL = 42 #Save as maximum power value 67 | DALI_DTR_MIN_LEVEL = 43 #Save as minimum power value 68 | DALI_DTR_SYS_FAIL_LEVEL = 44 #Save power value as value for event of error 69 | DALI_DTR_POWER_ON_LEVEL = 45 #Save power value as switch-on value 70 | DALI_DTR_FADE_TIME = 46 #Save value as dimming time 71 | DALI_DTR_FADE_RATE = 47 #Save value as dimming speed 72 | #48 - 63 RESERVED 73 | 74 | # Used for setting system parameters (64 ... 143) 75 | DALI_ADD_SCENE = 64 #64 - 79, Save DTR value as selected scene 0 to 15 76 | DALI_REMOVE_SCENE = 80 #80 - 95, Removes DALI slave from scene 0 to 15 77 | DALI_ADD_GROUP = 96 #96 - 111, Adds DALI slave to group 0 to 15 78 | DALI_REMOVE_GROUP = 112 #112 - 127, Removes DALI slave from group 0 to 15 79 | DALI_DTR_AS_SHORT_ADDRESS = 128 #Save DTR value as short address 80 | #129 - 143 RESERVED 81 | 82 | # Query commands (144 ... 223) 83 | DALI_QUERY_STATUS = 144 #Checks the general status (see further for specification) 84 | DALI_QUERY_BALLAST = 145 #Checks communication readiness, answer: "Yes" or "No" 85 | DALI_QUERY_LAMP_FAILURE = 146 #Checks for light failure, answer: "Yes" or "No" 86 | DALI_QUERY_LAMP_POWER_ON = 147 #Checks whether light is currently on, answer: "Yes" or "No" 87 | DALI_QUERY_LIMIT_ERROR = 148 #Checks whether the last requested power value was applied, answer: "Yes" or "No" 88 | DALI_QUERY_RESET_STATE = 149 #Checks whether the DALI slave is in reset state, answer: "Yes" or "No" 89 | DALI_QUERY_MISSING_SHORT_ADDRESS = 150 #Checks whether the DALI slave has a short address, answer: "Yes" or "No" 90 | DALI_QUERY_VERSION_NUMBER = 151 #Checks whether the DALI slave has a version number 91 | DALI_QUERY_CONTENT_DTR = 152 #Checks the DTR value 92 | DALI_QUERY_DEVICE_TYPE = 153 #Checks the device type, answer: 0 - 255 93 | DALI_QUERY_PHYSICAL_MIN_LEVEL = 154 #Checks the physical minimum level (greater than 0) 94 | DALI_QUERY_POWER_FAILURE = 155 #Checks for power failure, answer: "Yes" or "No" 95 | #156 - 159 RESERVED 96 | DALI_QUERY_ACTUAL_LEVEL = 160 #Checks the current power level, answer: 0 - 255 97 | DALI_QUERY_MAX_LEVEL = 161 #Checks the maximum value 98 | DALI_QUERY_MIN_LEVEL = 162 #Checks the minimum value 99 | DALI_QUERY_POWER_ON_LEVEL = 163 #Checks the switch-on power level 100 | DALI_QUERY_SYSTEM_FAILURE_LEVEL = 164 #Checks the power level in the event of error 101 | DALI_QUERY_FADE_TIME_RATE = 165 #Checks the dimming time and dimming speed (see further for specification) 102 | #166 - 175 RESERVED 103 | DALI_QUERY_SCENE_LEVEL = 176 #176 - 191, Checks the light level for scene 0 to 15 104 | DALI_QUERY_GROUPS_0_7 = 192 #Checks groups 0 to 7 105 | DALI_QUERY_GROUPS_8_15 = 193 #Checks groups 8 to 15 106 | DALI_QUERY_RANDOM_ADDRESS_H = 194 #Checks the 8 high bits of the random address 107 | DALI_QUERY_RANDOM_ADDRESS_M = 195 #Checks the 8 mids bits of the random address 108 | DALI_QUERY_RANDOM_ADDRESS_L = 196 #Checks the 8 low bits of the random address 109 | #197 - 223 RESERVED 110 | 111 | #224 - 255 Checks application-specific defined commands 112 | 113 | # Special commands 114 | # Special commands shall be broadcast and received by all ballasts. 115 | # This means that the main address shall be 101C CCC1or 110C CCC1. CCCC is the significant "SPECIAL COMMAND". 116 | # e.g.: 117 | # Command 169: 1010 1001 0000 0000 "COMPARE" 118 | # 1010 1011 = 169 119 | DALI_TERMINATE = 161 #Switches all DALI slaves on the bus in normal mode 120 | DALI_STORE_TO_DTR = 163 #10100011 XXXXXXXX, Writes the bit pattern XXXXXXXX to the Data Transfer Register (DTR) 121 | DALI_INITIALISE = 165 #Allows commands for special addressing within the next 15 minutes 122 | DALI_RANDOMISE = 167 #The ballast shall generate a new random address on the request of this command 123 | DALI_COMPARE = 169 #The ballast shall compare it's random address with the combined search address 124 | DALI_WITHDRAW = 171 #The selected slave is excluded from the subsequent search with "COMPARE" statements but remains initialized and can be selected 125 | #173 - 175 Reserved 126 | DALI_SEARCHADDR_H = 177 #The 8 high bits of the search address 127 | DALI_SEARCHADDR_M = 179 #The 8 mid bits of the search address 128 | DALI_SEARCHADDR_L = 181 #The 8 low bits of the search address 129 | 130 | DALI_PROGRAM_SHORT_ADDRESS = 183 #10110111 0AAAAAA1, The selected slave takes on the short address assigned to AAAAAA. 131 | DALI_VERIFY_SHORT_ADDRESS = 185 #The selected slave responds with YES if the value specified on aaaaaa corresponds to its short address 132 | DALI_QUERY_SHORT_ADDRESS = 187 #The selected slave responds with its current short address 133 | DALI_PHYSICAL_SELECTION = 189 #The selected slave is excluded from the subsequent search with "COMPARE" statements, no longer initialized and can no longer be selected 134 | 135 | #Additional special commands can be found in the DALI standard. 136 | 137 | 138 | # DALI_QUERY_STATUS 139 | # Answer is the following "STATUS INFORMATION" byte: 140 | # bit 0 Status of ballast; "0" = OK, "1" = KO 141 | # bit 1 Lamp failure; "0" = OK , "1" = KO 142 | # bit 2 Lamp arc power on; "0" = OFF , "1" = ON 143 | # bit 3 Query: Limit Error; "0" = Last requested arc power level is between MIN..MAX LEVEL or OFF 144 | # bit 4 Fade ready; "0" = fade is ready; "1" = fade is running 145 | # bit 5 Query: "RESET STATE"? "0" = "No", "1" = "Yes" 146 | # bit 6 Query: Missing short address? "0" = "No", "1" = "Yes" 147 | # bit 7 Query: "POWER FAILURE"? "0" = "No"; "RESET" or an arc power control command has been received after last power-on. 148 | 149 | # DALI_QUERY_FADE_TIME_RATE 150 | # Answer 8 bit, FADE TIME(X) and FADE RATE(Y): XXXX YYYYY 151 | # values for Fadetime and fade rate. Time is in seconds, Rate is in steps/second 152 | # fadetime = [0,0.7,1.0,1.4,2.0,2.8,4.0,5.7,8.0,11.3,16.0,22.6,32.0,45.3,64.0,90.5] #value to dali -> 0 ... 15 153 | # faderate = [0,358,253,179,127,89.4,63.3,44.7,31.6,22.4,15.8,11.2,7.9,5.6,4.0,2.8] #value to dali -> 0 ... 15 -> 0 is impossible! 154 | -------------------------------------------------------------------------------- /daliMaster.py: -------------------------------------------------------------------------------- 1 | import dali 2 | import defines 3 | import smbus 4 | import time 5 | 6 | 7 | class daliMaster: 8 | 9 | 10 | def __init__(self) : 11 | self.__address = 0 12 | self.__bus = 0 13 | try : 14 | self.__bus = smbus.SMBus(1) 15 | except IOError as e: 16 | print ("I/O error({0}): {1}".format(e.errno, e.strerror)) 17 | return defines.ERROR 18 | 19 | 20 | def begin(self, toSet = defines.LW14_I2C_ADDRESS): 21 | 22 | ''' 23 | Initialize daliMaster and check if there is any 24 | responding device with that address online 25 | ''' 26 | 27 | try : 28 | address = self.__checkAddress(toSet) 29 | print ("I2C DALI master {0}({1}) begin..".format(address, hex(address))) 30 | self.checkRange(address, 1, 127) 31 | self.__address = address 32 | self.__ping() 33 | except ValueError as e : 34 | print(e.args) 35 | return defines.ERROR 36 | except IOError as e : 37 | print ("I/O error({0}): {1}".format(e.errno, e.strerror)) 38 | return defines.ERROR 39 | else: 40 | print("Device {0}({1}) is ready".format(self.__address, hex(self.__address))) 41 | return True 42 | 43 | 44 | def setNewAddress(self, toSet): 45 | 46 | ''' 47 | Save the new I2C address to the Address Register 48 | and check if device has got the new one 49 | ''' 50 | 51 | try: 52 | address = self.__checkAddress(toSet) 53 | self.checkRange(address, 1, 127) 54 | data = [address, address ^ 0xFF] # the second byte must contain the value of the first byte XORed with $FF. 55 | 56 | print("Setting {0}({1}) as new I2C address".format(address, hex(address))) 57 | # print(":{0:08b}".format(data[0])) 58 | # print(":{0:08b}".format(0xFF)) 59 | # print(":{0:08b}".format(data[1])) 60 | 61 | self.__i2cWrite(defines.LW14_REGISTERS["address"]["address"], data) 62 | time.sleep(1) 63 | self.__ping(address) #pass new address as argument to override default 64 | self.__address = address 65 | except ValueError as e : 66 | print(e.args) 67 | return defines.ERROR 68 | except IOError as e : 69 | print ("I/O error({0}): {1}".format(e.errno, e.strerror)) 70 | return defines.ERROR 71 | else: 72 | return True 73 | 74 | 75 | def clean(self): 76 | 77 | ''' 78 | Read once the command register just to set it free 79 | ''' 80 | 81 | try: 82 | return self.__i2cRead(defines.LW14_REGISTERS["command"]["address"]) 83 | except IOError as e : 84 | print ("I/O error({0}): {1}".format(e.errno, e.strerror)) 85 | return defines.ERROR 86 | 87 | 88 | def waitForReady(self, timeout = defines.BUS_TIMEOUT): 89 | 90 | ''' 91 | Wait for bits of "status busy" and "bus fault" to be 0 92 | ''' 93 | 94 | try: 95 | # print("waiting for bus ready") 96 | return self.__waitFor(timeout, 0, defines.LW14_STATUS_BUSY, defines.LW14_STATUS_BUS_FAULT) 97 | except IOError as e : 98 | print ("I/O error({0}): {1}".format(e.errno, e.strerror)) 99 | return defines.ERROR 100 | 101 | 102 | def waitForTelegram_1(self, timeout = defines.BUS_TIMEOUT): 103 | 104 | ''' 105 | Wait for bits of "status valid" and "status 1 byte" to be 1 106 | ''' 107 | 108 | try: 109 | return self.__waitFor(timeout, 1, defines.LW14_STATUS_VALID, defines.LW14_STATUS_1BYTE) 110 | except IOError as e : 111 | print ("I/O error({0}): {1}".format(e.errno, e.strerror)) 112 | return defines.ERROR 113 | 114 | 115 | def waitForTelegram_2(self, timeout = defines.BUS_TIMEOUT): 116 | 117 | ''' 118 | Wait for bits of "status valid" and "status 2 byte" to be 1 119 | ''' 120 | 121 | try: 122 | return self.__waitFor(timeout, 1, defines.LW14_STATUS_VALID, defines.LW14_STATUS_2BYTE) 123 | except IOError as e : 124 | print ("I/O error({0}): {1}".format(e.errno, e.strerror)) 125 | return defines.ERROR 126 | 127 | 128 | def waitForIdle(self, timeout = defines.BUS_TIMEOUT): 129 | 130 | ''' 131 | Wait until all bits of status reg are equal to 0 132 | ''' 133 | 134 | try: 135 | return self.__waitFor(timeout, 0, 255, 255) 136 | except IOError as e : 137 | print ("I/O error({0}): {1}".format(e.errno, e.strerror)) 138 | return defines.ERROR 139 | 140 | 141 | def directCmd(self, ballast, cmd): 142 | 143 | print("direct command function called ({0},{1})".format(ballast,cmd)) 144 | 145 | try: 146 | self.checkRange(cmd, 0, defines.DALI_DEFAULT_MAX) 147 | self.__write(ballast, cmd) 148 | except ValueError as e: 149 | print(e.args) 150 | return defines.ERROR 151 | except IOError as e : 152 | print ("I/O error({0}): {1}".format(e.errno, e.strerror)) 153 | return defines.ERROR 154 | else: 155 | return True 156 | 157 | 158 | def indirectCmd(self, ballast, cmd): 159 | 160 | print("indirect command function called ({0},{1})".format(ballast,cmd)) 161 | 162 | try: 163 | self.checkRange(cmd, 0, 31) 164 | self.__write(ballast, cmd) 165 | except ValueError as e: 166 | print(e.args) 167 | return defines.ERROR 168 | except IOError as e : 169 | print ("I/O error({0}): {1}".format(e.errno, e.strerror)) 170 | return defines.ERROR 171 | else: 172 | return True 173 | 174 | 175 | def configCmd(self, ballast, cmd): 176 | 177 | print("config command function called ({0},{1})".format(ballast,cmd)) 178 | 179 | try: 180 | self.checkRange(cmd, 32, 128) 181 | self.__writeTwice(ballast, cmd) 182 | except ValueError as e: 183 | print(e.args) 184 | return defines.ERROR 185 | except IOError as e : 186 | print ("I/O error({0}): {1}".format(e.errno, e.strerror)) 187 | return defines.ERROR 188 | else: 189 | return True 190 | 191 | 192 | def queryCmd(self, ballast, cmd): 193 | 194 | print("query command function called ({0},{1})".format(ballast,cmd)) 195 | 196 | try: 197 | self.checkRange(cmd, 144, 196) 198 | self.__write(ballast, cmd) 199 | except ValueError as e: 200 | print(e.args) 201 | return defines.ERROR 202 | except IOError as e : 203 | print ("I/O error({0}): {1}".format(e.errno, e.strerror)) 204 | return defines.ERROR 205 | else: 206 | return True 207 | 208 | 209 | def specialCmd(self, cmd_1, cmd_2): 210 | 211 | print("special command function called ({0},{1})".format(cmd_1,cmd_2)) 212 | 213 | try: 214 | # self.checkRange(cmd, 161, 255) 215 | self.__write(cmd_1, cmd_2) 216 | return True 217 | except ValueError as e: 218 | print(e.args) 219 | return defines.ERROR 220 | except IOError as e : 221 | print ("I/O error({0}): {1}".format(e.errno, e.strerror)) 222 | return defines.ERROR 223 | else: 224 | return True 225 | 226 | 227 | def read(self, regName): 228 | 229 | try: 230 | regAddress = self.__getRegAddress(regName,"r") 231 | read = self.__i2cRead(regAddress) 232 | if regName == "status" : 233 | self.__printStatusReg(read) 234 | except ValueError as e: 235 | print(e.args) 236 | return defines.ERROR 237 | except IOError as e : 238 | print ("I/O error({0}): {1}".format(e.errno, e.strerror)) 239 | return defines.ERROR 240 | else: 241 | return read 242 | 243 | 244 | def getBroadcastAddress(self, mode) : 245 | 246 | return (0xFE | mode) 247 | 248 | 249 | def getGroupAddress(self, group, mode) : 250 | 251 | return (0x80 | ((group & 16) << 1) | mode) 252 | 253 | 254 | def getShortAddress(self, ballast, mode) : 255 | 256 | return (((ballast & 63) << 1) | mode) 257 | 258 | 259 | def checkRange(self, checkMe, rangeMin, rangeMax) : 260 | 261 | if checkMe < rangeMin or checkMe > rangeMax: 262 | raise ValueError('value out of bounds', checkMe, rangeMin, rangeMax) 263 | return True 264 | 265 | 266 | #Private 267 | def __ping(self, address = None) : 268 | 269 | ''' 270 | Perform quick transaction just to check if device is online. 271 | Throws IOError if unsuccessful. 272 | Take master.__address as default 273 | ''' 274 | 275 | if address == None : 276 | address = self.__address 277 | print("Ping I2C device {0}({1})..".format(address, hex(address)), end = '') 278 | self.__bus.write_quick(address) #if it fails, will raise an IOError 279 | print("ok") 280 | 281 | 282 | def __checkAddress(self, toSet) : 283 | 284 | if isinstance(toSet, str) : 285 | if not toSet.find("x") == -1 or not toSet.find("X") == -1 : 286 | return int(toSet, 16) 287 | else : 288 | return int(toSet) 289 | return toSet 290 | 291 | 292 | def __getRegAddress(self, name, mode, data = []) : 293 | 294 | ''' 295 | Check if the wanted register is compatible with mode and data length 296 | and if so, return the register address, otherwise raise an Exception 297 | ''' 298 | 299 | #check if the register name is correct 300 | if not name in defines.LW14_REGISTERS : 301 | raise ValueError ("register name doesn't match the avaibles ones", name) 302 | 303 | #check if the mode is correct 304 | if defines.LW14_REGISTERS[name]['mode'].find(mode) == -1 : 305 | raise ValueError ("mode doesn't match the avaibles ones", mode, defines.LW14_REGISTERS[name][mode]) 306 | 307 | #check if the data length is correct 308 | if len(data) > defines.LW14_REGISTERS[name]['length'] : 309 | raise ValueError ('data is too long to write', len(data), defines.LW14_REGISTERS[name][length]) 310 | 311 | return defines.LW14_REGISTERS[name]['address'] 312 | 313 | 314 | def __waitFor(self, timeout, target = 0, first_mask = 255, second_mask = 255): 315 | 316 | previous = time.time() 317 | 318 | while (time.time() - previous) < timeout: 319 | 320 | status = self.__i2cRead(defines.LW14_REGISTERS["status"]["address"]) 321 | # self.__printStatusReg(status) 322 | 323 | if target == 0: #wait for zero 324 | if status & first_mask == 0 and status & second_mask == 0: 325 | # self.__printStatusReg(status) 326 | return True 327 | else: #wait for greater than zero 328 | if status & first_mask > 0 and status & second_mask > 0: 329 | # self.__printStatusReg(status) 330 | return True 331 | 332 | print("timeout!") 333 | self.__printStatusReg(status) 334 | 335 | return defines.ERROR 336 | 337 | 338 | def __write(self, data_1, data_2): 339 | 340 | data = [data_1, data_2] #2 byte dali telegram 341 | regAddress = self.__getRegAddress("command","w", data) 342 | return self.__i2cWrite(regAddress, data) 343 | 344 | 345 | def __writeTwice(self, data_1, data_2): 346 | 347 | self.__write(data_1, data_2) 348 | if self.waitForReady(0.1) == defines.ERROR : #100ms should be the max interval between 349 | raise ValueError('timeout in double write!') 350 | self.__write(data_1, data_2) 351 | return True 352 | 353 | 354 | def __i2cWrite(self, reg_address, data): 355 | 356 | return self.__bus.write_i2c_block_data(self.__address, reg_address, data) 357 | 358 | 359 | def __i2cRead(self, reg_address): 360 | 361 | return self.__bus.read_byte_data(self.__address, reg_address) 362 | 363 | 364 | def __printStatusReg(self, data): 365 | 366 | bits = ["1 BYTE TELEGRAM", "2 BYTE TELEGRAM", "REPLY TIMEFRAME", "VALID REPLY", "FRAM ERROR", "OVERRUN\t", "BUSY\t", "BUS FAULT"] 367 | 368 | for i in range(8, 0, -1) : 369 | bit = data & (1 << (i-1)) 370 | if bit > 1 : 371 | bit = 1 372 | print ('{0}'.format(bits[i-1]), end = '\t\t') 373 | print (bit) 374 | print("++++++++++++++++++++++++++") 375 | --------------------------------------------------------------------------------