├── .gitignore ├── Makefile ├── README └── kmeter.c /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | *.ko 3 | *.mod.c 4 | modules.order 5 | Module.symvers 6 | 7 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | obj-m+=kmeter.o 2 | WARN := -W -Wall -Wextra 3 | 4 | all: 5 | make -C /lib/modules/$(shell uname -r)/build/ M=$(PWD) modules 6 | clean: 7 | make -C /lib/modules/$(shell uname -r)/build/ M=$(PWD) clean 8 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | The kMeter Loadable Kernel Module for montioring Sensus Protocol Absolute Encoder Water Meters 2 | ---------------------------------------------------------------------------------------------- 3 | by: Rich Zimmerman 4 | 5 | This Loadable Kernel Module is for reading a sensus compatible water meter via GPIO. 6 | In order to work, the meter must be a typical 3 wire absolute encoder model. 7 | 8 | Water Meter Pinout 9 | ------------------ 10 | most Sensus compatible meters (including the elster/AMCO meter) there are 3 wires (or three labeled 11 | terminals): 12 | RED: This is the clock signal and must be wired to an OUTPUT on the controller module. 13 | Typically these require at least 5V to work, so if you're running 3.3V outputs (e.g. Raspberry Pi), 14 | you'll need an opto-isolator or transistor to bump it up to 5V. 15 | GREEN: This is the data signal and must be wired to an INPUT on the controller module. The meter will 16 | pull this low to signify a bit and it'll float otherwise, so you'll have to pull this high to 17 | work properly. 18 | BLACK: Tie this to ground. 19 | 20 | NOTE: There are no guarantees that these wire colors correspond to your meters. The meter's I've tested 21 | have all been fairly robust about putting the wrong wires to the wrong terminals, but be careful! 22 | 23 | Sensus Protocol 24 | --------------- 25 | The sensus protocol sends data as 7 bits, 1 start, 1 stop, 1 parity bit. Baud rate is controlled by 26 | toggling the clock pin. The module is currently configured to a 1000us clock cycle. That seems to be 27 | about as fast as I can get and reliably read the couple different manufacturer's meters I have. YMMV. 28 | This is controlled by the TICK_TIME variable in the code (tick time is 1/2 the full cycle time, so 500us 29 | is the default) 30 | 31 | The data is ASCII encoded in the following format: 32 | V;RBxxxxxxx;IByyyyy;Kmmmmm 33 | where xxxx is the meter read value (arbitrary digits, but not more than 12) 34 | yyyy is the meter id (arbitrary digits) 35 | mmmm is another meter id (arbitrary digits) 36 | Note that the IB and K parts are optional 37 | The line is terminated with a carriage return and then repeated. 38 | 39 | Water Read Precision 40 | -------------------- 41 | The precision of the water read value is typically something that can be programmed into the meter itself. 42 | Most meters come factory programmed to only give values in terms of 10s or even 100s of gallons or liters. 43 | Therefore, the meter read value will only give you the most significant digits of the meter dials. To give 44 | full precision (even down to the tenth of a gallon if so equipped) you need specialized hardware. For 45 | example, for Sensus branded meters you need the FieldLogic Hub software package and the TouchRead autogun. 46 | I would LOVE to be able to reprogram Sensus compatible meters to give full resolution, but I don't have any documentation as to how to do that. If anyone out there does please send it to me! 47 | 48 | Building 49 | -------- 50 | To build, you'll need the kernel headers for your current kernel. 51 | e.g. on Raspberry-pi it's easy. Just run 52 | apt-get install raspberrypi-kernel-headers 53 | 54 | to build, cd to the directory and make 55 | 56 | to install the module use the insmod command. The module won't do anything without some parameters 57 | passed to it. To get info on the parameters, type: 58 | modinfo kmeter.ko 59 | 60 | So, for example: 61 | sudo insmod kmeter.ko debug=0 dataPin=27,22 clockPin=17,17 62 | This will fire off the module and read data from two separate meters that are sharing a clock pin. 63 | debug is set to 0 so there won't be a lot of chatter. 64 | If you want to debug, set debug to true and monitor the kernel log file (e.g. tail -f /var/log/kern.log) 65 | 66 | NOTE: PIN NUMBERS ON MODERN KERNELS 67 | ----------------------------------- 68 | In the old days, the insmod statement above worked fine on raspberrypi, but modern kernels now 69 | have mapped the GPIO values into new number spaces. Execute this to get the new gpio number: 70 | cat /sys/kernel/debug/gpio 71 | It will probably be in the 500-800 space. 72 | 73 | Reading Data 74 | ------------ 75 | When this module runs, it creates a sysfs at /sys/kMeter. This filesystem is described below 76 | /sys/kMeter/poll_interval (the rate at which we poll the meter in seconds) 77 | The next section has per meter variables. Replace the # with a meter number 78 | /sys/kMeter/#/clockPin - the gpio pin assigned to the clock. (typically the red wire. 5V logic only) 79 | /sys/kMeter/#/dataPin - the gpio pin assigned to the data input.(typically the green wire (must pull high)) 80 | /sys/kMeter/#/value - the meter read value 81 | /sys/kMeter/#/number - the meter number (if supplied. If not this will return a -1) 82 | /sys/kMeter/#/k_number - the meter k_number (if supplied. if not this will return a -1) 83 | /sys/kMeter/#/lastTime - the last unix time that the meter was successfully queried. 84 | 85 | 86 | Note on sharing clock pins 87 | -------------------------- 88 | This is somewhat experimental. Generally when the clock pin is set low for a second or so, the meter 89 | will reset its send buffer. The module takes care of this. If this didn't happen, the two meters 90 | would inevitably get out of sync in sending data. Generally it's a good idea to NOT share clock 91 | pins if you can avoid it. That said, I run it in a sprinkler app with a shared pin and have so 92 | for months without issue. No guarantees here though. 93 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /kmeter.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include // Required for the GPIO functions 5 | #include // Required for the IRQ code 6 | #include // Using kobjects for the sysfs bindings 7 | #include 8 | #include // sleep functions 9 | #include 10 | #include 11 | #include 12 | #if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 11, 0)) 13 | #include 14 | #endif 15 | 16 | MODULE_LICENSE("GPL"); 17 | MODULE_AUTHOR("Richard Zimmerman"); 18 | MODULE_DESCRIPTION("a Sensus meter reading Kernel Module"); 19 | MODULE_VERSION("0.1"); 20 | 21 | static int dataPin[4]={-1,-1,-1,-1}; 22 | static int dataPin_argc = 0; 23 | module_param_array(dataPin, int, &dataPin_argc, S_IRUGO); 24 | MODULE_PARM_DESC(dataPin, "The Data pin for each meter"); 25 | 26 | static int clockPin[4]={-1,-1,-1,-1}; 27 | static int clockPin_argc = 0; 28 | module_param_array(clockPin, int, &clockPin_argc, S_IRUGO); 29 | MODULE_PARM_DESC(clockPin, "The Clock pin for each meter (you can share!)"); 30 | 31 | static int debug=0; 32 | module_param(debug, int, S_IRUGO); 33 | MODULE_PARM_DESC(debug, "set nonzero to debug output data"); 34 | 35 | static int poll_interval=30; 36 | module_param(poll_interval, int, S_IRUGO); 37 | MODULE_PARM_DESC(poll_interval, "Meter Polling Interval in Seconds"); 38 | 39 | // Size of the received data array 40 | #define DATA_ARRAY_SIZE 100 41 | // Clock period is twice the TICK_TIME (in uS) 42 | #define TICK_TIME 500 43 | // Time we wait for the input to settle (in uS) 44 | #define SETTLE_TIME 70 45 | 46 | // main structure that defines a particular water meter 47 | static struct meterspec 48 | { 49 | // readable data values 50 | s32 value; 51 | s32 number; 52 | s32 k_number; 53 | struct timespec64 last_read_time; 54 | // config values 55 | int dataPin; 56 | int clockPin; 57 | 58 | // internals 59 | struct kobject* kobj; // location for storing sysfs data 60 | char data[DATA_ARRAY_SIZE]; // Received data buffer 61 | int data_index; // index into received data buffer 62 | int bitno; // bit position we're currently reading 63 | bool parity_check; // parity check bit 64 | bool isDone; // true if we're all done reading this meter 65 | enum STATE {WAIT_FOR_START, READ_BITS, WAIT_FOR_PARITY, WAIT_FOR_STOP} state; 66 | } mspec[4]; 67 | 68 | DEFINE_MUTEX(mspec_mutex); // for managing access to the shared parts of mspec 69 | 70 | static ssize_t value_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) 71 | { 72 | int index; 73 | ssize_t retval = 0; 74 | sscanf(kobj->name, "%d", &index); 75 | if ((index < 0) || (index >= dataPin_argc)) 76 | return 0; 77 | mutex_lock(&mspec_mutex); 78 | retval = scnprintf(buf, PAGE_SIZE, "%d", mspec[index].value); 79 | mutex_unlock(&mspec_mutex); 80 | return retval; 81 | } 82 | 83 | static ssize_t number_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) 84 | { 85 | int index; 86 | ssize_t retval = 0; 87 | sscanf(kobj->name, "%d", &index); 88 | if ((index < 0) || (index >= dataPin_argc)) 89 | return 0; 90 | mutex_lock(&mspec_mutex); 91 | retval = scnprintf(buf, PAGE_SIZE, "%d", mspec[index].number); 92 | mutex_unlock(&mspec_mutex); 93 | return retval; 94 | } 95 | 96 | static ssize_t k_number_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) 97 | { 98 | int index; 99 | ssize_t retval = 0; 100 | sscanf(kobj->name, "%d", &index); 101 | if ((index < 0) || (index >= dataPin_argc)) 102 | return 0; 103 | mutex_lock(&mspec_mutex); 104 | retval = scnprintf(buf, PAGE_SIZE, "%d", mspec[index].k_number); 105 | mutex_unlock(&mspec_mutex); 106 | return retval; 107 | } 108 | 109 | static ssize_t dataPin_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) 110 | { 111 | int index; 112 | ssize_t retval = 0; 113 | sscanf(kobj->name, "%d", &index); 114 | if ((index < 0) || (index >= dataPin_argc)) 115 | return 0; 116 | mutex_lock(&mspec_mutex); 117 | retval = scnprintf(buf, PAGE_SIZE, "%d", mspec[index].dataPin); 118 | mutex_unlock(&mspec_mutex); 119 | return retval; 120 | } 121 | 122 | static ssize_t clockPin_show(struct kobject* kobj, struct kobj_attribute* attr, char* buf) 123 | { 124 | int index; 125 | ssize_t retval = 0; 126 | sscanf(kobj->name, "%d", &index); 127 | if ((index < 0) || (index >= dataPin_argc)) 128 | return 0; 129 | mutex_lock(&mspec_mutex); 130 | retval = scnprintf(buf, PAGE_SIZE, "%d", mspec[index].clockPin); 131 | mutex_unlock(&mspec_mutex); 132 | return retval; 133 | } 134 | 135 | static ssize_t lastTime_show(struct kobject* kobj, struct kobj_attribute* attr, char* buf) 136 | { 137 | int index; 138 | ssize_t retval = 0; 139 | sscanf(kobj->name, "%d", &index); 140 | if ((index < 0) || (index >= dataPin_argc)) 141 | return 0; 142 | mutex_lock(&mspec_mutex); 143 | retval = scnprintf(buf, PAGE_SIZE, "%lld", (long long)mspec[index].last_read_time.tv_sec); 144 | mutex_unlock(&mspec_mutex); 145 | return retval; 146 | } 147 | 148 | static ssize_t poll_interval_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) 149 | { 150 | return scnprintf(buf, PAGE_SIZE, "%d", poll_interval); 151 | } 152 | 153 | static ssize_t poll_interval_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) 154 | { 155 | sscanf(buf, "%d", &(poll_interval)); 156 | return count; 157 | } 158 | 159 | static struct kobj_attribute value_attr = __ATTR_RO(value); 160 | static struct kobj_attribute number_attr = __ATTR_RO(number); 161 | static struct kobj_attribute k_number_attr = __ATTR_RO(k_number); 162 | static struct kobj_attribute time_attr = __ATTR_RO(lastTime); 163 | static struct kobj_attribute clockPin_attr = __ATTR_RO(clockPin); 164 | static struct kobj_attribute dataPin_attr = __ATTR_RO(dataPin); 165 | static struct kobj_attribute poll_interval_attr = __ATTR(poll_interval, 0664, poll_interval_show, poll_interval_store); 166 | 167 | 168 | static struct attribute *sysfs_attrs[] = { 169 | &value_attr.attr, 170 | &number_attr.attr, 171 | &time_attr.attr, 172 | &k_number_attr.attr, 173 | &clockPin_attr.attr, 174 | &dataPin_attr.attr, 175 | NULL, 176 | }; 177 | 178 | static struct attribute_group sysfs_group = { 179 | .attrs = sysfs_attrs, 180 | }; 181 | 182 | static struct kobject *sysfs_kobj; 183 | 184 | void reset_meter_buffer(int mtr_num) 185 | { 186 | mspec[mtr_num].data_index = 0; 187 | mspec[mtr_num].bitno = 0; 188 | mspec[mtr_num].parity_check = false; 189 | mspec[mtr_num].isDone = false; 190 | mspec[mtr_num].state = WAIT_FOR_START; 191 | } 192 | 193 | void reset_all_meters(void) 194 | { 195 | int i; 196 | for (i=0; i= 4)) 219 | return; 220 | if (mspec[mtr_num].kobj) 221 | kobject_put(mspec[mtr_num].kobj); 222 | gpio_free(mspec[mtr_num].dataPin); 223 | gpio_free(mspec[mtr_num].clockPin); 224 | } 225 | 226 | static struct task_struct *task; // The pointer to the thread task 227 | 228 | static int mainTask(void* data); 229 | 230 | static int __init kMeter_init(void){ 231 | int result = 0; 232 | int i; 233 | char counterNum[4]; 234 | 235 | printk(KERN_INFO "kMeter: Initializing the kMeter LKM\n"); 236 | 237 | // create the kobject sysfs entry at /sys/ebb 238 | sysfs_kobj = kobject_create_and_add("kMeter", kernel_kobj->parent); // kernel_kobj points to /sys/kernel 239 | if(!sysfs_kobj) 240 | { 241 | printk(KERN_ALERT "kMeter: failed to create kobject mapping\n"); 242 | return -ENOMEM; 243 | } 244 | result = sysfs_create_file(sysfs_kobj, &poll_interval_attr.attr); 245 | if (result) 246 | { 247 | printk(KERN_ALERT "kMeter: failed to add kobject attribute\n"); 248 | return result; 249 | } 250 | 251 | for (i=0; i= 1000) 256 | || (!gpio_is_valid(clockPin[i])) || (clockPin[i] < 100) || (clockPin[i] >= 1000)) 257 | { 258 | printk(KERN_ALERT "kMeter: Illegal Pin Config Meter %d\n", i); 259 | for(i--; i>=0;i--) 260 | free_meter(i); 261 | kobject_put(sysfs_kobj); // remove sysfs entry 262 | return -EINVAL; 263 | } 264 | // TODO: Check for duplication in assignment of pins (e.g. accidentally using the same pin numer 265 | // for both data and clock. 266 | 267 | init_meter(i, dataPin[i], clockPin[i]); 268 | 269 | sprintf(counterNum, "%d", i); 270 | mspec[i].kobj = kobject_create_and_add(counterNum, sysfs_kobj); 271 | if (!mspec[i].kobj) 272 | { 273 | printk(KERN_ALERT "kMeter: failed to create kobject mapping\n"); 274 | for(i--; i>=0;i--) 275 | free_meter(i); 276 | kobject_put(sysfs_kobj); // clean up -- remove the kobject sysfs entry 277 | return -EINVAL; 278 | } 279 | 280 | // add the attributes to the counter 281 | result = sysfs_create_group(mspec[i].kobj, &sysfs_group); 282 | if(result) { 283 | printk(KERN_ALERT "kMeter: failed to create sysfs group\n"); 284 | for(; i>=0;i--) 285 | free_meter(i); 286 | kobject_put(sysfs_kobj); // clean up -- remove the kobject sysfs entry 287 | return result; 288 | } 289 | 290 | result = gpio_request(mspec[i].dataPin, "dataPin"); // Set up the dataPin 291 | if (result) { 292 | printk(KERN_ERR "Failed to request GPIO %d\n", mspec[i].dataPin); 293 | return result; 294 | } 295 | gpio_direction_input(mspec[i].dataPin); // Set the dataPin to be an input 296 | 297 | result = gpio_request(mspec[i].clockPin, "clockPin"); // Set up the clockPin 298 | if (result) { 299 | printk(KERN_ERR "Failed to request GPIO %d\n", mspec[i].clockPin); 300 | return result; 301 | } 302 | gpio_direction_output(mspec[i].clockPin, 0); // Set the clockPin to be an output 303 | } 304 | 305 | task = kthread_run(mainTask, NULL, "kMeter_thread"); 306 | return result; 307 | } 308 | 309 | /** @brief The LKM cleanup function 310 | * Similar to the initialization function, it is static. The __exit macro notifies that if this 311 | * code is used for a built-in driver (not a LKM) that this function is not required. 312 | */ 313 | static void __exit kMeter_exit(void){ 314 | int i; 315 | 316 | printk(KERN_INFO "kMeter: Requesting thread stop\n"); 317 | if (task) 318 | kthread_stop(task); 319 | 320 | for (i=0; idata; 338 | // temp storage for variables until we've parsed the whole string. 339 | s32 value = -1; 340 | s32 number = -1; 341 | s32 k_number = -1; 342 | s32* num_ptr = &(k_number); 343 | while (*data_ptr != 0) 344 | { 345 | // if (debug) 346 | // printk(KERN_INFO "s%d, c%02d\n", state, *data_ptr); 347 | switch (state) 348 | { 349 | case PARSE_V: 350 | if (*data_ptr != 'V') 351 | goto err_exit; 352 | state = PARSE_SEMI; 353 | break; 354 | case PARSE_SEMI: 355 | if (*data_ptr != ';') 356 | goto err_exit; 357 | state = PARSE_PRE; 358 | break; 359 | case PARSE_PRE: 360 | if ((*data_ptr == 'R') && (*(data_ptr + 1) == 'B')) 361 | { 362 | num_ptr = &value; 363 | data_ptr ++; 364 | } 365 | else if ((*data_ptr == 'I') && (*(data_ptr + 1) == 'B')) 366 | { 367 | num_ptr = &number; 368 | data_ptr++; 369 | } 370 | else if ((*data_ptr == 'K')) 371 | num_ptr = &k_number; 372 | *num_ptr = 0; 373 | state = PARSE_NUM; 374 | break; 375 | case PARSE_NUM: 376 | { 377 | if (((*data_ptr) >= 48) && ((*data_ptr) <= 57)) 378 | { 379 | *num_ptr = (*num_ptr) * 10 + (*data_ptr) - 48; 380 | break; 381 | } 382 | data_ptr--; 383 | state = PARSE_SEMI; 384 | } 385 | } 386 | data_ptr++; 387 | } 388 | mutex_lock(&mspec_mutex); 389 | p->value = value; 390 | p->number = number; 391 | p->k_number = k_number; 392 | ktime_get_real_ts64(&ts); 393 | p->last_read_time = ts; 394 | mutex_unlock(&mspec_mutex); 395 | if (debug) 396 | printk(KERN_INFO "kMeter:%d,%d,%d\n", p->value, p->number, p->k_number); 397 | return; 398 | err_exit: 399 | if (debug) 400 | printk(KERN_INFO "Parse Error\n"); 401 | return; 402 | } 403 | 404 | // Sets all the clock pins to the value passed in 405 | static void setClockPin(bool value) 406 | { 407 | int i; 408 | for (i=0; i=0) && (!(mspec[i].isDone))) 410 | gpio_set_value(mspec[i].clockPin, value); 411 | } 412 | 413 | static void update_state(int mtr_num) 414 | { 415 | bool input = gpio_get_value(mspec[mtr_num].dataPin); 416 | struct meterspec* p = &(mspec[mtr_num]); 417 | //if (debug) 418 | // printk(KERN_INFO "kMeter: g %d s %d i %d\n", input, p->state, p->data_index); 419 | 420 | switch (p->state) 421 | { 422 | case WAIT_FOR_START: // LOST_SYNC 423 | if (!input) // wait unto we get a 0 424 | { 425 | p->data[p->data_index] = 0; 426 | p->bitno = 0; 427 | p->parity_check = false; 428 | p->state = READ_BITS; 429 | } 430 | break; 431 | case READ_BITS: 432 | if (input) 433 | { 434 | p->data[p->data_index] |= 1 << p->bitno; 435 | p->parity_check = !(p->parity_check); 436 | } 437 | if ((++(p->bitno)) == 7) 438 | p->state = WAIT_FOR_PARITY; 439 | break; 440 | case WAIT_FOR_PARITY: 441 | if ((!input) != (!(p->parity_check))) 442 | { 443 | if (debug) 444 | printk(KERN_INFO "kMeter: Meter %d Parity Error\n", mtr_num); 445 | reset_meter_buffer(mtr_num); 446 | return; 447 | } 448 | p->state = WAIT_FOR_STOP; 449 | break; 450 | case WAIT_FOR_STOP: 451 | p->state = WAIT_FOR_START; 452 | if (!input) 453 | { 454 | if (debug) 455 | printk(KERN_INFO "kMeter: Meter %d Lost Sync\n", mtr_num); 456 | reset_meter_buffer(mtr_num); 457 | return; 458 | } 459 | if (p->data[p->data_index] == 13) 460 | { 461 | p->data[p->data_index] = 0; 462 | if (debug) 463 | printk(KERN_INFO "kMeter: Meter %d, Data:%s\n", mtr_num, p->data); 464 | parse_data(p); 465 | p->isDone = true; 466 | break; 467 | } 468 | if ((++(p->data_index)) >= DATA_ARRAY_SIZE) 469 | { 470 | if (debug) 471 | printk(KERN_INFO "kMeter: Meter %d Buffer Overflow\n", mtr_num); 472 | reset_meter_buffer(0); 473 | return; 474 | } 475 | break; 476 | } 477 | } 478 | 479 | // returns true if all the meters have been read and we can safely go to sleep until the next polling interval 480 | bool mainLoop(void) 481 | { 482 | static bool tick_tock = false; 483 | int i; 484 | bool allMetersDone = true; 485 | 486 | tick_tock = !tick_tock; 487 | if (!tick_tock) 488 | { 489 | setClockPin(true); 490 | // wait for the data pins to settle 491 | usleep_range(SETTLE_TIME, SETTLE_TIME+10); 492 | 493 | // Update all the meters 494 | for (i=0; i