├── .gitignore ├── LICENSE ├── README.md ├── SerialScale ├── FilterCascade.cpp ├── FilterCascade.h └── SerialScale.ino ├── calibrator └── calibrator.ino ├── experiments └── .leaveme ├── graph ├── __pycache__ │ ├── tkplot.cpython-35.pyc │ └── tkplot.cpython-36.pyc ├── experiments │ └── dumy ├── scales.py ├── tkplot.py └── weight-graph.py ├── pics ├── alldone.jpg ├── logger.png ├── schematics.png ├── wiring1.png └── wiring2.png └── schematics.fzz /.gitignore: -------------------------------------------------------------------------------- 1 | graph/experiments/* 2 | !graph/experiments/.leaveme 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Scale hack 2 | 3 | ## Rationale 4 | Electornic scales could be bought online for several bucks, but any scales that could be connected to a computer is a lot more expensive. The idea of this project is to create a compact and cheap solution that could be used to hack any conventional consumer-grade scales to make them connected. 5 | 6 | ## Solution 7 | 8 | #### Hardware 9 | 10 | ![](pics/alldone.jpg) 11 | 12 | * [HX711](http://www.ebay.com/itm/HX711-Weighing-Sensor-Dual-Channel-24-Bit-Precision-A-D-Module-Pressure-Sensor-/161264280835) - a 24 bit delta-sigma ADC with integrated instrumentation amplifier is a chip specifically designed for digitizing strain-gauge bridges. 13 | * A goto solution for any quick and dirty hack - an Arduino clone sourced from ebay. 14 | * HC-05 Bluetooth dongle of the same origin 15 | 16 | #### Build instructions 17 | 18 | ![](pics/wiring1.png) 19 | Open the scales and trace/buzz the wires of the strain gauges. Majority of them will be connected together, leaving just 4 wires which participate in measurement process. Of those two will be Excitation + and - wires, the other pair - Signal + and -. To distinguish whitch is whitch, measure voltage from gound to all of those 4 wires. The lowest voltage point will be Excitation-, highest - Excitation+, middle point - Signal + and -, it's not exactly clear which is which instantly, you can always just exchange them if your readings go negative. 20 | 21 | ![](pics/wiring2.png) 22 | 23 | Connect Excitation wires to E+ and E- of HX711 board, connect Signal wires to A+ and A- of HX711. Hook up VCC, GND, DT and SCK pins to pins 8, 9, 10, 11 of Arduino as shown in diagram below. 24 | 25 | ![](pics/schematics.png) 26 | 27 | #### Software 28 | 29 | * Arduino library used: https://github.com/bogde/HX711 30 | * A simple serial protocol implemented - if 0 is sent, the scale is zeroed, if any non-zero number is sent, it is cansidered that equal weight object is placed on the scale and used to calibrate the upper range of the scale. 31 | 32 | ```Arduino 33 | #include "HX711.h" 34 | 35 | String content = ""; 36 | 37 | HX711 scale; 38 | 39 | #define DOUT 9 40 | #define SCK 11 41 | 42 | void setup() { 43 | pinMode(10, OUTPUT); 44 | digitalWrite(10, LOW); 45 | pinMode(8, OUTPUT); 46 | digitalWrite(8, HIGH); 47 | 48 | Serial.begin(9600); 49 | scale.begin(DOUT, SCK); 50 | 51 | scale.set_scale(19.55); 52 | scale.tare(); 53 | } 54 | 55 | void loop() { 56 | Serial.println(scale.get_units(10), 1); 57 | while(Serial.available()) { 58 | char c = Serial.read(); 59 | if('\n' == c) { 60 | unsigned int n = content.toInt(); 61 | content = String(""); 62 | if(0 == n) { 63 | scale.tare(); 64 | } else { 65 | scale.set_scale(1); 66 | double t = scale.get_units(10); 67 | double r = t/n; 68 | scale.set_scale(r); 69 | } 70 | } else { 71 | content.concat(c); 72 | } 73 | } 74 | } 75 | ``` 76 | 77 | Host-side python code allows for data logging and visualisation. Add python modules until it works. 78 | 79 | ![](pics/logger.png) 80 | 81 | ### Cost analysis 82 | 83 | Component | Cost 84 | ----------|--------- 85 | HX711 | $0.99 86 | Arduino Nano | $2.73 87 | HC-05 | $0.76 88 | **Total** | **$4.48** 89 | 90 | -------------------------------------------------------------------------------- /SerialScale/FilterCascade.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2016 Albertas Mickėnas 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | #include "FilterCascade.h" 17 | 18 | FilterCascade::FilterCascade(unsigned char _size, unsigned char heaviness[]): 19 | size(_size) 20 | { 21 | unsigned char i; 22 | if(size > MAX_CASCADE_SIZE) { 23 | size = MAX_CASCADE_SIZE; 24 | } 25 | 26 | for(i = 0; i < size; i++) { 27 | filters[i].heaviness = heaviness[i]; 28 | } 29 | } 30 | 31 | long FilterCascade::filter(struct Filter *filter, long value) { 32 | filter->filteredVal = filter->filteredVal + ((value - filter->filteredVal) >> filter->heaviness); 33 | return filter->filteredVal; 34 | } 35 | 36 | long FilterCascade::apply(long value) { 37 | long filtered = value; 38 | int i = 0; 39 | for(i = 0; i < size; i++) { 40 | filtered = filter(&filters[i], filtered); 41 | } 42 | return filtered; 43 | } 44 | 45 | -------------------------------------------------------------------------------- /SerialScale/FilterCascade.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2016 Albertas Mickėnas 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | #ifndef _FILTER_H 17 | #define _FILTER_H 18 | 19 | #define MAX_CASCADE_SIZE 5 20 | typedef struct Filter { 21 | long filteredVal; 22 | unsigned char heaviness; 23 | } Filter_t; 24 | 25 | class FilterCascade { 26 | public: 27 | FilterCascade(unsigned char size, unsigned char heaviness[]); 28 | long apply(long value); 29 | private: 30 | unsigned char size; 31 | struct Filter filters[MAX_CASCADE_SIZE]; 32 | long filter(struct Filter *filter, long value); 33 | }; 34 | 35 | #endif -------------------------------------------------------------------------------- /SerialScale/SerialScale.ino: -------------------------------------------------------------------------------- 1 | #include "HX711.h" 2 | #include "FilterCascade.h" 3 | 4 | String content = ""; 5 | 6 | HX711 scale; 7 | 8 | #define DOUT 9 9 | #define SCK 11 10 | 11 | double r = 18.15; 12 | 13 | void setup() { 14 | pinMode(10, OUTPUT); 15 | digitalWrite(10, LOW); 16 | pinMode(8, OUTPUT); 17 | digitalWrite(8, HIGH); 18 | 19 | Serial.begin(9600); 20 | scale.begin(DOUT, SCK); 21 | 22 | //scale.set_scale(); 23 | scale.tare(); 24 | } 25 | 26 | unsigned char heaviness[] = {2,2}; 27 | FilterCascade filtered = FilterCascade(2, heaviness); 28 | 29 | void loop() { 30 | 31 | Serial.println(filtered.apply(scale.get_units())/r); 32 | while(Serial.available()) { 33 | char c = Serial.read(); 34 | if('\n' == c) { 35 | unsigned int n = content.toInt(); 36 | content = String(""); 37 | if(0 == n) { 38 | scale.tare(); 39 | } else { 40 | //scale.set_scale(1); 41 | double t = scale.get_units(10); 42 | r = t/n; 43 | //scale.set_scale(r); 44 | } 45 | } else { 46 | content.concat(c); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /calibrator/calibrator.ino: -------------------------------------------------------------------------------- 1 | #include "HX711.h" 2 | 3 | HX711 scale; 4 | 5 | #define DOUT 9 6 | #define SCK 11 7 | 8 | void setup() { 9 | pinMode(10, OUTPUT); 10 | digitalWrite(10, LOW); 11 | pinMode(8, OUTPUT); 12 | digitalWrite(8, HIGH); 13 | 14 | 15 | 16 | Serial.begin(115200); 17 | scale.begin(DOUT, SCK); 18 | Serial.println("Hello."); 19 | delay(1000); 20 | Serial.print("Averaging 100 readings: "); 21 | Serial.println(scale.get_units(100)); 22 | Serial.println("Entering it as offset, put 5kg object on scale, you have 5 seconds."); 23 | scale.tare(); 24 | delay(5000); 25 | Serial.print("Averaging 100 readings: "); 26 | double w = scale.get_units(100); 27 | Serial.println(w); 28 | Serial.print("Scaling factor is:"); 29 | float s = w/5000; 30 | Serial.println(s); 31 | scale.set_scale(s); 32 | Serial.println("Done calibrating, weiht in grams follows"); 33 | } 34 | 35 | void loop() { 36 | Serial.println(scale.get_units(10)); 37 | } 38 | -------------------------------------------------------------------------------- /experiments/.leaveme: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Miceuz/ScaleHack/3bfc589283354ba0cac3c67b848b115db43bc970/experiments/.leaveme -------------------------------------------------------------------------------- /graph/__pycache__/tkplot.cpython-35.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Miceuz/ScaleHack/3bfc589283354ba0cac3c67b848b115db43bc970/graph/__pycache__/tkplot.cpython-35.pyc -------------------------------------------------------------------------------- /graph/__pycache__/tkplot.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Miceuz/ScaleHack/3bfc589283354ba0cac3c67b848b115db43bc970/graph/__pycache__/tkplot.cpython-36.pyc -------------------------------------------------------------------------------- /graph/experiments/dumy: -------------------------------------------------------------------------------- 1 | This is directoy for data storage. Normaly it is gitignored 2 | -------------------------------------------------------------------------------- /graph/scales.py: -------------------------------------------------------------------------------- 1 | import serial 2 | import matplotlib.pyplot as plt 3 | import numpy as np 4 | import sys, select 5 | 6 | ser = serial.Serial('/dev/rfcomm0', 9600) 7 | 8 | x = np.arange(100) 9 | y = np.ones(100)*0 10 | 11 | plt.ion() 12 | fig = plt.figure() 13 | ax = fig.add_subplot(111) 14 | h1, = ax.plot(x, y, 'r-') 15 | 16 | 17 | def updateLine(h1, newData): 18 | global y 19 | print(newData) 20 | # h1.set_xdata(numpy.append(h1.get_xdata(), newData)) 21 | y = np.append(y, newData) 22 | y = y[1:] 23 | h1.set_ydata(y) 24 | #ax.relim() 25 | ax.set_ylim(np.amin(y), np.amax(y)) 26 | 27 | #~ ax.set_ylim(-10, 10000) 28 | # ax.set_ylim(y.mean() - np.std(y) * 5, y.mean() + np.std(y) * 5) 29 | # ax.set_ylim(y.mean() - y.mean()/2, y.mean() + y.mean()/2) 30 | ax.autoscale_view() 31 | fig.canvas.draw() 32 | fig.canvas.flush_events() 33 | plt.draw() 34 | 35 | def GetChar(Block=True): 36 | if Block or select.select([sys.stdin], [], [], 0) == ([sys.stdin], [], []): 37 | return sys.stdin.read(1) 38 | #~ raise error('NoChar') 39 | 40 | while(True): 41 | val = ser.readline() 42 | ival = np.float(val.decode("utf-8").strip()) 43 | #~ print(ival) 44 | updateLine(h1, ival) 45 | if "n" == GetChar(False): 46 | break 47 | 48 | #~ print(type(y[0])) 49 | #~ print(np.amax(y)) 50 | 51 | ser.close() 52 | -------------------------------------------------------------------------------- /graph/tkplot.py: -------------------------------------------------------------------------------- 1 | import matplotlib 2 | matplotlib.use('TkAgg') 3 | 4 | from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2TkAgg 5 | from matplotlib.figure import Figure 6 | import tkinter as tk 7 | 8 | 9 | try: 10 | from matplotlib.pyplot import style 11 | style.use('ggplot') 12 | except ImportError: 13 | pass 14 | 15 | 16 | class TkPlot(tk.Frame): 17 | def __init__(self, master, figsize=(9, 6)): 18 | tk.Frame.__init__(self, master) 19 | self.figure = Figure(figsize=figsize) 20 | self.canvas = FigureCanvasTkAgg(self.figure, master=self) 21 | self.canvas.show() 22 | self.canvas.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=1) 23 | self.toolbar = NavigationToolbar2TkAgg(self.canvas, self) 24 | self.toolbar.update() 25 | -------------------------------------------------------------------------------- /graph/weight-graph.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import sys 4 | import tkinter as tk 5 | import tkplot 6 | import threading 7 | from queue import Queue, Empty 8 | import serial 9 | import struct 10 | from collections import namedtuple 11 | import time 12 | import csv 13 | import math 14 | 15 | def execute_delayed(root, generator): 16 | """For each yielded value wait the given amount of time (in seconds) 17 | without pausing the Tkinter main loop. 18 | 19 | See 'slowmotion' in http://effbot.org/zone/tkinter-generator-polyline.htm 20 | """ 21 | try: 22 | root.after(int(next(generator) * 100), execute_delayed, root, generator) 23 | except StopIteration: 24 | pass 25 | 26 | 27 | class Status(namedtuple('Status', ['local_time', 'weight_reading'])): 28 | @property 29 | def temp(self): 30 | return self.weight_reading 31 | 32 | 33 | START_TIME = time.time() 34 | 35 | def local_time(): 36 | return time.time() - START_TIME 37 | 38 | 39 | class Arduino: 40 | def __init__(self, port, filename): 41 | self.filename = filename 42 | self.port = port 43 | self.status = Queue() 44 | self.command = Queue() 45 | self.thread = threading.Thread(target=self.interact, daemon=True) 46 | self.started = threading.Event() 47 | self.last_status = None 48 | self._power = None 49 | 50 | def line_status(self, line): 51 | weight_reading = float(line.strip()) 52 | return Status(local_time(), weight_reading) 53 | 54 | def interact(self): 55 | with open(self.filename, 'wb') as f: 56 | self.serial = serial.Serial(self.port, 9600) 57 | try: 58 | self.started.set() 59 | while True: 60 | try: 61 | while True: 62 | None 63 | self.serial.write(self.command.get_nowait()) 64 | except Empty: 65 | pass 66 | 67 | line = self.serial.readline() 68 | try: 69 | status = self.line_status(line) 70 | except ValueError: 71 | continue 72 | f.write(line) 73 | f.flush() 74 | self.status.put_nowait(status) 75 | self.last_status = status 76 | finally: 77 | self.started.clear() 78 | 79 | def iter_status(self): 80 | assert(self.started.is_set()) 81 | try: 82 | while True: 83 | status = self.status.get_nowait() 84 | yield status 85 | except Empty: 86 | pass 87 | 88 | def __str__(self): 89 | return "<{} {}>".format(self.__class__.__name__, self.last_status if self.started.is_set() else '(stopped)') 90 | 91 | 92 | @property 93 | def calibrationWeight(self): 94 | assert(self.started.is_set()) 95 | return self._power 96 | 97 | @calibrationWeight.setter 98 | def calibrationWeight(self, calibrationWeight): 99 | assert(self.started.is_set()) 100 | assert(0 <= calibrationWeight <= 2**24) 101 | command = struct.pack('4sc', str.encode(str(int(calibrationWeight))), b'\n') 102 | self.command.put(command) 103 | 104 | def start(self): 105 | self.thread.start() 106 | self.started.wait() 107 | 108 | 109 | class HeatPlot(tkplot.TkPlot): 110 | def __init__(self, root): 111 | tkplot.TkPlot.__init__(self, root, (9, 6)) 112 | 113 | self.plot = self.figure.add_subplot(111) 114 | self.plot.set_xlabel("Time (s)") 115 | self.plot.set_ylabel("Weight (g)") 116 | self.plot.set_xlim(0, 1) 117 | self.plot.set_ylim(0, 110) 118 | self.weight_reading_line, = self.plot.plot([], [], label="Weight, g", linewidth=3.0) 119 | self.plot.legend(handles=[self.weight_reading_line], bbox_to_anchor=(0.3, 1)) 120 | self.figure.tight_layout() 121 | 122 | def update(self, status): 123 | time = [s.local_time for s in status] 124 | weight_reading = [s.weight_reading for s in status] 125 | time = time[-200:] 126 | weight_reading = weight_reading[-200:] 127 | if time: 128 | self.plot.set_xlim(min(time), max(time)) 129 | self.plot.set_ylim(0, max(110, round(max(weight_reading) / 50.0 + 0.5) * 50 + 10)) 130 | self.weight_reading_line.set_xdata(time) 131 | self.weight_reading_line.set_ydata(weight_reading) 132 | self.figure.canvas.draw() 133 | 134 | 135 | class Krosnis: 136 | def __init__(self, root, port, experiment): 137 | self.root = root 138 | self.root.title("Scales - {}".format(experiment)) 139 | self.experiment = experiment 140 | 141 | self.update_period = 1.0 142 | 143 | self.plot = HeatPlot(self.root) 144 | self.plot.pack(fill=tk.BOTH, expand=1) 145 | 146 | self.toolbar = tk.Frame(self.root) 147 | self.toolbar.pack(fill=tk.X) 148 | self.label = tk.Label(self.toolbar) 149 | self.label.pack(side=tk.RIGHT, fill=tk.BOTH, expand=1) 150 | 151 | self.power_val = tk.StringVar() 152 | self.power_val.set('0') 153 | self.power = tk.Entry(self.toolbar, textvariable=self.power_val) 154 | self.power.bind('', self.set_calibrationWeight) 155 | self.power.pack(side=tk.LEFT) 156 | self.power.focus_set() 157 | self.set_power = tk.Button(self.toolbar, text='Set weight', command=self.set_calibrationWeight) 158 | self.set_power.pack(side=tk.LEFT) 159 | 160 | # self.setpoint_val = tk.StringVar() 161 | # self.setpoint_val.set('0.0') 162 | # self.setpoint = tk.Entry(self.toolbar, textvariable=self.setpoint_val) 163 | # self.setpoint.bind('', self.set_setpoint) 164 | # self.setpoint.pack(side=tk.LEFT) 165 | # self.setpoint.focus_set() 166 | # self.set_setpoint = tk.Button(self.toolbar, text='Set temperature', command=self.set_setpoint) 167 | # self.set_setpoint.pack(side=tk.LEFT) 168 | 169 | self.arduino = Arduino(port, "experiments/{}_raw.csv".format(experiment)) 170 | self.every_status = [] 171 | self.th0 = 0 172 | 173 | def set_status(self, status): 174 | self.label.config(text=status) 175 | 176 | def set_calibrationWeight(self, event=None): 177 | self.arduino.calibrationWeight = float(self.power_val.get()) 178 | 179 | # def set_setpoint(self, event=None): 180 | # self.arduino.setpoint = float(self.setpoint_val.get()) 181 | 182 | def start(self): 183 | _self = self 184 | def shell(): 185 | self = _self 186 | threading.Thread(target=shell, daemon=True).start() 187 | execute_delayed(self.root, self.sample()) 188 | 189 | def time_deviation(self): 190 | if self.every_status: 191 | t0 = self.every_status[0].time 192 | t0_local = self.every_status[0].local_time 193 | t_sum = 0 194 | for s in self.every_status: 195 | t_sum += (s.time - t0) - (s.local_time - t0_local) 196 | return t_sum / len(self.every_status) 197 | else: 198 | return 0 199 | 200 | def control(self): 201 | pass 202 | 203 | def sample(self): 204 | self.arduino.start() 205 | with open("experiments/{}.csv".format(self.experiment), 'w') as f: 206 | csvf = csv.writer(f) 207 | csvf.writerow(Status._fields) 208 | while True: 209 | try: 210 | for s in self.arduino.iter_status(): 211 | if self.th0 is None: 212 | self.th0 = s.temp 213 | csvf.writerow(s) 214 | self.set_status(str(s)) 215 | self.every_status.append(s) 216 | f.flush() 217 | self.plot.update(self.every_status) 218 | self.control() 219 | yield self.update_period 220 | except Exception as e: 221 | print(e) 222 | 223 | 224 | def run(port, experiment): 225 | root = tk.Tk() 226 | root.geometry("1000x700") 227 | win = Krosnis(root, port, experiment) 228 | win.start() 229 | tk.mainloop() 230 | 231 | if __name__ == "__main__": 232 | run(sys.argv[1], sys.argv[2]) 233 | -------------------------------------------------------------------------------- /pics/alldone.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Miceuz/ScaleHack/3bfc589283354ba0cac3c67b848b115db43bc970/pics/alldone.jpg -------------------------------------------------------------------------------- /pics/logger.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Miceuz/ScaleHack/3bfc589283354ba0cac3c67b848b115db43bc970/pics/logger.png -------------------------------------------------------------------------------- /pics/schematics.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Miceuz/ScaleHack/3bfc589283354ba0cac3c67b848b115db43bc970/pics/schematics.png -------------------------------------------------------------------------------- /pics/wiring1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Miceuz/ScaleHack/3bfc589283354ba0cac3c67b848b115db43bc970/pics/wiring1.png -------------------------------------------------------------------------------- /pics/wiring2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Miceuz/ScaleHack/3bfc589283354ba0cac3c67b848b115db43bc970/pics/wiring2.png -------------------------------------------------------------------------------- /schematics.fzz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Miceuz/ScaleHack/3bfc589283354ba0cac3c67b848b115db43bc970/schematics.fzz --------------------------------------------------------------------------------