├── Makefile ├── README.md ├── atsci_ph.cpp ├── atsci_ec.cpp └── atsci_do.cpp /Makefile: -------------------------------------------------------------------------------- 1 | all: atsci_ph atsci_ec atsci_do 2 | 3 | atsci_ph: atsci_ph.cpp 4 | g++ -Wall -Wextra -std=c++98 atsci_ph.cpp -o atsci_ph 5 | 6 | atsci_ec: atsci_ec.cpp 7 | g++ -Wall -Wextra -std=c++98 atsci_ec.cpp -o atsci_ec 8 | 9 | atsci_do: atsci_do.cpp 10 | g++ -Wall -Wextra -std=c++98 atsci_do.cpp -o atsci_do 11 | 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # atlas_scientific 2 | Atlas Scientific EZO pH, EC and dissolved oxygen circuit I2C CLI tools 3 | 4 | Developed originally for Raspberry Pi. Works at least with version 1.6 firmware (you can get the version with the 'info' command). Output of the commands is intended to be machine (eg. shell script) readable. 5 | 6 | Build using 'make'. You might need to install libraries like libi2c-dev if it doesn't build. 7 | 8 | Make sure you have set the chip up in I2C mode; UART is the default. See the Atlas Scientific PDF for how to do this. 9 | 10 | The I2C device should be /dev/i2c-0 or /dev/i2c-1 depending on your Raspberry Pi revision. 11 | 12 | These commands exit with status 0 if everything went OK. 13 | 14 | Usege: 15 | 16 | ``` 17 | $ ./atsci_ec 18 | Atlas Scientific EZO class EC sensor I2C driver 19 | Author: Jaakko Salo (jaakkos@gmail.com) 20 | 21 | Usage: atsci_ec [arguments ...] 22 | 23 | Device is the Linux device node, like /dev/i2c-2. 24 | Supported operations: 25 | 26 | read Get a reading from the probe 27 | read_avg Read count times and return average. 28 | info Get device type and firmware version 29 | status Get reason for previous restart, and voltage at VCC pin 30 | temp get Query current temperature compensation value 31 | temp set Set temperature compensation value (Celsius) 32 | K get Query probe K constant 33 | K set Set probe K constant 34 | led get Query LED status 35 | led set Turn LED on/off 36 | cal get Get calibration status 37 | cal clear Clear all calibration data 38 | cal dry Start calibration. Probe must be dry first. 39 | cal one Single point calibration at EC (after 'cal dry'). Ends calibration. 40 | cal low Start dual point calibration (after 'cal dry'). Low point at EC. 41 | cal high Continue dual point calibration (after 'cal dry'). High point at EC. 42 | sleep Enter low-power sleep mode. 43 | 44 | $ ./atsci_ph 45 | Atlas Scientific EZO class pH sensor I2C driver 46 | Author: Jaakko Salo (jaakkos@gmail.com) 47 | 48 | Usage: atsci_ph [arguments ...] 49 | 50 | Device is the Linux device node, like /dev/i2c-2. 51 | Supported operations: 52 | 53 | read Get a reading from the probe 54 | read_avg Read count times and return average. 55 | info Get device type and firmware version 56 | status Get reason for previous restart, and voltage at VCC pin 57 | temp get Query current temperature compensation value 58 | temp set Set temperature compensation value (Celsius) 59 | led get Query LED status 60 | led set Turn LED on/off 61 | cal get Get calibration status 62 | cal clear Clear all calibration data 63 | cal mid Midpoint calibration at given pH, preferably 7.00. Clears low 64 | and high calibration points, so this must be done first! 65 | cal low Lowpoint calibration at given pH, should be from 1.00 to 6.00 66 | cal high Highpoint calibration at given pH, should be from 8.00 to 14.00 67 | 68 | $ ./atsci_do 69 | Atlas Scientific EZO class dissolved oxygen sensor I2C driver 70 | Author: Jaakko Salo (jaakkos@gmail.com) 71 | 72 | Usage: atsci_do [arguments ...] 73 | 74 | Device is the Linux device node, like /dev/i2c-2. 75 | Supported operations: 76 | 77 | read_saturation Get saturation reading from the probe 78 | read_do Get dissolved oxygen reading in mg/L 79 | read_avgsat Read count times and return average 80 | read_avgdo Read count times and return average 81 | info Get device type and firmware version 82 | status Get reason for previous restart, and voltage at VCC pin 83 | temp get Query current temperature compensation value 84 | temp set Set temperature compensation value (Celsius) 85 | EC get Query current conductivity compensation value (uS/cm) 86 | EC set Set conductivity compensation value (uS/cm) 87 | pressure get Query current pressure compensation value (kPa) 88 | pressure set

