├── .gitignore ├── AtlasI2C.py ├── LICENSE ├── README.md └── i2c.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | .gitignore 3 | .idea/ 4 | -------------------------------------------------------------------------------- /AtlasI2C.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import io 4 | import sys 5 | import fcntl 6 | import time 7 | import copy 8 | import string 9 | 10 | 11 | class AtlasI2C: 12 | 13 | # the timeout needed to query readings and calibrations 14 | LONG_TIMEOUT = 1.5 15 | # timeout for regular commands 16 | SHORT_TIMEOUT = .3 17 | # the default bus for I2C on the newer Raspberry Pis, 18 | # certain older boards use bus 0 19 | DEFAULT_BUS = 1 20 | # the default address for the sensor 21 | DEFAULT_ADDRESS = 98 22 | LONG_TIMEOUT_COMMANDS = ("R", "CAL") 23 | SLEEP_COMMANDS = ("SLEEP", ) 24 | 25 | def __init__(self, address=None, moduletype = "", name = "", bus=None): 26 | ''' 27 | open two file streams, one for reading and one for writing 28 | the specific I2C channel is selected with bus 29 | it is usually 1, except for older revisions where its 0 30 | wb and rb indicate binary read and write 31 | ''' 32 | self._address = address or self.DEFAULT_ADDRESS 33 | self.bus = bus or self.DEFAULT_BUS 34 | self._long_timeout = self.LONG_TIMEOUT 35 | self._short_timeout = self.SHORT_TIMEOUT 36 | self.file_read = io.open(file="/dev/i2c-{}".format(self.bus), 37 | mode="rb", 38 | buffering=0) 39 | self.file_write = io.open(file="/dev/i2c-{}".format(self.bus), 40 | mode="wb", 41 | buffering=0) 42 | self.set_i2c_address(self._address) 43 | self._name = name 44 | self._module = moduletype 45 | 46 | 47 | @property 48 | def long_timeout(self): 49 | return self._long_timeout 50 | 51 | @property 52 | def short_timeout(self): 53 | return self._short_timeout 54 | 55 | @property 56 | def name(self): 57 | return self._name 58 | 59 | @property 60 | def address(self): 61 | return self._address 62 | 63 | @property 64 | def moduletype(self): 65 | return self._module 66 | 67 | 68 | def set_i2c_address(self, addr): 69 | ''' 70 | set the I2C communications to the slave specified by the address 71 | the commands for I2C dev using the ioctl functions are specified in 72 | the i2c-dev.h file from i2c-tools 73 | ''' 74 | I2C_SLAVE = 0x703 75 | fcntl.ioctl(self.file_read, I2C_SLAVE, addr) 76 | fcntl.ioctl(self.file_write, I2C_SLAVE, addr) 77 | self._address = addr 78 | 79 | def write(self, cmd): 80 | ''' 81 | appends the null character and sends the string over I2C 82 | ''' 83 | cmd += "\00" 84 | self.file_write.write(cmd.encode('latin-1')) 85 | 86 | def handle_raspi_glitch(self, response): 87 | ''' 88 | Change MSB to 0 for all received characters except the first 89 | and get a list of characters 90 | NOTE: having to change the MSB to 0 is a glitch in the raspberry pi, 91 | and you shouldn't have to do this! 92 | ''' 93 | if self.app_using_python_two(): 94 | return list(map(lambda x: chr(ord(x) & ~0x80), list(response))) 95 | else: 96 | return list(map(lambda x: chr(x & ~0x80), list(response))) 97 | 98 | def app_using_python_two(self): 99 | return sys.version_info[0] < 3 100 | 101 | def get_response(self, raw_data): 102 | if self.app_using_python_two(): 103 | response = [i for i in raw_data if i != '\x00'] 104 | else: 105 | response = raw_data 106 | 107 | return response 108 | 109 | def response_valid(self, response): 110 | valid = True 111 | error_code = None 112 | if(len(response) > 0): 113 | 114 | if self.app_using_python_two(): 115 | error_code = str(ord(response[0])) 116 | else: 117 | error_code = str(response[0]) 118 | 119 | if error_code != '1': #1: 120 | valid = False 121 | 122 | return valid, error_code 123 | 124 | def get_device_info(self): 125 | if(self._name == ""): 126 | return self._module + " " + str(self.address) 127 | else: 128 | return self._module + " " + str(self.address) + " " + self._name 129 | 130 | def read(self, num_of_bytes=31): 131 | ''' 132 | reads a specified number of bytes from I2C, then parses and displays the result 133 | ''' 134 | 135 | raw_data = self.file_read.read(num_of_bytes) 136 | response = self.get_response(raw_data=raw_data) 137 | #print(response) 138 | is_valid, error_code = self.response_valid(response=response) 139 | 140 | if is_valid: 141 | char_list = self.handle_raspi_glitch(response[1:]) 142 | result = "Success " + self.get_device_info() + ": " + str(''.join(char_list)) 143 | #result = "Success: " + str(''.join(char_list)) 144 | else: 145 | result = "Error " + self.get_device_info() + ": " + error_code 146 | 147 | return result 148 | 149 | def get_command_timeout(self, command): 150 | timeout = None 151 | if command.upper().startswith(self.LONG_TIMEOUT_COMMANDS): 152 | timeout = self._long_timeout 153 | elif not command.upper().startswith(self.SLEEP_COMMANDS): 154 | timeout = self.short_timeout 155 | 156 | return timeout 157 | 158 | def query(self, command): 159 | ''' 160 | write a command to the board, wait the correct timeout, 161 | and read the response 162 | ''' 163 | self.write(command) 164 | current_timeout = self.get_command_timeout(command=command) 165 | if not current_timeout: 166 | return "sleep mode" 167 | else: 168 | time.sleep(current_timeout) 169 | return self.read() 170 | 171 | def close(self): 172 | self.file_read.close() 173 | self.file_write.close() 174 | 175 | def list_i2c_devices(self): 176 | ''' 177 | save the current address so we can restore it after 178 | ''' 179 | prev_addr = copy.deepcopy(self._address) 180 | i2c_devices = [] 181 | for i in range(0, 128): 182 | try: 183 | self.set_i2c_address(i) 184 | self.read(1) 185 | i2c_devices.append(i) 186 | except IOError: 187 | pass 188 | # restore the address we were using 189 | self.set_i2c_address(prev_addr) 190 | 191 | return i2c_devices 192 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 AtlasScientific 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | _this code has been forked from https://github.com/AtlasScientific/Raspberry-Pi-sample-code and is maintained here separately, optimised for Whitebox carrier boards_ 2 | 3 | This is example code for Whitebox carrier boards for the Raspberry Pi platform to be used with Atlas Scientific EZO devices and third-party devices. This code is compatible with Whitebox T3 and Whitebox T5. 4 | 5 | Full hardware documentation, quickstart guide 6 | - for the Whitebox Tentacle T3 for Raspberry Pi: here https://www.whiteboxes.ch/docs/tentacle/t3 7 | - for the Whitebox Tentacle T5 for Raspberry Pi: here https://www.whiteboxes.ch/docs/tentacle/t5 8 | 9 | # Preparing the Raspberry Pi # 10 | ### Install the latest Raspberry Pi OS 11 | Follow the instructions on this page to get Raspberry Pi OS running 12 | https://www.raspberrypi.org/downloads/raspberry-pi-os/ 13 | 14 | # Download sample code. 15 | 16 | cd ~ 17 | git clone https://github.com/whitebox-labs/whitebox-raspberry-ezo.git 18 | 19 | 20 | # I2C MODE # 21 | 22 | ### Enable I2C bus on the Raspberry Pi ### 23 | 24 | Enable I2C bus on the Raspberry Pi by following this: 25 | 26 | https://learn.adafruit.com/adafruits-raspberry-pi-lesson-4-gpio-setup/configuring-i2c 27 | 28 | You can confirm that the setup worked and sensors are present with the `sudo i2cdetect -y 1` command. 29 | 30 | ### Test Sensor ### 31 | 32 | Run the sample code below: 33 | 34 | cd ~/Raspberry-Pi-sample-code 35 | sudo python i2c.py 36 | 37 | When the code starts up a list of commands will be shown. 38 | 39 | For more details on the commands & responses, please refer to the Datasheets of the Atlas Scientific Sensors. 40 | -------------------------------------------------------------------------------- /i2c.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import io 4 | import sys 5 | import fcntl 6 | import time 7 | import copy 8 | import string 9 | from AtlasI2C import ( 10 | AtlasI2C 11 | ) 12 | 13 | def print_devices(device_list, device): 14 | for i in device_list: 15 | if(i == device): 16 | print("--> " + i.get_device_info()) 17 | else: 18 | print(" - " + i.get_device_info()) 19 | #print("") 20 | 21 | def get_devices(): 22 | device = AtlasI2C() 23 | device_address_list = device.list_i2c_devices() 24 | device_list = [] 25 | 26 | for i in device_address_list: 27 | device.set_i2c_address(i) 28 | response = device.query("i") 29 | 30 | # check if the device is an EZO device 31 | checkEzo = response.split(",") 32 | if len(checkEzo) > 0: 33 | if checkEzo[0].endswith("?I"): 34 | # yes - this is an EZO device 35 | moduletype = checkEzo[1] 36 | response = device.query("name,?").split(",")[1] 37 | device_list.append(AtlasI2C(address = i, moduletype = moduletype, name = response)) 38 | return device_list 39 | 40 | def print_help_text(): 41 | print(''' 42 | >> Atlas Scientific I2C sample code 43 | >> Any commands entered are passed to the default target device via I2C except: 44 | - Help 45 | brings up this menu 46 | - List 47 | lists the available I2C circuits. 48 | the --> indicates the target device that will receive individual commands 49 | - xxx:[command] 50 | sends the command to the device at I2C address xxx 51 | and sets future communications to that address 52 | Ex: "102:status" will send the command status to address 102 53 | - all:[command] 54 | sends the command to all devices 55 | - Poll[,x.xx] 56 | command continuously polls all devices 57 | the optional argument [,x.xx] lets you set a polling time 58 | where x.xx is greater than the minimum %0.2f second timeout. 59 | by default it will poll every %0.2f seconds 60 | >> Pressing ctrl-c will stop the polling 61 | ''' % (AtlasI2C.LONG_TIMEOUT, AtlasI2C.LONG_TIMEOUT)) 62 | 63 | def main(): 64 | 65 | device_list = get_devices() 66 | 67 | if len(device_list) == 0: 68 | print ("No EZO devices found") 69 | exit() 70 | 71 | device = device_list[0] 72 | 73 | print_help_text() 74 | 75 | print_devices(device_list, device) 76 | 77 | real_raw_input = vars(__builtins__).get('raw_input', input) 78 | 79 | while True: 80 | 81 | user_cmd = real_raw_input(">> Enter command: ") 82 | 83 | # show all the available devices 84 | if user_cmd.upper().strip().startswith("LIST"): 85 | print_devices(device_list, device) 86 | 87 | # print the help text 88 | elif user_cmd.upper().startswith("HELP"): 89 | print_help_text() 90 | 91 | # continuous polling command automatically polls the board 92 | elif user_cmd.upper().strip().startswith("POLL"): 93 | cmd_list = user_cmd.split(',') 94 | if len(cmd_list) > 1: 95 | delaytime = float(cmd_list[1]) 96 | else: 97 | delaytime = device.long_timeout 98 | 99 | # check for polling time being too short, change it to the minimum timeout if too short 100 | if delaytime < device.long_timeout: 101 | print("Polling time is shorter than timeout, setting polling time to %0.2f" % device.long_timeout) 102 | delaytime = device.long_timeout 103 | try: 104 | while True: 105 | print("-------press ctrl-c to stop the polling") 106 | for dev in device_list: 107 | dev.write("R") 108 | time.sleep(delaytime) 109 | for dev in device_list: 110 | print(dev.read()) 111 | 112 | except KeyboardInterrupt: # catches the ctrl-c command, which breaks the loop above 113 | print("Continuous polling stopped") 114 | print_devices(device_list, device) 115 | 116 | # send a command to all the available devices 117 | elif user_cmd.upper().strip().startswith("ALL:"): 118 | cmd_list = user_cmd.split(":") 119 | for dev in device_list: 120 | dev.write(cmd_list[1]) 121 | 122 | # figure out how long to wait before reading the response 123 | timeout = device_list[0].get_command_timeout(cmd_list[1].strip()) 124 | # if we dont have a timeout, dont try to read, since it means we issued a sleep command 125 | if(timeout): 126 | time.sleep(timeout) 127 | for dev in device_list: 128 | print(dev.read()) 129 | 130 | # if not a special keyword, see if we change the address, and communicate with that device 131 | else: 132 | try: 133 | cmd_list = user_cmd.split(":") 134 | if(len(cmd_list) > 1): 135 | addr = cmd_list[0] 136 | 137 | # go through the devices to figure out if its available 138 | # and swith to it if it is 139 | switched = False 140 | for i in device_list: 141 | if(i.address == int(addr)): 142 | device = i 143 | switched = True 144 | if(switched): 145 | print(device.query(cmd_list[1])) 146 | else: 147 | print("No device found at address " + addr) 148 | else: 149 | # if no address change, just send the command to the device 150 | print(device.query(user_cmd)) 151 | except IOError: 152 | print("Query failed \n - Address may be invalid, use list command to see available addresses") 153 | 154 | 155 | if __name__ == '__main__': 156 | main() 157 | --------------------------------------------------------------------------------