Set pressure compensation value (kPa) 89 | led get Query LED status 90 | led set Turn LED on/off 91 | cal get Get calibration status 92 | cal clear Clear all calibration data 93 | cal zero Calibrate at zero dissolved oxygen level 94 | cal atmospheric Calibrate at atmospheric oxygen levels 95 | sleep Enter low-power sleep mode. 96 | ``` 97 | -------------------------------------------------------------------------------- /atsci_ph.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | void usage() { 17 | std::cout << "Atlas Scientific EZO class pH sensor I2C driver\n" 18 | "Author: Jaakko Salo (jaakkos@gmail.com)\n" 19 | "\n" 20 | "Usage: atsci_ph [arguments ...]\n" 21 | "\n" 22 | "Device is the Linux device node, like /dev/i2c-2.\n" 23 | "Supported operations:\n" 24 | "\n" 25 | " read Get a reading from the probe\n" 26 | " read_avg Read count times and return average.\n" 27 | " info Get device type and firmware version\n" 28 | " status Get reason for previous restart, and voltage at VCC pin\n" 29 | " temp get Query current temperature compensation value\n" 30 | " temp set Set temperature compensation value (Celsius)\n" 31 | " led get Query LED status\n" 32 | " led set Turn LED on/off\n" 33 | " cal get Get calibration status\n" 34 | " cal clear Clear all calibration data\n" 35 | " cal mid Midpoint calibration at given pH, preferably 7.00. Clears low\n" 36 | " and high calibration points, so this must be done first!\n" 37 | " cal low Lowpoint calibration at given pH, should be from 1.00 to 6.00\n" 38 | " cal high Highpoint calibration at given pH, should be from 8.00 to 14.00\n" 39 | "\n"; 40 | 41 | exit(1); 42 | } 43 | 44 | int read_string(std::string &out, int dev) { 45 | char buf[33] = {0}; 46 | out = ""; 47 | 48 | if(read(dev, buf, 32) < 1) { 49 | perror("read"); 50 | std::cout << "I2C read failed." << std::endl; 51 | return 1; 52 | } 53 | 54 | for(int byte=0; byte<32; byte++) 55 | buf[byte] &= 0x7F; 56 | 57 | out = std::string(buf); 58 | 59 | if(out[0] != 1) { 60 | std::cout << "Command failed. The error from device was: "; 61 | switch((unsigned char)out[0]) { 62 | case 255: std::cout << "No Data (no pending request)" << std::endl; break; 63 | case 254: std::cout << "Pending (request still being processed)" << std::endl; break; 64 | case 2: std::cout << "Failed (the request failed)" << std::endl; break; 65 | default: std::cout << "Unknown" << std::endl; 66 | } 67 | 68 | return 1; 69 | } 70 | 71 | out = out.substr(1); 72 | // std::cout << "Outputting: " << out << std::endl; 73 | return 0; 74 | } 75 | 76 | int write_string(const std::string &cmd, int dev) { 77 | if(write(dev, cmd.c_str(), cmd.size()) != (int)cmd.size()) { 78 | perror("write"); 79 | std::cout << "I2C write failed." << std::endl; 80 | return 1; 81 | } 82 | 83 | return 0; 84 | } 85 | 86 | int do_read(std::vector& args, int dev, float *out) { 87 | if(args.size() != 3 && !out) usage(); 88 | 89 | if(write_string("R", dev) != 0) 90 | return 1; 91 | 92 | usleep(1050000); // Sleep min 1 second 93 | 94 | std::string result; 95 | if(read_string(result, dev) != 0) 96 | return 1; 97 | 98 | float pH; 99 | if(sscanf(result.c_str(), "%f", &pH) != 1) { 100 | std::cout << "Float conversion of the result failed. The raw result was " << result << std::endl; 101 | return 1; 102 | } 103 | 104 | if(out) *out = pH; 105 | else printf("%.2f\n", pH); 106 | return 0; 107 | } 108 | 109 | int do_read_avg(std::vector& args, int dev) { 110 | if(args.size() != 4) usage(); 111 | 112 | int count; 113 | 114 | if(sscanf(args[3].c_str(), "%d", &count) != 1) { 115 | std::cout << "Invalid argument." << std::endl; 116 | return 1; 117 | } 118 | 119 | float avg = 0.0; 120 | 121 | for(int i=0; i& args, int dev) { 135 | if(args.size() != 3) usage(); 136 | 137 | if(write_string("I", dev) != 0) 138 | return 1; 139 | 140 | usleep(350000); // Sleep min 300 milliseconds 141 | 142 | std::string result; 143 | if(read_string(result, dev) != 0) 144 | return 1; 145 | 146 | if(result.length() < 3) { 147 | std::cout << "Invalid info string returned: " << result << std::endl; 148 | return 1; 149 | } 150 | 151 | std::cout << "Device info string: " << result.substr(3) << std::endl; 152 | return 0; 153 | } 154 | 155 | int do_status(const std::vector& args, int dev) { 156 | if(args.size() != 3) usage(); 157 | 158 | if(write_string("STATUS", dev) != 0) 159 | return 1; 160 | 161 | usleep(350000); // Sleep min 300 milliseconds 162 | 163 | std::string result; 164 | if(read_string(result, dev) != 0) 165 | return 1; 166 | 167 | char reason; 168 | float vcc; 169 | if((result.length() < 8) || (sscanf(result.c_str() + 8, "%c,%f", &reason, &vcc) != 2)) { 170 | std::cout << "Invalid status string returned: " << result << std::endl; 171 | return 1; 172 | } 173 | 174 | std::string sreason; 175 | switch(reason) { 176 | case 'P': sreason = "power on reset"; break; 177 | case 'S': sreason = "software reset"; break; 178 | case 'B': sreason = "brown out reset"; break; 179 | case 'W': sreason = "watchdog reset"; break; 180 | default: sreason = "unknown"; 181 | } 182 | 183 | std::cout << "Last restart reason: " << sreason << ", voltage at VCC pin: " << vcc << std::endl; 184 | return 0; 185 | } 186 | 187 | int do_temp(const std::vector& args, int dev) { 188 | if(args.size() < 4) usage(); 189 | 190 | std::string tstr; 191 | 192 | if(args[3] == "get") { 193 | if(args.size() != 4) usage(); 194 | tstr = "?"; 195 | } 196 | 197 | else if(args[3] == "set") { 198 | if(args.size() != 5) usage(); 199 | 200 | float temp; 201 | if(sscanf(args[4].c_str(), "%f", &temp) != 1) { 202 | std::cout << "Invalid floating point as temperature: " << args[4] << std::endl; 203 | return 1; 204 | } 205 | 206 | tstr = args[4]; 207 | } 208 | 209 | else usage(); 210 | 211 | if(write_string(std::string("T,") + tstr, dev) != 0) 212 | return 1; 213 | 214 | usleep(350000); // Sleep min 300 milliseconds 215 | 216 | std::string result; 217 | if(read_string(result, dev) != 0) 218 | return 1; 219 | 220 | if(args[3] == "set") 221 | return 0; 222 | 223 | float temp; 224 | if((result.length() < 3) || (sscanf(result.c_str() + 3, "%f", &temp) != 1)) 225 | std::cout << "Invalid floating point from device: " << result << std::endl; 226 | 227 | std::cout << temp << std::endl; 228 | return 0; 229 | } 230 | 231 | int do_led(const std::vector& args, int dev) { 232 | if(args.size() < 4) usage(); 233 | 234 | std::string lstr; 235 | 236 | if(args[3] == "get") { 237 | if(args.size() != 4) usage(); 238 | lstr = "?"; 239 | } 240 | 241 | else if(args[3] == "set") { 242 | if(args.size() != 5) usage(); 243 | 244 | if(args[4] == "on") lstr = "1"; 245 | else if(args[4] == "off") lstr = "0"; 246 | else { 247 | std::cout << "State must be on or off." << std::endl; 248 | return 1; 249 | } 250 | } 251 | 252 | else usage(); 253 | 254 | if(write_string(std::string("L,") + lstr, dev) != 0) 255 | return 1; 256 | 257 | usleep(350000); // Sleep min 300 milliseconds 258 | 259 | std::string result; 260 | if(read_string(result, dev) != 0) 261 | return 1; 262 | 263 | if(args[3] == "set") 264 | return 0; 265 | 266 | if(result.length() < 4) { 267 | std::cout << "Invalid LED state from device: " << result << std::endl; 268 | return 1; 269 | } 270 | 271 | switch(result[3]) { 272 | case '1': std::cout << "on" << std::endl; return 0; 273 | case '0': std::cout << "off" << std::endl; return 0; 274 | default: 275 | std::cout << "Invalid LED state from device: " << result << std::endl; 276 | return 1; 277 | } 278 | } 279 | 280 | int do_cal(const std::vector& args, int dev) { 281 | std::string result; 282 | 283 | if(args.size() < 4) usage(); 284 | 285 | if(args[3] == "get") { 286 | if(args.size() != 4) usage(); 287 | 288 | if(write_string(std::string("Cal,?"), dev) != 0) 289 | return 1; 290 | 291 | usleep(350000); // Sleep min 300 milliseconds 292 | 293 | if(read_string(result, dev) != 0) 294 | return 1; 295 | 296 | if(result.length() < 6) { 297 | std::cout << "Invalid calibration state from device: " << result << std::endl; 298 | return 1; 299 | } 300 | 301 | switch(result[5]) { 302 | case '0': std::cout << "Not calibrated." << std::endl; break; 303 | case '1': std::cout << "Single-point calibrated." << std::endl; break; 304 | case '2': std::cout << "Two-point calibrated." << std::endl; break; 305 | case '3': std::cout << "Three-point calibrated." << std::endl; break; 306 | default: std::cout << "Unknown calibration status." << std::endl; break; 307 | } 308 | 309 | return 0; 310 | } 311 | 312 | else if(args[3] == "clear") { 313 | if(args.size() != 4) usage(); 314 | 315 | if(write_string(std::string("Cal,clear"), dev) != 0) 316 | return 1; 317 | 318 | usleep(350000); // Sleep min 300 milliseconds 319 | 320 | return read_string(result, dev); 321 | } 322 | 323 | if(args[3] != "mid" && args[3] != "low" && args[3] != "high") 324 | usage(); 325 | 326 | if(args.size() != 5) usage(); 327 | 328 | float pH; 329 | if(sscanf(args[4].c_str(), "%f", &pH) != 1) { 330 | std::cout << "Invalid floating point as pH." << std::endl; 331 | return 1; 332 | } 333 | 334 | char pHstr[6]; 335 | snprintf(pHstr, 6, "%.2f", pH); 336 | 337 | if(write_string(std::string("Cal,") + args[3] + "," + pHstr, dev) != 0) 338 | return 1; 339 | 340 | usleep(1350000); // Sleep min 1.3 seconds 341 | 342 | return read_string(result, dev); 343 | } 344 | 345 | int init_dev(const std::vector& args) { 346 | int dev = open(args[1].c_str(), O_RDWR); 347 | if(dev < 0) { 348 | perror("open"); 349 | std::cout << "Failed to open the device node; exiting." << std::endl; 350 | return -1; 351 | } 352 | 353 | if(ioctl(dev, I2C_SLAVE, 0x63) < 0) { 354 | perror("ioctl"); 355 | std::cout << "Unable to set I2C slave address." << std::endl; 356 | return -1; 357 | } 358 | 359 | return dev; 360 | } 361 | 362 | int main(int argc, char **argv) { 363 | if(argc < 3) usage(); 364 | std::vector args(argv, argv+argc); 365 | 366 | int dev = init_dev(args); 367 | if(dev < 0) return 1; 368 | 369 | if(args[2] == "read") return do_read(args, dev, NULL); 370 | else if(args[2] == "info") return do_info(args, dev); 371 | else if(args[2] == "status") return do_status(args, dev); 372 | else if(args[2] == "temp") return do_temp(args, dev); 373 | else if(args[2] == "led") return do_led(args, dev); 374 | else if(args[2] == "cal") return do_cal(args, dev); 375 | else if(args[2] == "read_avg") return do_read_avg(args, dev); 376 | else usage(); 377 | } 378 | -------------------------------------------------------------------------------- /atsci_ec.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | void usage() { 17 | std::cout << "Atlas Scientific EZO class EC sensor I2C driver\n" 18 | "Author: Jaakko Salo (jaakkos@gmail.com)\n" 19 | "\n" 20 | "Usage: atsci_ec [arguments ...]\n" 21 | "\n" 22 | "Device is the Linux device node, like /dev/i2c-2.\n" 23 | "Supported operations:\n" 24 | "\n" 25 | " read Get a reading from the probe\n" 26 | " read_avg Read count times and return average.\n" 27 | " info Get device type and firmware version\n" 28 | " status Get reason for previous restart, and voltage at VCC pin\n" 29 | " temp get Query current temperature compensation value\n" 30 | " temp set Set temperature compensation value (Celsius)\n" 31 | " K get Query probe K constant\n" 32 | " K set Set probe K constant\n" 33 | " led get Query LED status\n" 34 | " led set Turn LED on/off\n" 35 | " cal get Get calibration status\n" 36 | " cal clear Clear all calibration data\n" 37 | " cal dry Start calibration. Probe must be dry first.\n" 38 | " cal one Single point calibration at EC (after 'cal dry'). Ends calibration.\n" 39 | " cal low Start dual point calibration (after 'cal dry'). Low point at EC.\n" 40 | " cal high Continue dual point calibration (after 'cal dry'). High point at EC.\n" 41 | " sleep Enter low-power sleep mode.\n" 42 | "\n"; 43 | 44 | exit(1); 45 | } 46 | 47 | int read_string(std::string &out, int dev) { 48 | char buf[65] = {0}; 49 | out = ""; 50 | 51 | if(read(dev, buf, 64) < 1) { 52 | perror("read"); 53 | std::cout << "I2C read failed." << std::endl; 54 | return 1; 55 | } 56 | 57 | for(int byte=0; byte<64; byte++) 58 | buf[byte] &= 0x7F; 59 | 60 | out = std::string(buf); 61 | 62 | if(out[0] != 1) { 63 | std::cout << "Command failed. The error from device was: "; 64 | switch((unsigned char)out[0]) { 65 | case 255: std::cout << "No Data (no pending request)" << std::endl; break; 66 | case 254: std::cout << "Pending (request still being processed)" << std::endl; break; 67 | case 2: std::cout << "Failed (the request failed)" << std::endl; break; 68 | default: std::cout << "Unknown" << std::endl; 69 | } 70 | 71 | return 1; 72 | } 73 | 74 | out = out.substr(1); 75 | //std::cout << "Outputting: " << out << std::endl; 76 | return 0; 77 | } 78 | 79 | int write_string(const std::string &cmd, int dev) { 80 | //std::cout << "Writing: " << cmd << std::endl; 81 | 82 | if(write(dev, cmd.c_str(), cmd.size()) != (int)cmd.size()) { 83 | perror("write"); 84 | std::cout << "I2C write failed." << std::endl; 85 | return 1; 86 | } 87 | 88 | return 0; 89 | } 90 | 91 | int check_and_set_format(int dev) { 92 | if(write_string("O,?", dev) != 0) 93 | return 1; 94 | 95 | usleep(350000); // Sleep min 300 milliseconds 96 | 97 | std::string result; 98 | if(read_string(result, dev) != 0) 99 | return 1; 100 | 101 | if(result.find("EC") == std::string::npos) { 102 | if(write_string("O,EC,1", dev) != 0) 103 | return 1; 104 | 105 | usleep(350000); // Sleep min 300 milliseconds 106 | 107 | if(read_string(result, dev) != 0) 108 | return 1; 109 | } 110 | 111 | return 0; 112 | } 113 | 114 | int do_read(std::vector& args, int dev, float *out) { 115 | if(args.size() != 3 && !out) usage(); 116 | 117 | if(!out && check_and_set_format(dev) != 0) 118 | return 1; 119 | 120 | if(write_string("R", dev) != 0) 121 | return 1; 122 | 123 | usleep(1050000); // Sleep min 1 second 124 | 125 | std::string result; 126 | if(read_string(result, dev) != 0) 127 | return 1; 128 | 129 | float EC; 130 | if(sscanf(result.c_str(), "%f", &EC) != 1) { 131 | std::cout << "Float conversion of the result failed. The raw result was " << result << std::endl; 132 | return 1; 133 | } 134 | 135 | if(out) *out = EC; 136 | else std::cout << EC << std::endl; 137 | 138 | // Sleep out the electrical interference caused by the measurement 139 | usleep(1500000); 140 | 141 | return 0; 142 | } 143 | 144 | int do_read_avg(std::vector& args, int dev) { 145 | if(args.size() != 4) usage(); 146 | 147 | int count; 148 | 149 | if(sscanf(args[3].c_str(), "%d", &count) != 1) { 150 | std::cout << "Invalid argument." << std::endl; 151 | return 1; 152 | } 153 | 154 | if(check_and_set_format(dev) != 0) 155 | return 1; 156 | 157 | float avg = 0.0; 158 | 159 | for(int i=0; i& args, int dev) { 173 | if(args.size() != 3) usage(); 174 | 175 | if(write_string("I", dev) != 0) 176 | return 1; 177 | 178 | usleep(350000); // Sleep min 300 milliseconds 179 | 180 | std::string result; 181 | if(read_string(result, dev) != 0) 182 | return 1; 183 | 184 | if(result.length() < 3) { 185 | std::cout << "Invalid info string returned: " << result << std::endl; 186 | return 1; 187 | } 188 | 189 | std::cout << "Device info string: " << result.substr(3) << std::endl; 190 | return 0; 191 | } 192 | 193 | int do_status(const std::vector& args, int dev) { 194 | if(args.size() != 3) usage(); 195 | 196 | if(write_string("STATUS", dev) != 0) 197 | return 1; 198 | 199 | usleep(350000); // Sleep min 300 milliseconds 200 | 201 | std::string result; 202 | if(read_string(result, dev) != 0) 203 | return 1; 204 | 205 | char reason; 206 | float vcc; 207 | if((result.length() < 8) || (sscanf(result.c_str() + 8, "%c,%f", &reason, &vcc) != 2)) { 208 | std::cout << "Invalid status string returned: " << result << std::endl; 209 | return 1; 210 | } 211 | 212 | std::string sreason; 213 | switch(reason) { 214 | case 'P': sreason = "power on reset"; break; 215 | case 'S': sreason = "software reset"; break; 216 | case 'B': sreason = "brown out reset"; break; 217 | case 'W': sreason = "watchdog reset"; break; 218 | default: sreason = "unknown"; 219 | } 220 | 221 | std::cout << "Last restart reason: " << sreason << ", voltage at VCC pin: " << vcc << std::endl; 222 | return 0; 223 | } 224 | 225 | int do_temp(const std::vector& args, int dev) { 226 | if(args.size() < 4) usage(); 227 | 228 | std::string tstr; 229 | 230 | if(args[3] == "get") { 231 | if(args.size() != 4) usage(); 232 | tstr = "?"; 233 | } 234 | 235 | else if(args[3] == "set") { 236 | if(args.size() != 5) usage(); 237 | 238 | float temp; 239 | if(sscanf(args[4].c_str(), "%f", &temp) != 1) { 240 | std::cout << "Invalid floating point as temperature: " << args[4] << std::endl; 241 | return 1; 242 | } 243 | 244 | tstr = args[4]; 245 | } 246 | 247 | else usage(); 248 | 249 | if(write_string(std::string("T,") + tstr, dev) != 0) 250 | return 1; 251 | 252 | usleep(350000); // Sleep min 300 milliseconds 253 | 254 | std::string result; 255 | if(read_string(result, dev) != 0) 256 | return 1; 257 | 258 | if(args[3] == "set") 259 | return 0; 260 | 261 | float temp; 262 | if((result.length() < 3) || (sscanf(result.c_str() + 3, "%f", &temp) != 1)) 263 | std::cout << "Invalid floating point from device: " << result << std::endl; 264 | 265 | std::cout << temp << std::endl; 266 | return 0; 267 | } 268 | 269 | int do_K(const std::vector& args, int dev) { 270 | if(args.size() < 4) usage(); 271 | 272 | std::string tstr; 273 | 274 | if(args[3] == "get") { 275 | if(args.size() != 4) usage(); 276 | tstr = "?"; 277 | } 278 | 279 | else if(args[3] == "set") { 280 | if(args.size() != 5) usage(); 281 | 282 | float K; 283 | if(sscanf(args[4].c_str(), "%f", &K) != 1) { 284 | std::cout << "Invalid floating point as K: " << args[4] << std::endl; 285 | return 1; 286 | } 287 | 288 | tstr = args[4]; 289 | } 290 | 291 | else usage(); 292 | 293 | if(write_string(std::string("K,") + tstr, dev) != 0) 294 | return 1; 295 | 296 | usleep(350000); // Sleep min 300 milliseconds 297 | 298 | std::string result; 299 | if(read_string(result, dev) != 0) 300 | return 1; 301 | 302 | if(args[3] == "set") 303 | return 0; 304 | 305 | float K; 306 | if((result.length() < 3) || (sscanf(result.c_str() + 3, "%f", &K) != 1)) 307 | std::cout << "Invalid floating point from device: " << result << std::endl; 308 | 309 | std::cout << K << std::endl; 310 | return 0; 311 | } 312 | 313 | int do_led(const std::vector& args, int dev) { 314 | if(args.size() < 4) usage(); 315 | 316 | std::string lstr; 317 | 318 | if(args[3] == "get") { 319 | if(args.size() != 4) usage(); 320 | lstr = "?"; 321 | } 322 | 323 | else if(args[3] == "set") { 324 | if(args.size() != 5) usage(); 325 | 326 | if(args[4] == "on") lstr = "1"; 327 | else if(args[4] == "off") lstr = "0"; 328 | else { 329 | std::cout << "State must be on or off." << std::endl; 330 | return 1; 331 | } 332 | } 333 | 334 | else usage(); 335 | 336 | if(write_string(std::string("L,") + lstr, dev) != 0) 337 | return 1; 338 | 339 | usleep(350000); // Sleep min 300 milliseconds 340 | 341 | std::string result; 342 | if(read_string(result, dev) != 0) 343 | return 1; 344 | 345 | if(args[3] == "set") 346 | return 0; 347 | 348 | if(result.length() < 4) { 349 | std::cout << "Invalid LED state from device: " << result << std::endl; 350 | return 1; 351 | } 352 | 353 | switch(result[3]) { 354 | case '1': std::cout << "on" << std::endl; return 0; 355 | case '0': std::cout << "off" << std::endl; return 0; 356 | default: 357 | std::cout << "Invalid LED state from device: " << result << std::endl; 358 | return 1; 359 | } 360 | } 361 | 362 | int do_cal(const std::vector& args, int dev) { 363 | std::string result; 364 | 365 | if(args.size() < 4) usage(); 366 | 367 | if(args[3] == "get") { 368 | if(args.size() != 4) usage(); 369 | 370 | if(write_string(std::string("Cal,?"), dev) != 0) 371 | return 1; 372 | 373 | usleep(350000); // Sleep min 300 milliseconds 374 | 375 | if(read_string(result, dev) != 0) 376 | return 1; 377 | 378 | if(result.length() < 6) { 379 | std::cout << "Invalid calibration state from device: " << result << std::endl; 380 | return 1; 381 | } 382 | 383 | switch(result[5]) { 384 | case '0': std::cout << "Not calibrated." << std::endl; break; 385 | case '1': std::cout << "Single-point calibrated." << std::endl; break; 386 | case '2': std::cout << "Two-point calibrated." << std::endl; break; 387 | default: std::cout << "Unknown calibration status." << std::endl; break; 388 | } 389 | 390 | return 0; 391 | } 392 | 393 | else if(args[3] == "clear") { 394 | if(args.size() != 4) usage(); 395 | 396 | if(write_string("Cal,clear", dev) != 0) 397 | return 1; 398 | 399 | usleep(500000); // Sleep min 300 milliseconds 400 | 401 | return read_string(result, dev); 402 | } 403 | 404 | else if(args[3] == "dry") { 405 | if(args.size() != 4) usage(); 406 | 407 | if(write_string("Cal,dry", dev) != 0) 408 | return 1; 409 | 410 | usleep(2000000); // Sleep min 1.3 seconds 411 | 412 | return read_string(result, dev); 413 | } 414 | 415 | if(args[3] != "one" && args[3] != "low" && args[3] != "high") 416 | usage(); 417 | 418 | if(args.size() != 5) usage(); 419 | 420 | float EC; 421 | if(sscanf(args[4].c_str(), "%f", &EC) != 1) { 422 | std::cout << "Invalid floating point as EC." << std::endl; 423 | return 1; 424 | } 425 | 426 | char ECstr[16]; 427 | snprintf(ECstr, 16, "%.3f", EC); 428 | 429 | if(write_string(std::string("Cal,") + args[3] + "," + ECstr, dev) != 0) 430 | return 1; 431 | 432 | usleep(2000000); // Sleep min 1.3 seconds 433 | 434 | return read_string(result, dev); 435 | } 436 | 437 | int do_sleep(const std::vector& args, int dev) { 438 | if(args.size() != 3) usage(); 439 | 440 | if(write_string("SLEEP", dev) != 0) 441 | return 1; 442 | 443 | return 0; 444 | } 445 | 446 | int init_dev(const std::vector& args) { 447 | int dev = open(args[1].c_str(), O_RDWR); 448 | if(dev < 0) { 449 | perror("open"); 450 | std::cout << "Failed to open the device node; exiting." << std::endl; 451 | return -1; 452 | } 453 | 454 | if(ioctl(dev, I2C_SLAVE, 0x64) < 0) { 455 | perror("ioctl"); 456 | std::cout << "Unable to set I2C slave address." << std::endl; 457 | return -1; 458 | } 459 | 460 | return dev; 461 | } 462 | 463 | int main(int argc, char **argv) { 464 | if(argc < 3) usage(); 465 | std::vector args(argv, argv+argc); 466 | 467 | int dev = init_dev(args); 468 | if(dev < 0) return 1; 469 | 470 | if(args[2] == "read") return do_read(args, dev, NULL); 471 | else if(args[2] == "read_avg") return do_read_avg(args, dev); 472 | else if(args[2] == "info") return do_info(args, dev); 473 | else if(args[2] == "status") return do_status(args, dev); 474 | else if(args[2] == "temp") return do_temp(args, dev); 475 | else if(args[2] == "led") return do_led(args, dev); 476 | else if(args[2] == "cal") return do_cal(args, dev); 477 | else if(args[2] == "K") return do_K(args, dev); 478 | else if(args[2] == "sleep") return do_sleep(args, dev); 479 | else usage(); 480 | } 481 | -------------------------------------------------------------------------------- /atsci_do.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | void usage() { 17 | std::cout << "Atlas Scientific EZO class dissolved oxygen sensor I2C driver\n" 18 | "Author: Jaakko Salo (jaakkos@gmail.com)\n" 19 | "\n" 20 | "Usage: atsci_do [arguments ...]\n" 21 | "\n" 22 | "Device is the Linux device node, like /dev/i2c-2.\n" 23 | "Supported operations:\n" 24 | "\n" 25 | " read_saturation Get saturation reading from the probe\n" 26 | " read_do Get dissolved oxygen reading in mg/L\n" 27 | " read_avgsat Read count times and return average\n" 28 | " read_avgdo Read count times and return average\n" 29 | " info Get device type and firmware version\n" 30 | " status Get reason for previous restart, and voltage at VCC pin\n" 31 | " temp get Query current temperature compensation value\n" 32 | " temp set Set temperature compensation value (Celsius)\n" 33 | " EC get Query current conductivity compensation value (uS/cm)\n" 34 | " EC set Set conductivity compensation value (uS/cm)\n" 35 | " pressure get Query current pressure compensation value (kPa)\n" 36 | " pressure set

Set pressure compensation value (kPa)\n" 37 | " led get Query LED status\n" 38 | " led set Turn LED on/off\n" 39 | " cal get Get calibration status\n" 40 | " cal clear Clear all calibration data\n" 41 | " cal zero Calibrate at zero dissolved oxygen level\n" 42 | " cal atmospheric Calibrate at atmospheric oxygen levels\n" 43 | " sleep Enter low-power sleep mode.\n" 44 | "\n"; 45 | 46 | exit(1); 47 | } 48 | 49 | int read_string(std::string &out, int dev) { 50 | char buf[65] = {0}; 51 | out = ""; 52 | 53 | if(read(dev, buf, 64) < 1) { 54 | perror("read"); 55 | std::cout << "I2C read failed." << std::endl; 56 | return 1; 57 | } 58 | 59 | for(int byte=0; byte<64; byte++) 60 | buf[byte] &= 0x7F; 61 | 62 | out = std::string(buf); 63 | 64 | if(out[0] != 1) { 65 | std::cout << "Command failed. The error from device was: "; 66 | switch((unsigned char)out[0]) { 67 | case 255: std::cout << "No Data (no pending request)" << std::endl; break; 68 | case 254: std::cout << "Pending (request still being processed)" << std::endl; break; 69 | case 2: std::cout << "Failed (the request failed)" << std::endl; break; 70 | default: std::cout << "Unknown" << std::endl; 71 | } 72 | 73 | return 1; 74 | } 75 | 76 | out = out.substr(1); 77 | //std::cout << "Outputting: " << out << std::endl; 78 | return 0; 79 | } 80 | 81 | int write_string(const std::string &cmd, int dev) { 82 | //std::cout << "Writing: " << cmd << std::endl; 83 | 84 | if(write(dev, cmd.c_str(), cmd.size()) != (int)cmd.size()) { 85 | perror("write"); 86 | std::cout << "I2C write failed." << std::endl; 87 | return 1; 88 | } 89 | 90 | return 0; 91 | } 92 | 93 | int check_and_set_format(int dev) { 94 | if(write_string("O,?", dev) != 0) 95 | return 1; 96 | 97 | usleep(350000); // Sleep min 300 milliseconds 98 | 99 | std::string result; 100 | if(read_string(result, dev) != 0) 101 | return 1; 102 | 103 | if(result.find("%") == std::string::npos) { 104 | if(write_string("O,%,1", dev) != 0) 105 | return 1; 106 | 107 | usleep(350000); // Sleep min 300 milliseconds 108 | 109 | if(read_string(result, dev) != 0) 110 | return 1; 111 | } 112 | 113 | if(result.find("DO") == std::string::npos) { 114 | if(write_string("O,DO,1", dev) != 0) 115 | return 1; 116 | 117 | usleep(350000); // Sleep min 300 milliseconds 118 | 119 | if(read_string(result, dev) != 0) 120 | return 1; 121 | } 122 | 123 | return 0; 124 | } 125 | 126 | int do_read(int dev, float *dissoxy, float *saturation) { 127 | if(write_string("R", dev) != 0) 128 | return 1; 129 | 130 | usleep(1050000); // Sleep min 1 second 131 | 132 | std::string result; 133 | if(read_string(result, dev) != 0) 134 | return 1; 135 | 136 | if(sscanf(result.c_str(), "%f,%f", dissoxy, saturation) != 2) { 137 | std::cout << "Float conversion of the result failed. The raw result was " << result << std::endl; 138 | return 1; 139 | } 140 | 141 | return 0; 142 | } 143 | 144 | int do_read_saturation(std::vector& args, int dev, float *out) { 145 | if(args.size() != 3 && !out) usage(); 146 | 147 | if(!out && check_and_set_format(dev) != 0) 148 | return 1; 149 | 150 | float dissoxy, saturation; 151 | if(do_read(dev, &dissoxy, &saturation) != 0) 152 | return 1; 153 | 154 | if(out) *out = saturation; 155 | else std::cout << saturation << std::endl; 156 | 157 | return 0; 158 | } 159 | 160 | int do_read_dissoxy(std::vector& args, int dev, float *out) { 161 | if(args.size() != 3 && !out) usage(); 162 | 163 | if(!out && check_and_set_format(dev) != 0) 164 | return 1; 165 | 166 | float dissoxy, saturation; 167 | if(do_read(dev, &dissoxy, &saturation) != 0) 168 | return 1; 169 | 170 | if(out) *out = dissoxy; 171 | else std::cout << dissoxy << std::endl; 172 | 173 | return 0; 174 | } 175 | 176 | 177 | int do_read_avgsat(std::vector& args, int dev) { 178 | if(args.size() != 4) usage(); 179 | 180 | int count; 181 | 182 | if(sscanf(args[3].c_str(), "%d", &count) != 1) { 183 | std::cout << "Invalid argument." << std::endl; 184 | return 1; 185 | } 186 | 187 | if(check_and_set_format(dev) != 0) 188 | return 1; 189 | 190 | float avg = 0.0; 191 | 192 | for(int i=0; i& args, int dev) { 206 | if(args.size() != 4) usage(); 207 | 208 | int count; 209 | 210 | if(sscanf(args[3].c_str(), "%d", &count) != 1) { 211 | std::cout << "Invalid argument." << std::endl; 212 | return 1; 213 | } 214 | 215 | if(check_and_set_format(dev) != 0) 216 | return 1; 217 | 218 | float avg = 0.0; 219 | 220 | for(int i=0; i& args, int dev) { 234 | if(args.size() != 3) usage(); 235 | 236 | if(write_string("I", dev) != 0) 237 | return 1; 238 | 239 | usleep(350000); // Sleep min 300 milliseconds 240 | 241 | std::string result; 242 | if(read_string(result, dev) != 0) 243 | return 1; 244 | 245 | if(result.length() < 3) { 246 | std::cout << "Invalid info string returned: " << result << std::endl; 247 | return 1; 248 | } 249 | 250 | std::cout << "Device info string: " << result.substr(3) << std::endl; 251 | return 0; 252 | } 253 | 254 | int do_status(const std::vector& args, int dev) { 255 | if(args.size() != 3) usage(); 256 | 257 | if(write_string("STATUS", dev) != 0) 258 | return 1; 259 | 260 | usleep(350000); // Sleep min 300 milliseconds 261 | 262 | std::string result; 263 | if(read_string(result, dev) != 0) 264 | return 1; 265 | 266 | char reason; 267 | float vcc; 268 | if((result.length() < 8) || (sscanf(result.c_str() + 8, "%c,%f", &reason, &vcc) != 2)) { 269 | std::cout << "Invalid status string returned: " << result << std::endl; 270 | return 1; 271 | } 272 | 273 | std::string sreason; 274 | switch(reason) { 275 | case 'P': sreason = "power on reset"; break; 276 | case 'S': sreason = "software reset"; break; 277 | case 'B': sreason = "brown out reset"; break; 278 | case 'W': sreason = "watchdog reset"; break; 279 | default: sreason = "unknown"; 280 | } 281 | 282 | std::cout << "Last restart reason: " << sreason << ", voltage at VCC pin: " << vcc << std::endl; 283 | return 0; 284 | } 285 | 286 | int do_temp(const std::vector& args, int dev) { 287 | if(args.size() < 4) usage(); 288 | 289 | std::string tstr; 290 | 291 | if(args[3] == "get") { 292 | if(args.size() != 4) usage(); 293 | tstr = "?"; 294 | } 295 | 296 | else if(args[3] == "set") { 297 | if(args.size() != 5) usage(); 298 | 299 | float temp; 300 | if(sscanf(args[4].c_str(), "%f", &temp) != 1) { 301 | std::cout << "Invalid floating point as temperature: " << args[4] << std::endl; 302 | return 1; 303 | } 304 | 305 | tstr = args[4]; 306 | } 307 | 308 | else usage(); 309 | 310 | if(write_string(std::string("T,") + tstr, dev) != 0) 311 | return 1; 312 | 313 | usleep(350000); // Sleep min 300 milliseconds 314 | 315 | std::string result; 316 | if(read_string(result, dev) != 0) 317 | return 1; 318 | 319 | if(args[3] == "set") 320 | return 0; 321 | 322 | float temp; 323 | if((result.length() < 3) || (sscanf(result.c_str(), "?T,%f", &temp) != 1)) 324 | std::cout << "Invalid floating point from device: " << result << std::endl; 325 | 326 | std::cout << temp << std::endl; 327 | return 0; 328 | } 329 | 330 | int do_EC(const std::vector& args, int dev) { 331 | if(args.size() < 4) usage(); 332 | 333 | std::string tstr; 334 | 335 | if(args[3] == "get") { 336 | if(args.size() != 4) usage(); 337 | tstr = "?"; 338 | } 339 | 340 | else if(args[3] == "set") { 341 | if(args.size() != 5) usage(); 342 | 343 | float EC; 344 | if(sscanf(args[4].c_str(), "%f", &EC) != 1) { 345 | std::cout << "Invalid floating point as EC: " << args[4] << std::endl; 346 | return 1; 347 | } 348 | 349 | tstr = args[4]; 350 | } 351 | 352 | else usage(); 353 | 354 | if(write_string(std::string("S,") + tstr, dev) != 0) 355 | return 1; 356 | 357 | usleep(350000); // Sleep min 300 milliseconds 358 | 359 | std::string result; 360 | if(read_string(result, dev) != 0) 361 | return 1; 362 | 363 | if(args[3] == "set") 364 | return 0; 365 | 366 | float EC; 367 | if((result.length() < 3) || (sscanf(result.c_str(), "?S,%f", &EC) != 1)) 368 | std::cout << "Invalid floating point from device: " << result << std::endl; 369 | 370 | std::cout << EC << std::endl; 371 | return 0; 372 | } 373 | 374 | int do_pressure(const std::vector& args, int dev) { 375 | if(args.size() < 4) usage(); 376 | 377 | std::string tstr; 378 | 379 | if(args[3] == "get") { 380 | if(args.size() != 4) usage(); 381 | tstr = "?"; 382 | } 383 | 384 | else if(args[3] == "set") { 385 | if(args.size() != 5) usage(); 386 | 387 | float pressure; 388 | if(sscanf(args[4].c_str(), "%f", &pressure) != 1) { 389 | std::cout << "Invalid floating point as pressure: " << args[4] << std::endl; 390 | return 1; 391 | } 392 | 393 | tstr = args[4]; 394 | } 395 | 396 | else usage(); 397 | 398 | if(write_string(std::string("P,") + tstr, dev) != 0) 399 | return 1; 400 | 401 | usleep(350000); // Sleep min 300 milliseconds 402 | 403 | std::string result; 404 | if(read_string(result, dev) != 0) 405 | return 1; 406 | 407 | if(args[3] == "set") 408 | return 0; 409 | 410 | float pressure; 411 | if((result.length() < 3) || (sscanf(result.c_str(), "?P,%f", &pressure) != 1)) 412 | std::cout << "Invalid floating point from device: " << result << std::endl; 413 | 414 | std::cout << pressure << std::endl; 415 | return 0; 416 | } 417 | 418 | int do_led(const std::vector& args, int dev) { 419 | if(args.size() < 4) usage(); 420 | 421 | std::string lstr; 422 | 423 | if(args[3] == "get") { 424 | if(args.size() != 4) usage(); 425 | lstr = "?"; 426 | } 427 | 428 | else if(args[3] == "set") { 429 | if(args.size() != 5) usage(); 430 | 431 | if(args[4] == "on") lstr = "1"; 432 | else if(args[4] == "off") lstr = "0"; 433 | else { 434 | std::cout << "State must be on or off." << std::endl; 435 | return 1; 436 | } 437 | } 438 | 439 | else usage(); 440 | 441 | if(write_string(std::string("L,") + lstr, dev) != 0) 442 | return 1; 443 | 444 | usleep(350000); // Sleep min 300 milliseconds 445 | 446 | std::string result; 447 | if(read_string(result, dev) != 0) 448 | return 1; 449 | 450 | if(args[3] == "set") 451 | return 0; 452 | 453 | if(result.length() < 4) { 454 | std::cout << "Invalid LED state from device: " << result << std::endl; 455 | return 1; 456 | } 457 | 458 | switch(result[3]) { 459 | case '1': std::cout << "on" << std::endl; return 0; 460 | case '0': std::cout << "off" << std::endl; return 0; 461 | default: 462 | std::cout << "Invalid LED state from device: " << result << std::endl; 463 | return 1; 464 | } 465 | } 466 | 467 | int do_cal(const std::vector& args, int dev) { 468 | std::string result; 469 | 470 | if(args.size() < 4) usage(); 471 | 472 | if(args[3] == "get") { 473 | if(args.size() != 4) usage(); 474 | 475 | if(write_string(std::string("Cal,?"), dev) != 0) 476 | return 1; 477 | 478 | usleep(350000); // Sleep min 300 milliseconds 479 | 480 | if(read_string(result, dev) != 0) 481 | return 1; 482 | 483 | if(result.length() < 6) { 484 | std::cout << "Invalid calibration state from device: " << result << std::endl; 485 | return 1; 486 | } 487 | 488 | switch(result[5]) { 489 | case '0': std::cout << "Not calibrated." << std::endl; break; 490 | case '1': std::cout << "Single-point calibrated." << std::endl; break; 491 | case '2': std::cout << "Two-point calibrated." << std::endl; break; 492 | default: std::cout << "Unknown calibration status." << std::endl; break; 493 | } 494 | 495 | return 0; 496 | } 497 | 498 | else if(args[3] == "clear") { 499 | if(args.size() != 4) usage(); 500 | 501 | if(write_string("Cal,clear", dev) != 0) 502 | return 1; 503 | 504 | usleep(500000); // Sleep min 300 milliseconds 505 | 506 | return read_string(result, dev); 507 | } 508 | 509 | else if(args[3] == "zero") { 510 | if(args.size() != 4) usage(); 511 | 512 | if(write_string("Cal,0", dev) != 0) 513 | return 1; 514 | 515 | usleep(2000000); // Sleep min 1.3 seconds 516 | 517 | return read_string(result, dev); 518 | } 519 | 520 | else if(args[3] == "atmospheric") { 521 | if(args.size() != 4) usage(); 522 | 523 | if(write_string("Cal", dev) != 0) 524 | return 1; 525 | 526 | usleep(2000000); // Sleep min 1.3 seconds 527 | 528 | return read_string(result, dev); 529 | } 530 | 531 | else { 532 | std::cout << "No such calibration mode." << std::endl; 533 | return 1; 534 | } 535 | } 536 | 537 | int do_sleep(const std::vector& args, int dev) { 538 | if(args.size() != 3) usage(); 539 | 540 | if(write_string("SLEEP", dev) != 0) 541 | return 1; 542 | 543 | return 0; 544 | } 545 | 546 | int init_dev(const std::vector& args) { 547 | int dev = open(args[1].c_str(), O_RDWR); 548 | if(dev < 0) { 549 | perror("open"); 550 | std::cout << "Failed to open the device node; exiting." << std::endl; 551 | return -1; 552 | } 553 | 554 | if(ioctl(dev, I2C_SLAVE, 0x61) < 0) { 555 | perror("ioctl"); 556 | std::cout << "Unable to set I2C slave address." << std::endl; 557 | return -1; 558 | } 559 | 560 | return dev; 561 | } 562 | 563 | int main(int argc, char **argv) { 564 | if(argc < 3) usage(); 565 | std::vector args(argv, argv+argc); 566 | 567 | int dev = init_dev(args); 568 | if(dev < 0) return 1; 569 | 570 | if(args[2] == "read_do") return do_read_dissoxy(args, dev, NULL); 571 | else if(args[2] == "read_saturation") return do_read_saturation(args, dev, NULL); 572 | else if(args[2] == "read_avgdo") return do_read_avgdo(args, dev); 573 | else if(args[2] == "read_avgsat") return do_read_avgsat(args, dev); 574 | else if(args[2] == "info") return do_info(args, dev); 575 | else if(args[2] == "status") return do_status(args, dev); 576 | else if(args[2] == "temp") return do_temp(args, dev); 577 | else if(args[2] == "EC") return do_EC(args, dev); 578 | else if(args[2] == "pressure") return do_pressure(args, dev); 579 | else if(args[2] == "led") return do_led(args, dev); 580 | else if(args[2] == "cal") return do_cal(args, dev); 581 | else if(args[2] == "sleep") return do_sleep(args, dev); 582 | else usage(); 583 | } 584 | --------------------------------------------------------------------------------