Add a node version of the reciever part
parent
3e9dc244e2
commit
97896772ce
|
|
@ -1,3 +1,5 @@
|
||||||
|
|
||||||
# Binary
|
# Binary
|
||||||
roost
|
roost
|
||||||
|
node/node_modules/
|
||||||
|
node/records/
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,307 @@
|
||||||
|
# DATABASE
|
||||||
|
|
||||||
|
This is the server side component for the robot hands system.
|
||||||
|
|
||||||
|
This is going to be split into a few parts:
|
||||||
|
|
||||||
|
1. The database, which stores the data
|
||||||
|
2. The receiver/responder, which receives data from the base stations and puts it into the database and responds with settings if appropriate
|
||||||
|
3. The frond-end, which reads from the database and serves the front-end (in another repo)
|
||||||
|
|
||||||
|
The idea is to make this in node quickly and then transfer it into go later.
|
||||||
|
|
||||||
|
## Database stuff
|
||||||
|
|
||||||
|
In the database there are two unifying concepts: The node and the temperature site. Data is received from nodes and can be retrieved by node id. Some nodes are assigned temperature sites, with each temperature site having at most one node, data can be retrieed by temperature site.
|
||||||
|
|
||||||
|
### Tables
|
||||||
|
|
||||||
|
#### Users table
|
||||||
|
|
||||||
|
This table is probably going to be handled by django, if not we need to figure out what else we have to add here.
|
||||||
|
|
||||||
|
- `name` the username
|
||||||
|
- `creation_time` when the entry was added, this is automatically set.
|
||||||
|
|
||||||
|
```
|
||||||
|
CREATE TABLE IF NOT EXISTS Users (
|
||||||
|
name TEXT UNIQUE NOT NULL,
|
||||||
|
creation_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Nodes table
|
||||||
|
|
||||||
|
This table lists all of the nodes and names for the nodes.
|
||||||
|
This does not give any type to the nodes, that is all done using groups.
|
||||||
|
`author` is a foreign key to the users table.
|
||||||
|
|
||||||
|
- `hardware_id` the node id, currently we use the mac address
|
||||||
|
- `friendly_name` a human readable name
|
||||||
|
- `creation_time` when the entry was added, this is automatically set.
|
||||||
|
- `author` the user that added the entry, must match an entry in the Users table
|
||||||
|
|
||||||
|
```
|
||||||
|
CREATE TABLE IF NOT EXISTS Nodes (
|
||||||
|
hardware_id VARCHAR(255) UNIQUE NOT NULL,
|
||||||
|
friendly_name TEXT NOT NULL,
|
||||||
|
creation_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
author TEXT NOT NULL,
|
||||||
|
CONSTRAINT FOREIGN KEY (author) REFERENCES Users(name)
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Groups table
|
||||||
|
|
||||||
|
This table defines different groups that exist
|
||||||
|
|
||||||
|
- `name` the group name
|
||||||
|
- `creation_time` when the entry was added, this is automatically set.
|
||||||
|
- `author` the user that added the entry, must match an entry in the Users table
|
||||||
|
|
||||||
|
```
|
||||||
|
CREATE TABLE IF NOT EXISTS Groups (
|
||||||
|
name TEXT UNIQUE NOT NULL,
|
||||||
|
author TEXT NOT NULL,
|
||||||
|
creation_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
CONSTRAINT FOREIGN KEY (author) REFERENCES Users(name)
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Node2Group table
|
||||||
|
|
||||||
|
This table associates each node with its groups
|
||||||
|
|
||||||
|
- `node` is the hardware id of the node, it must match an entry in the `Nodes` table.
|
||||||
|
- `group` is the group name, it must match an entry is the `Groups` table.
|
||||||
|
- `author` the user that added the entry, must match an entry in the Users table
|
||||||
|
- `creation_time` when the entry was added, this is automatically set.
|
||||||
|
|
||||||
|
```
|
||||||
|
CREATE TABLE IF NOT EXISTS Node2Group (
|
||||||
|
node VARCHAR(255) NOT NULL,
|
||||||
|
node_group TEXT NOT NULL,
|
||||||
|
author TEXT NOT NULL,
|
||||||
|
creation_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
CONSTRAINT unique_pairing UNIQUE(node, node_group),
|
||||||
|
CONSTRAINT FOREIGN KEY (author) REFERENCES Users(name),
|
||||||
|
CONSTRAINT FOREIGN KEY (node) REFERENCES Nodes(hardware_id),
|
||||||
|
CONSTRAINT FOREIGN KEY (node_group) REFERENCES Groups(name)
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
#### GroupType table
|
||||||
|
|
||||||
|
This defines the different group types
|
||||||
|
|
||||||
|
- `name` the group type name
|
||||||
|
- `author` the user that added the entry, must match an entry in the Users table
|
||||||
|
- `creation_time` when the entry was added, this is automatically set.
|
||||||
|
|
||||||
|
```
|
||||||
|
CREATE TABLE IF NOT EXISTS GroupType (
|
||||||
|
name VARCHAR(255) UNIQUE NOT NULL,
|
||||||
|
author TEXT NOT NULL,
|
||||||
|
creation_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
CONSTRAINT FOREIGN KEY (author) REFERENCES Users(name)
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
#### GroupParent table
|
||||||
|
|
||||||
|
This gives groups a hierarachy.
|
||||||
|
|
||||||
|
Make sure that we enforce that `parent != child`
|
||||||
|
|
||||||
|
- `child` the child group, must match an entry in the `Groups` table
|
||||||
|
- `parent` the parent group, must match an entry in the `Groups' table
|
||||||
|
- `author` the user that added the entry, must match an entry in the Users table
|
||||||
|
- `creation_time` when the entry was added, this is automatically set.
|
||||||
|
|
||||||
|
```
|
||||||
|
CREATE TABLE IF NOT EXISTS GroupParent (
|
||||||
|
child TEXT NOT NULL,
|
||||||
|
parent TEXT NOT NULL,
|
||||||
|
author TEXT NOT NULL,
|
||||||
|
creation_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
CONSTRAINT unique_relation UNIQUE(child, parent),
|
||||||
|
CONSTRAINT FOREIGN KEY (author) REFERENCES Users(name),
|
||||||
|
CONSTRAINT FOREIGN KEY (child) REFERENCES Nodes(hardware_id),
|
||||||
|
CONSTRAINT FOREIGN KEY (parent) REFERENCES Nodes(hardware_id)
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Group2Type table
|
||||||
|
|
||||||
|
This associates each group with a type
|
||||||
|
|
||||||
|
- `group` is the group name, it must match an entry is the `Groups` table.
|
||||||
|
- `type` the group type, it must match an entry in the `GroupType` table
|
||||||
|
- `author` the user that added the entry, must match an entry in the Users table
|
||||||
|
- `creation_time` when the entry was added, this is automatically set.
|
||||||
|
|
||||||
|
```
|
||||||
|
CREATE TABLE IF NOT EXISTS Group2Type (
|
||||||
|
group_name TEXT NOT NULL,
|
||||||
|
type TEXT NOT NULL,
|
||||||
|
author TEXT NOT NULL,
|
||||||
|
creation_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
CONSTRAINT unique_grouptype UNIQUE(group_name, type),
|
||||||
|
CONSTRAINT FOREIGN KEY (author) REFERENCES Users(name),
|
||||||
|
CONSTRAINT FOREIGN KEY (group_name) REFERENCES Groups(name),
|
||||||
|
CONSTRAINT FOREIGN KEY (type) REFERENCES GroupType(name)
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Measurement table
|
||||||
|
|
||||||
|
This stores all of the measurement data
|
||||||
|
|
||||||
|
- `source_node` the hardware id of the node that took the measurement, must match an entry in the `Nodes` table
|
||||||
|
- `reporting_node` the hardware id of the node that sent the data to the server, generally a basestation, must match an entry in the `Nodes` table
|
||||||
|
- `associated_group` the group associated with the node, generally a temperature site, must match an entry in the `Groups` table
|
||||||
|
- `collection_time` the time that the node took the measurement as reported by the node
|
||||||
|
- `server_received_time` the time that the server received the measurement data
|
||||||
|
- `temperature_18_inch` the temperature at the 18 inch probe in degrees C
|
||||||
|
- `temperature_36_inch` the temperature at the 36 inch probe in degrees C
|
||||||
|
- `device_temperature` the internal temperature of the controller on the node, in degrees C
|
||||||
|
- `ambient_temperature` the ambient temperature as measured by the on-board sensor (SHT40 or BME280 for now). This isn't really ambient temperature if the probe is in sunlight due to the case heating up.
|
||||||
|
- `relative_humidity` the relative humidity as measured by the on-board sensor. This is humidity inside the case, not necessarily the external humidity
|
||||||
|
- `barometric_pressure` the measured barometric pressure in hpa, measured using a BME280 at the base station
|
||||||
|
- `accelerometer_x` the measured acceleration along the x axis
|
||||||
|
- on the probe the x axis is along the pipe, the point is in the +x direction and the brain case is -x
|
||||||
|
- on the base station the x axis is left and right with +x pointing to the right and -x pointing to the left
|
||||||
|
- `accelerometer_y` the measured acceleration along the y axis
|
||||||
|
- on the probe the y axis is left and right from the board, +y is to the right, -y is to the left
|
||||||
|
- on the base station y is front to back (pitch), with +y at the back and -y at the front
|
||||||
|
- `accelerometer_z` the measured acceleration along the z axis
|
||||||
|
- on the probe the +z axis points out of the front of the board, -z out the back
|
||||||
|
- on the base station +z is toward the top, -z is down
|
||||||
|
- `battery_charge_percent` is the measured remaining battery change is %, where 100% is full charge and 0% is our defined lowest acceptable charge
|
||||||
|
- `battery_voltage` is the measured battery voltage
|
||||||
|
- `remaining_battery_capacity` is the estimated remaining battery capacity is mAh
|
||||||
|
|
||||||
|
```
|
||||||
|
CREATE TABLE IF NOT EXISTS Measurement (
|
||||||
|
source_node VARCHAR(255) NOT NULL,
|
||||||
|
reporting_node VARCHAR(255) NOT NULL,
|
||||||
|
associated_group TEXT NOT NULL,
|
||||||
|
collection_time TIMESTAMP NOT NULL,
|
||||||
|
server_received_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
temperature_18_inch FLOAT,
|
||||||
|
temperature_36_inch FLOAT,
|
||||||
|
device_temperature FLOAT NOT NULL,
|
||||||
|
ambient_temperature FLOAT NOT NULL,
|
||||||
|
relative_humidity FLOAT,
|
||||||
|
barometric_pressure FLOAT,
|
||||||
|
accelerometer_x FLOAT NOT NULL,
|
||||||
|
accelerometer_y FLOAT NOT NULL,
|
||||||
|
accelerometer_z FLOAT NOT NULL,
|
||||||
|
battery_charge_percent FLOAT NOT NULL,
|
||||||
|
battery_voltage FLOAT NOT NULL,
|
||||||
|
remaining_battery_capacity FLOAT NOT NULL,
|
||||||
|
CONSTRAINT device_measurement UNIQUE(source_node, collection_time),
|
||||||
|
CONSTRAINT FOREIGN KEY (source_node) REFERENCES Nodes(hardware_id),
|
||||||
|
CONSTRAINT FOREIGN KEY (reporting_node) REFERENCES Nodes(hardware_id),
|
||||||
|
CONSTRAINT FOREIGN KEY (associated_group) REFERENCES Groups(name)
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
#### NodeRssiRecord table
|
||||||
|
|
||||||
|
This stores RSSI data
|
||||||
|
|
||||||
|
- `node` is the hardware id of the node measuring the rssi, must match an entry in the `Nodes` table
|
||||||
|
- `neighbour` is the hardware id of the node that sent the message used to get the rssi, must match an entry in the `Nodes` table
|
||||||
|
- `rssi` is the rssi value
|
||||||
|
- `collection_time` the time that the node took the measurement as reported by the node
|
||||||
|
- `server_received_time` the time that the server received the measurement data
|
||||||
|
|
||||||
|
```
|
||||||
|
CREATE TABLE IF NOT EXISTS NodeRssiRecord (
|
||||||
|
node VARCHAR(255) NOT NULL,
|
||||||
|
neighbor VARCHAR(255) NOT NULL,
|
||||||
|
rssi INT NOT NULL,
|
||||||
|
collection_time TIMESTAMP NOT NULL,
|
||||||
|
server_received_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
CONSTRAINT unique_thing UNIQUE(node, neighbor, collection_time),
|
||||||
|
CONSTRAINT FOREIGN KEY (node) REFERENCES Nodes(hardware_id),
|
||||||
|
CONSTRAINT FOREIGN KEY (neighbor) REFERENCES Nodes(hardware_id)
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
#### NodeFileManifest table
|
||||||
|
|
||||||
|
This holds the file manifests for each node
|
||||||
|
|
||||||
|
- `node` is the hardware id of the reporting node, must match an entry in the `Nodes` table
|
||||||
|
- `collection_time` the time that the node took the measurement as reported by the node
|
||||||
|
- `server_received_time` the time that the server received the measurement data
|
||||||
|
- `program_id` the id of the file
|
||||||
|
- `program_version` the file version
|
||||||
|
|
||||||
|
```
|
||||||
|
CREATE TABLE IF NOT EXISTS NodeFileManifest (
|
||||||
|
node VARCHAR(255) NOT NULL,
|
||||||
|
collection_time TIMESTAMP NOT NULL,
|
||||||
|
server_received_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
program_id INT NOT NULL,
|
||||||
|
program_version INT NOT NULL,
|
||||||
|
CONSTRAINT unique_version UNIQUE(node, collection_time, program_id),
|
||||||
|
CONSTRAINT FOREIGN KEY (node) REFERENCES Nodes(hardware_id)
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
#### NodeReportedStatus table
|
||||||
|
|
||||||
|
This holds the reported status from nodes.
|
||||||
|
|
||||||
|
- `node` is the hardware id of the reporting node, must match an entry in the `Nodes` table
|
||||||
|
- `collection_time` the time that the node sent the data, as reported by the node
|
||||||
|
- `server_received_time` the time that the server received the data
|
||||||
|
- `measurement_interval_minutes` the time between measurements in minutes
|
||||||
|
- `wake_window_length_minutes` the time that the node stays awake (basestation only)
|
||||||
|
- `offset_from_midnight_minutes` the time offset from midnight for that measurement times are aligned to
|
||||||
|
- `sleep_duration_minutes` the amonut of time that the node is set to sleep for
|
||||||
|
- `number_saved_measurements` the number of measurements saved in the nodes local memory
|
||||||
|
- `when_time_was_last_updated` the time when the node last received a time update
|
||||||
|
|
||||||
|
```
|
||||||
|
CREATE TABLE IF NOT EXISTS NodeReportedStatus (
|
||||||
|
node VARCHAR(255) NOT NULL,
|
||||||
|
collection_time TIMESTAMP NOT NULL,
|
||||||
|
server_received_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
measurement_interval_minutes INT NOT NULL,
|
||||||
|
wake_window_length_minutes INT NOT NULL,
|
||||||
|
offset_from_midnight_minutes INT NOT NULL,
|
||||||
|
sleep_duration_minutes INT NOT NULL,
|
||||||
|
number_saved_measurements INT NOT NULL,
|
||||||
|
when_time_was_last_updated TIMESTAMP NOT NULL,
|
||||||
|
CONSTRAINT unique_status UNIQUE(node, collection_time),
|
||||||
|
CONSTRAINT FOREIGN KEY (node) REFERENCES Nodes(hardware_id)
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
#### NodeConfiguration table
|
||||||
|
|
||||||
|
This holds the configuration data for the nodes. This sets the configuration, it doesn't report it.
|
||||||
|
|
||||||
|
- `node` is the hardware id of the node the settings are for, must match an entry in the `Nodes` table
|
||||||
|
- `author` the user that added the entry, must match an entry in the `Users` table
|
||||||
|
- `creation_time` the time when the entry was added
|
||||||
|
- `measurement_interval_minutes` the time the node should wait between measurements
|
||||||
|
- `offset_from_midnight_minutes` the offset from midnight that the measurements are aligned to
|
||||||
|
- `wake_window_length_minutes` how long the node stays awake, only relevant for basestations
|
||||||
|
|
||||||
|
```
|
||||||
|
CREATE TABLE IF NOT EXISTS NodeConfiguration (
|
||||||
|
node VARCHAR(255) NOT NULL,
|
||||||
|
author TEXT NOT NULL,
|
||||||
|
creation_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
measurement_interval_minutes INT NOT NULL,
|
||||||
|
offset_from_midnight_minutes INT NOT NULL,
|
||||||
|
wake_window_length_minutes INT NOT NULL,
|
||||||
|
CONSTRAINT FOREIGN KEY (author) REFERENCES Users(name),
|
||||||
|
CONSTRAINT FOREIGN KEY (node) REFERENCES Nodes(hardware_id)
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
@ -0,0 +1,158 @@
|
||||||
|
(function() {
|
||||||
|
// with version 1 the chunk length isn't relevant, but in the future we may want to minimize the number of messages sent so this could concatenate multiple
|
||||||
|
// short chunks into one message so we are keeping it. I don't think that is going to happen but I am keeping it anyway. It could also be error checking
|
||||||
|
// on the receiving end.
|
||||||
|
const chunk_record = {}
|
||||||
|
const max_chunk_length = 1400
|
||||||
|
|
||||||
|
// remove records for old messages so they aren't just taking up ram
|
||||||
|
function prune_chunk_record() {
|
||||||
|
Object.keys(chunk_record).forEach(function(thisMessageID) {
|
||||||
|
if(chunk_record[thisMessageID]) {
|
||||||
|
if(chunk_record[thisMessageID].ctime && (new Date() - chunk_record[thisMessageID].ctime > 30000)) {
|
||||||
|
// check to make sure that the ctime is old enough that we aren't worried about unneeded retransmissions
|
||||||
|
// 30 seconds seems long enough until we have a reason to change it
|
||||||
|
chunk_record[thisMessageID] = undefined // clear it so that the garbage collector can free the memory
|
||||||
|
} else if (chunk_record[thisMessageID].rtime && !chunk_record[thisMessageID].ctime && (new Date() - chunk_record[thisMessageID].rtime > 600000)) {
|
||||||
|
// if the first part of a message was received over 10 minutes ago and we don't have the full message we drop it.
|
||||||
|
// In the future we have to ask for a resend but we don't have that set up yet.
|
||||||
|
chunk_record[thisMessageID] = undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function receive_chunk(chunk, message_handler, rinfo) {
|
||||||
|
if(typeof message_handler !== 'function') {
|
||||||
|
message_handler = () => {}
|
||||||
|
}
|
||||||
|
console.log('received chunk: ', chunk)
|
||||||
|
const the_header = parse_chunk_header(chunk)
|
||||||
|
console.log('header: ', the_header)
|
||||||
|
if(typeof the_header.message_id !== 'undefined') {
|
||||||
|
// if this is a new message start a record for it
|
||||||
|
if(!chunk_record[the_header.message_id]) {
|
||||||
|
console.log('new message thing')
|
||||||
|
chunk_record[the_header.message_id] = {
|
||||||
|
chunks: {},
|
||||||
|
rtime: new Date(), // received time, when the first chunk came in
|
||||||
|
ctime: undefined, // completed time, when the full message was reconstructed
|
||||||
|
num_chunks: the_header.num_chunks,
|
||||||
|
r_chunks: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(chunk_record[the_header.message_id].ctime) {
|
||||||
|
// if the message has already been fully received we just drop it
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// if this is a new chunk save it in chunk_records
|
||||||
|
if(!chunk_record[the_header.message_id].chunks[the_header.chunk_index]) {
|
||||||
|
console.log('new chunk for existing message')
|
||||||
|
chunk_record[the_header.message_id].chunks[the_header.chunk_index] = chunk.slice(the_header.header_length, the_header.chunk_length+the_header.header_length)
|
||||||
|
chunk_record[the_header.message_id].r_chunks = chunk_record[the_header.message_id].r_chunks + 1
|
||||||
|
}
|
||||||
|
// if we have all the chunks reconstruct the message and pass it on for processing
|
||||||
|
if(chunk_record[the_header.message_id].r_chunks === chunk_record[the_header.message_id].num_chunks) {
|
||||||
|
console.log('have all chunks')
|
||||||
|
let reconstructed_message = Buffer.from([])
|
||||||
|
Object.keys(chunk_record[the_header.message_id].chunks).sort((a,b)=>a-b).forEach(function(thisChunkIndex) {
|
||||||
|
reconstructed_message = Buffer.concat([reconstructed_message, chunk_record[the_header.message_id].chunks[thisChunkIndex]])
|
||||||
|
})
|
||||||
|
if(reconstructed_message) {
|
||||||
|
chunk_record[the_header.message_id].ctime = new Date() // completed time
|
||||||
|
message_handler(reconstructed_message, rinfo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
prune_chunk_record()
|
||||||
|
}
|
||||||
|
if(the_header.chunk_length + the_header.header_length < chunk.length) {
|
||||||
|
// if there is more data than has been processed, cut off the bit we have already done and process the rest.
|
||||||
|
receive_chunk(chunk.slice(the_header.chunk_length + the_header.header_length), message_handler, rinfo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// we should never receive more than one chunk at a time over the cellular network, but make it so that we can handle that in the future
|
||||||
|
function parse_chunk_header(chunk) {
|
||||||
|
if (chunk.length < 9) {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
chunkBuf = Buffer.from(chunk, 'hex')
|
||||||
|
message_id = chunkBuf.readUInt16BE()
|
||||||
|
chunk_length = chunkBuf.readUInt16BE(2)
|
||||||
|
chunk_version = chunkBuf.readUInt8(4)
|
||||||
|
if (chunk_version == 0x01) {
|
||||||
|
return {
|
||||||
|
"message_id": message_id,
|
||||||
|
"chunk_length": chunk_length,
|
||||||
|
"chunk_version": chunk_version,
|
||||||
|
"chunk_index": (chunkBuf.slice(5,7)).readUInt16BE(),
|
||||||
|
"num_chunks": (chunkBuf.slice(7,9)).readUInt16BE(),
|
||||||
|
"header_length": 9
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// we don't know how to handle this chunk version, so return the chunk length so it can be skipped
|
||||||
|
return {
|
||||||
|
"chunk_length": chunk_length
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// take a message and split it into chunks suitable for sending to the base station (the MTU is 1500, so we have to chunk longer messages)
|
||||||
|
function chunk_message(message, message_id) {
|
||||||
|
const messageChunks = []
|
||||||
|
const messageLen = message.length
|
||||||
|
const numChunks = Math.floor(messageLen / max_chunk_length) // we use 1400 instead of 1500 to account for the header and because I am paranoid about sending things that are the max length
|
||||||
|
for(let i = 0; i < numChunks; i++) {
|
||||||
|
// 1409 is the chunk length here, we split the message into 1400 byte lengths and add a 9 byte header
|
||||||
|
// for now the version is always 1
|
||||||
|
const thisHeader = make_chunk_header(message_id, i+1, numChunks+1, max_chunk_length+9, 1)
|
||||||
|
//messageChunks.push(Buffer.concat([thisHeader, Buffer.from(message.slice(max_chunk_length*i, max_chunk_length*(i+1)), 'hex')]))
|
||||||
|
messageChunks.push(thisHeader.toString('hex') + message.slice(max_chunk_length*i, max_chunk_length*(i+1)))
|
||||||
|
}
|
||||||
|
// if the message is less than 1400 bytes than this catches it, and this catches any left over after pulling off all the 1400 byte lengths
|
||||||
|
if(message.length % max_chunk_length > 0) {
|
||||||
|
// there is an annoying edge case when the message is an integer multiple of your chunk length and it doesn't have any leftovers which can cause a crash if you don't check for it
|
||||||
|
const thisHeader = make_chunk_header(message_id, numChunks+1, numChunks+1, (message.length % max_chunk_length) + 9, 1)
|
||||||
|
//messageChunks.push(Buffer.concat([thisHeader, Buffer.from(message.slice(numChunks * max_chunk_length), 'hex')]))
|
||||||
|
messageChunks.push(thisHeader.toString('hex') + message.slice(numChunks * max_chunk_length))
|
||||||
|
}
|
||||||
|
return messageChunks
|
||||||
|
}
|
||||||
|
|
||||||
|
// to make this we build a buffer then convert it to a hex string at the end
|
||||||
|
function make_chunk_header(message_id, chunk_index, num_chunks, chunk_length, chunk_version=1) {
|
||||||
|
// only version 1 so far, so we don't have to check yet because this is making it.
|
||||||
|
if (!Buffer.isBuffer(message_id)) {
|
||||||
|
const tmp1 = Buffer.allocUnsafe(2)
|
||||||
|
tmp1.writeUInt16BE(message_id)
|
||||||
|
message_id = tmp1
|
||||||
|
}
|
||||||
|
if (!Buffer.isBuffer(chunk_length)) {
|
||||||
|
const tmp5 = Buffer.allocUnsafe(2)
|
||||||
|
tmp5.writeUInt16BE(chunk_length)
|
||||||
|
chunk_length = tmp5
|
||||||
|
}
|
||||||
|
if (!Buffer.isBuffer(chunk_version)) {
|
||||||
|
const tmp2 = Buffer.allocUnsafe(1)
|
||||||
|
tmp2.writeUInt8(chunk_version)
|
||||||
|
chunk_version = tmp2
|
||||||
|
}
|
||||||
|
if (!Buffer.isBuffer(chunk_index)) {
|
||||||
|
const tmp3 = Buffer.allocUnsafe(2)
|
||||||
|
tmp3.writeUInt16BE(chunk_index)
|
||||||
|
chunk_index = tmp3
|
||||||
|
}
|
||||||
|
if (!Buffer.isBuffer(num_chunks)) {
|
||||||
|
const tmp4 = Buffer.allocUnsafe(2)
|
||||||
|
tmp4.writeUInt16BE(num_chunks)
|
||||||
|
num_chunks = tmp4
|
||||||
|
}
|
||||||
|
chunk_header = Buffer.concat([message_id, chunk_length, chunk_version, chunk_index, num_chunks])
|
||||||
|
return chunk_header
|
||||||
|
}
|
||||||
|
module.exports = {
|
||||||
|
chunk_message,
|
||||||
|
receive_chunk,
|
||||||
|
parse_chunk_header
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
|
@ -0,0 +1,156 @@
|
||||||
|
CREATE DATABASE IF NOT EXISTS Chickens;
|
||||||
|
|
||||||
|
USE Chickens;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS Users (
|
||||||
|
name TEXT UNIQUE NOT NULL,
|
||||||
|
creation_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS Nodes (
|
||||||
|
hardware_id VARCHAR(255) UNIQUE NOT NULL,
|
||||||
|
friendly_name TEXT NOT NULL,
|
||||||
|
creation_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
author TEXT NOT NULL,
|
||||||
|
CONSTRAINT FOREIGN KEY (author) REFERENCES Users(name)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS Groups (
|
||||||
|
name VARCHAR(255) UNIQUE NOT NULL,
|
||||||
|
author TEXT NOT NULL,
|
||||||
|
creation_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
CONSTRAINT FOREIGN KEY (author) REFERENCES Users(name)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS Node2Group (
|
||||||
|
node VARCHAR(255) NOT NULL,
|
||||||
|
node_group VARCHAR(255) NOT NULL,
|
||||||
|
author TEXT NOT NULL,
|
||||||
|
creation_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
CONSTRAINT unique_pairing UNIQUE(node, node_group),
|
||||||
|
CONSTRAINT FOREIGN KEY (author) REFERENCES Users(name),
|
||||||
|
CONSTRAINT FOREIGN KEY (node) REFERENCES Nodes(hardware_id),
|
||||||
|
CONSTRAINT FOREIGN KEY (node_group) REFERENCES Groups(name)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS GroupType (
|
||||||
|
name VARCHAR(255) UNIQUE NOT NULL,
|
||||||
|
author TEXT NOT NULL,
|
||||||
|
creation_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
CONSTRAINT FOREIGN KEY (author) REFERENCES Users(name)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS GroupParent (
|
||||||
|
child VARCHAR(255) NOT NULL,
|
||||||
|
parent VARCHAR(255) NOT NULL,
|
||||||
|
author TEXT NOT NULL,
|
||||||
|
creation_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
CONSTRAINT unique_relation UNIQUE(child, parent),
|
||||||
|
CONSTRAINT FOREIGN KEY (author) REFERENCES Users(name),
|
||||||
|
CONSTRAINT FOREIGN KEY (child) REFERENCES Nodes(hardware_id),
|
||||||
|
CONSTRAINT FOREIGN KEY (parent) REFERENCES Nodes(hardware_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS Group2Type (
|
||||||
|
group_name VARCHAR(255) NOT NULL,
|
||||||
|
type TEXT NOT NULL,
|
||||||
|
author TEXT NOT NULL,
|
||||||
|
creation_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
CONSTRAINT unique_grouptype UNIQUE(group_name, type),
|
||||||
|
CONSTRAINT FOREIGN KEY (author) REFERENCES Users(name),
|
||||||
|
CONSTRAINT FOREIGN KEY (group_name) REFERENCES Groups(name),
|
||||||
|
CONSTRAINT FOREIGN KEY (type) REFERENCES GroupType(name)
|
||||||
|
);
|
||||||
|
|
||||||
|
--CREATE TABLE IF NOT EXISTS Measurement (
|
||||||
|
-- source_node VARCHAR(255) NOT NULL,
|
||||||
|
-- reporting_node VARCHAR(255) NOT NULL,
|
||||||
|
-- associated_group TEXT NOT NULL,
|
||||||
|
-- collection_time TIMESTAMP NOT NULL,
|
||||||
|
-- server_received_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
-- temperature_18_inch FLOAT NOT NULL,
|
||||||
|
-- temperature_36_inch FLOAT NOT NULL,
|
||||||
|
-- device_temperature FLOAT NOT NULL,
|
||||||
|
-- ambient_temperature FLOAT NOT NULL,
|
||||||
|
-- relative_humidity FLOAT NOT NULL,
|
||||||
|
-- barometric_pressure FLOAT NOT NULL,
|
||||||
|
-- accelerometer_x FLOAT NOT NULL,
|
||||||
|
-- accelerometer_y FLOAT NOT NULL,
|
||||||
|
-- accelerometer_z FLOAT NOT NULL,
|
||||||
|
-- battery_charge_percent FLOAT NOT NULL,
|
||||||
|
-- battery_voltage FLOAT NOT NULL,
|
||||||
|
-- remaining_battery_capacity FLOAT NOT NULL,
|
||||||
|
-- CONSTRAINT device_measurement UNIQUE(source_node, collection_time),
|
||||||
|
-- CONSTRAINT FOREIGN KEY (source_node) REFERENCES Nodes(hardware_id),
|
||||||
|
-- CONSTRAINT FOREIGN KEY (reporting_node) REFERENCES Nodes(hardware_id),
|
||||||
|
-- CONSTRAINT FOREIGN KEY (associated_group) REFERENCES Groups(name)
|
||||||
|
--);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS Measurement (
|
||||||
|
source_node VARCHAR(255) NOT NULL,
|
||||||
|
reporting_node VARCHAR(255) NOT NULL,
|
||||||
|
associated_group VARCHAR(255) NOT NULL,
|
||||||
|
collection_time TIMESTAMP NOT NULL,
|
||||||
|
server_received_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
temperature_18_inch FLOAT NOT NULL,
|
||||||
|
temperature_36_inch FLOAT NOT NULL,
|
||||||
|
device_temperature FLOAT NOT NULL,
|
||||||
|
ambient_temperature FLOAT NOT NULL,
|
||||||
|
relative_humidity FLOAT NOT NULL,
|
||||||
|
barometric_pressure FLOAT NOT NULL,
|
||||||
|
accelerometer_x FLOAT NOT NULL,
|
||||||
|
accelerometer_y FLOAT NOT NULL,
|
||||||
|
accelerometer_z FLOAT NOT NULL,
|
||||||
|
battery_charge_percent FLOAT NOT NULL,
|
||||||
|
battery_voltage FLOAT NOT NULL,
|
||||||
|
remaining_battery_capacity FLOAT NOT NULL,
|
||||||
|
CONSTRAINT device_measurement UNIQUE(source_node, collection_time),
|
||||||
|
CONSTRAINT FOREIGN KEY (source_node) REFERENCES Nodes(hardware_id),
|
||||||
|
CONSTRAINT FOREIGN KEY (reporting_node) REFERENCES Nodes(hardware_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS NodeRssiRecord (
|
||||||
|
node VARCHAR(255) NOT NULL,
|
||||||
|
neighbor VARCHAR(255) NOT NULL,
|
||||||
|
rssi INT NOT NULL,
|
||||||
|
collection_time TIMESTAMP NOT NULL,
|
||||||
|
server_received_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
CONSTRAINT unique_thing UNIQUE(node, neighbor, collection_time),
|
||||||
|
CONSTRAINT FOREIGN KEY (node) REFERENCES Nodes(hardware_id),
|
||||||
|
CONSTRAINT FOREIGN KEY (neighbor) REFERENCES Nodes(hardware_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS NodeFileManifest (
|
||||||
|
node VARCHAR(255) NOT NULL,
|
||||||
|
collection_time TIMESTAMP NOT NULL,
|
||||||
|
server_received_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
program_id INT NOT NULL,
|
||||||
|
program_version INT NOT NULL,
|
||||||
|
CONSTRAINT unique_version UNIQUE(node, collection_time, program_id),
|
||||||
|
CONSTRAINT FOREIGN KEY (node) REFERENCES Nodes(hardware_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS NodeReportedStatus (
|
||||||
|
node VARCHAR(255) NOT NULL,
|
||||||
|
collection_time TIMESTAMP NOT NULL,
|
||||||
|
server_received_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
measurement_interval_minutes INT NOT NULL,
|
||||||
|
wake_window_length_minutes INT NOT NULL,
|
||||||
|
offset_from_midnight_minutes INT NOT NULL,
|
||||||
|
sleep_duration_minutes INT NOT NULL,
|
||||||
|
number_saved_measurements INT NOT NULL,
|
||||||
|
when_time_was_last_updated TIMESTAMP NOT NULL,
|
||||||
|
CONSTRAINT unique_status UNIQUE(node, collection_time),
|
||||||
|
CONSTRAINT FOREIGN KEY (node) REFERENCES Nodes(hardware_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS NodeConfiguration (
|
||||||
|
node VARCHAR(255) NOT NULL,
|
||||||
|
author TEXT NOT NULL,
|
||||||
|
creation_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
measurement_interval_minutes INT NOT NULL,
|
||||||
|
offset_from_midnight_minutes INT NOT NULL,
|
||||||
|
wake_window_length_minutes INT NOT NULL,
|
||||||
|
CONSTRAINT FOREIGN KEY (author) REFERENCES Users(name),
|
||||||
|
CONSTRAINT FOREIGN KEY (node) REFERENCES Nodes(hardware_id)
|
||||||
|
);
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
export db_host="127.0.0.1"
|
||||||
|
export db_user=chicken
|
||||||
|
export db_password=cluckcluck
|
||||||
|
export db_database=Chickens
|
||||||
|
export db_port=3306
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
const mysql = require("mysql2");
|
||||||
|
|
||||||
|
function createPool() {
|
||||||
|
// const pool = mysql.createPool({
|
||||||
|
// host: process.env.db_host,
|
||||||
|
// user: process.env.db_user,
|
||||||
|
// password: process.env.db_password,
|
||||||
|
// database: process.env.db_database,
|
||||||
|
// port: process.env.db_port,
|
||||||
|
// timezone: '+00:00'
|
||||||
|
// });
|
||||||
|
const pool = mysql.createPool({
|
||||||
|
});
|
||||||
|
return pool
|
||||||
|
}
|
||||||
|
|
||||||
|
const pool = createPool()
|
||||||
|
|
||||||
|
pool.getConnection(function(err, connection) {
|
||||||
|
if(err) {
|
||||||
|
console.log(err)
|
||||||
|
} else {
|
||||||
|
pool.getConnection(function(err, connection) {
|
||||||
|
connection.execute("USE Chickens;",[],console.log)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
@ -0,0 +1,522 @@
|
||||||
|
USE Chickens;
|
||||||
|
|
||||||
|
-- add users
|
||||||
|
-- TODO: what goes here?
|
||||||
|
INSERT IGNORE INTO Users (name) VALUES
|
||||||
|
('admin');
|
||||||
|
|
||||||
|
-- add nodes
|
||||||
|
INSERT IGNORE INTO Nodes (friendly_name, hardware_id, author) VALUES
|
||||||
|
('Null Node','000000000000','admin'),
|
||||||
|
('Broadcast','ffffffffffff','admin'),
|
||||||
|
('Probe 0', '34cdb084cac8','admin'),
|
||||||
|
('Probe 2', '34cdb08f9ddc','admin'),
|
||||||
|
('Probe 3', '34cdb08ff64c','admin'),
|
||||||
|
('Probe 4', '34cdb0900390','admin'),
|
||||||
|
('Probe 5', '34cdb08ff358','admin'),
|
||||||
|
('Probe 6', '34cdb0926c80','admin'),
|
||||||
|
('Probe 7', '34cdb084cad8','admin'),
|
||||||
|
('Probe 8', '34cdb0901f10','admin'),
|
||||||
|
('Probe 9', '34cdb090250c','admin'),
|
||||||
|
('Probe 10', '34cdb084cae8','admin'),
|
||||||
|
('Probe 11', '34cdb084caf4','admin'),
|
||||||
|
('Probe 12', '34cdb084cb00','admin'),
|
||||||
|
('Probe 13', '34cdb0902b60','admin'),
|
||||||
|
('Probe 14', '34cdb08ff5c8','admin'),
|
||||||
|
('Probe 15', '34cdb0901ee4','admin'),
|
||||||
|
('Probe 16', '34cdb091f940','admin'),
|
||||||
|
('Probe 17', '34cdb084cadc','admin'),
|
||||||
|
('Probe 18', '34cdb08fffd8','admin'),
|
||||||
|
('Probe 19', '34cdb084cb10','admin'),
|
||||||
|
('Probe 20', '34cdb0901180','admin'),
|
||||||
|
('Probe 21', '34cdb084cb14','admin'),
|
||||||
|
('Probe 22', '34cdb084cb24','admin'),
|
||||||
|
('Probe 23', '34cdb0902bb4','admin'),
|
||||||
|
('Probe 24', '34cdb084cb20','admin'),
|
||||||
|
('Probe 25', '34cdb09001ac','admin'),
|
||||||
|
('Probe 27', '34cdb0910dc8','admin'),
|
||||||
|
('Probe 28', '34cdb0902b08','admin'),
|
||||||
|
('Probe 29', '34cdb0902c78','admin'),
|
||||||
|
('Probe 30', '34cdb0901ba0','admin'),
|
||||||
|
('Probe 31', '34cdb08ffd14','admin'),
|
||||||
|
('Probe 32', '34cdb084cb40','admin'),
|
||||||
|
('Probe 33', '34cdb08ffdb8','admin'),
|
||||||
|
('Probe 34', '34cdb0901d58','admin'),
|
||||||
|
('Probe 35', '34cdb084cb48','admin'),
|
||||||
|
('Probe 36', '34cdb0902ff4','admin'),
|
||||||
|
('Probe 37', '34cdb084cb28','admin'),
|
||||||
|
('Probe 38', '34cdb08fffb4','admin'),
|
||||||
|
('Probe 39', '34cdb0901d18','admin'),
|
||||||
|
('Probe 40', '34cdb0900d90','admin'),
|
||||||
|
('Probe 41', '34cdb08ffcb0','admin'),
|
||||||
|
('Probe 42', '34cdb09022a4','admin'),
|
||||||
|
('Probe 43', '34cdb0900434', 'admin'),
|
||||||
|
('Probe 45', '34cdb091a93c', 'admin'),
|
||||||
|
('Basestation 1', 'cc8da22c9090','admin'),
|
||||||
|
('Basestation 3', 'cc8da22b4600','admin'),
|
||||||
|
('Basestation 4', '4827e28abc94','admin'),
|
||||||
|
('Basestation 5', '48ca430f8d5c','admin'),
|
||||||
|
('Basestation 6', '48ca430f9834','admin');
|
||||||
|
|
||||||
|
-- add default group types
|
||||||
|
INSERT IGNORE INTO GroupType (name, author) VALUES
|
||||||
|
('Site', 'admin'),
|
||||||
|
('Windrow', 'admin'),
|
||||||
|
('Temperature Site', 'admin'),
|
||||||
|
('Device Role', 'admin'),
|
||||||
|
('Device Deployment Status', 'admin'),
|
||||||
|
('Owner', 'admin'),
|
||||||
|
('Device Type', 'admin'),
|
||||||
|
('Device Status', 'admin');
|
||||||
|
|
||||||
|
-- add groups
|
||||||
|
INSERT IGNORE INTO Groups (name, author) VALUES
|
||||||
|
('Probe', 'admin'),
|
||||||
|
('Basestation', 'admin'),
|
||||||
|
('Robot Hands', 'admin'),
|
||||||
|
('The Danger V1.2', 'admin'),
|
||||||
|
('Jav V1.10', 'admin'),
|
||||||
|
('BOYD V1.11', 'admin'),
|
||||||
|
('BOYD V1.12', 'admin'),
|
||||||
|
('Incomplete', 'admin'),
|
||||||
|
('Incomplete Probes', 'admin'),
|
||||||
|
('Incomplete Basestations', 'admin'),
|
||||||
|
('Ready to Deploy', 'admin'),
|
||||||
|
('Probes Ready to Deploy', 'admin'),
|
||||||
|
('Basestations Ready to Deploy', 'admin'),
|
||||||
|
('Deployed', 'admin'),
|
||||||
|
('Deployed Probes', 'admin'),
|
||||||
|
('Deployed Basestations', 'admin'),
|
||||||
|
('In Maintenance', 'admin'),
|
||||||
|
('Inactive', 'admin'),
|
||||||
|
('Wisconsin Turkey', 'admin'),
|
||||||
|
('WT Windrow 1', 'admin'),
|
||||||
|
('WT Windrow 2', 'admin'),
|
||||||
|
('WT1 Temperature Site 1', 'admin'),
|
||||||
|
('WT1 Temperature Site 2', 'admin'),
|
||||||
|
('WT1 Temperature Site 3', 'admin'),
|
||||||
|
('WT1 Temperature Site 4', 'admin'),
|
||||||
|
('WT1 Temperature Site 5', 'admin'),
|
||||||
|
('WT1 Temperature Site 6', 'admin'),
|
||||||
|
('WT1 Temperature Site 7', 'admin'),
|
||||||
|
('WT1 Temperature Site 8', 'admin'),
|
||||||
|
('WT1 Temperature Site 9', 'admin'),
|
||||||
|
('WT1 Temperature Site 10', 'admin'),
|
||||||
|
('WT2 Temperature Site 1', 'admin'),
|
||||||
|
('WT2 Temperature Site 2', 'admin'),
|
||||||
|
('WT2 Temperature Site 3', 'admin'),
|
||||||
|
('WT2 Temperature Site 4', 'admin'),
|
||||||
|
('WT2 Temperature Site 5', 'admin'),
|
||||||
|
('WT2 Temperature Site 6', 'admin'),
|
||||||
|
('WT2 Temperature Site 7', 'admin'),
|
||||||
|
('WT2 Temperature Site 8', 'admin'),
|
||||||
|
('WT2 Temperature Site 9', 'admin'),
|
||||||
|
('WT2 Temperature Site 10', 'admin'),
|
||||||
|
('Cour des Miracles', 'admin'),
|
||||||
|
('CDM Garden', 'admin'),
|
||||||
|
('CDM House', 'admin'),
|
||||||
|
('CDMG Compost', 'admin'),
|
||||||
|
('CDMG Wall', 'admin'),
|
||||||
|
('CDMG Back Roof', 'admin'),
|
||||||
|
('CDMG Middle Roof', 'admin'),
|
||||||
|
('CDMH Middle House', 'admin'),
|
||||||
|
('CDMH Couch', 'admin'),
|
||||||
|
('CDMH Floor', 'admin'),
|
||||||
|
('CDMH Table', 'admin'),
|
||||||
|
('CDMH Window', 'admin'),
|
||||||
|
('Chez Mari', 'admin'),
|
||||||
|
('Chez Mari Row 1', 'admin'),
|
||||||
|
('Chez Mari Row 2', 'admin'),
|
||||||
|
('Chez Mari Row 3', 'admin'),
|
||||||
|
('CM1 Basestation Site', 'admin'),
|
||||||
|
('CM1 Temperature Site 1', 'admin'),
|
||||||
|
('CM1 Temperature Site 2', 'admin'),
|
||||||
|
('CM1 Temperature Site 3', 'admin'),
|
||||||
|
('CM1 Temperature Site 4', 'admin'),
|
||||||
|
('CM1 Temperature Site 5', 'admin'),
|
||||||
|
('CM1 Temperature Site 6', 'admin'),
|
||||||
|
('CM1 Temperature Site 7', 'admin'),
|
||||||
|
('CM1 Temperature Site 8', 'admin'),
|
||||||
|
('CM1 Temperature Site 9', 'admin'),
|
||||||
|
('CM1 Temperature Site 10', 'admin'),
|
||||||
|
('CM2 Temperature Site 1', 'admin'),
|
||||||
|
('CM2 Temperature Site 2', 'admin'),
|
||||||
|
('CM2 Temperature Site 3', 'admin'),
|
||||||
|
('CM2 Temperature Site 4', 'admin'),
|
||||||
|
('CM2 Temperature Site 5', 'admin'),
|
||||||
|
('CM2 Temperature Site 6', 'admin'),
|
||||||
|
('CM2 Temperature Site 7', 'admin'),
|
||||||
|
('CM2 Temperature Site 8', 'admin'),
|
||||||
|
('CM2 Temperature Site 9', 'admin'),
|
||||||
|
('CM2 Temperature Site 10', 'admin'),
|
||||||
|
('CM3 Temperature Site 1', 'admin'),
|
||||||
|
('CM3 Temperature Site 2', 'admin'),
|
||||||
|
('J4 House', 'admin'),
|
||||||
|
('J4 House Windrow 1', 'admin'),
|
||||||
|
('J4 W1 Basestation Site', 'admin'),
|
||||||
|
('J4 W1 Temperature Site 1', 'admin'),
|
||||||
|
('J4 W1 Temperature Site 2', 'admin'),
|
||||||
|
('J4 W1 Temperature Site 3', 'admin'),
|
||||||
|
('J4 W1 Temperature Site 4', 'admin'),
|
||||||
|
('J4 W1 Temperature Site 5', 'admin'),
|
||||||
|
('J4 W1 Temperature Site 6', 'admin'),
|
||||||
|
('J4 W1 Temperature Site 7', 'admin'),
|
||||||
|
('J4 W1 Temperature Site 8', 'admin'),
|
||||||
|
('J4 W1 Temperature Site 9', 'admin'),
|
||||||
|
('J4 W1 Temperature Site 10', 'admin'),
|
||||||
|
('The Void', 'admin'),
|
||||||
|
('Where Mortals Fear to Tread', 'admin'),
|
||||||
|
('Unknowable 1', 'admin'),
|
||||||
|
('Unknowable 2', 'admin'),
|
||||||
|
('Unknowable 3', 'admin'),
|
||||||
|
('Unknowable 4', 'admin'),
|
||||||
|
('Unknowable 5', 'admin'),
|
||||||
|
('Unknowable 6', 'admin'),
|
||||||
|
('Unknowable 7', 'admin'),
|
||||||
|
('Unknowable 8', 'admin'),
|
||||||
|
('Unknowable 9', 'admin'),
|
||||||
|
('Unknowable 10', 'admin');
|
||||||
|
|
||||||
|
|
||||||
|
-- add group2type
|
||||||
|
INSERT IGNORE INTO Group2type (group_name, type, author) VALUES
|
||||||
|
('Probe', 'Device Role', 'admin'),
|
||||||
|
('Basestation', 'Device Role', 'admin'),
|
||||||
|
('Robot Hands', 'Owner', 'admin'),
|
||||||
|
('The Danger V1.2', 'Device Type', 'admin'),
|
||||||
|
('Jav V1.10', 'Device Type', 'admin'),
|
||||||
|
('BYOD V1,11', 'Device Type', 'admin'),
|
||||||
|
('BYOD V1.12', 'Device Type', 'admin'),
|
||||||
|
('Incomplete', 'Device Status', 'admin'),
|
||||||
|
('Incomplete Probes', 'Device Status', 'admin'),
|
||||||
|
('Incomplete Basestations', 'Device Status', 'admin'),
|
||||||
|
('Ready to Deploy', 'Device Status', 'admin'),
|
||||||
|
('Probes Ready to Deploy', 'Device Status', 'admin'),
|
||||||
|
('Basestations Ready to Deploy', 'Device Status', 'admin'),
|
||||||
|
('Deployed', 'Device Status', 'admin'),
|
||||||
|
('Deployed Probes', 'Device Status', 'admin'),
|
||||||
|
('Deployed Basestations', 'Device Status', 'admin'),
|
||||||
|
('In Maintenance', 'Device Status', 'admin'),
|
||||||
|
('Inactive', 'Device Status', 'admin'),
|
||||||
|
('Wisconsin Turkey', 'Site', 'admin'),
|
||||||
|
('WT Windrow 1', 'Windrow', 'admin'),
|
||||||
|
('WT Windrow 2', 'Windrow', 'admin'),
|
||||||
|
('WT1 Temperature Site 1', 'Temperature Site', 'admin'),
|
||||||
|
('WT1 Temperature Site 2', 'Temperature Site', 'admin'),
|
||||||
|
('WT1 Temperature Site 3', 'Temperature Site', 'admin'),
|
||||||
|
('WT1 Temperature Site 4', 'Temperature Site', 'admin'),
|
||||||
|
('WT1 Temperature Site 5', 'Temperature Site', 'admin'),
|
||||||
|
('WT1 Temperature Site 6', 'Temperature Site', 'admin'),
|
||||||
|
('WT1 Temperature Site 7', 'Temperature Site', 'admin'),
|
||||||
|
('WT1 Temperature Site 8', 'Temperature Site', 'admin'),
|
||||||
|
('WT1 Temperature Site 9', 'Temperature Site', 'admin'),
|
||||||
|
('WT1 Temperature Site 10', 'Temperature Site', 'admin'),
|
||||||
|
('WT2 Temperature Site 1', 'Temperature Site', 'admin'),
|
||||||
|
('WT2 Temperature Site 2', 'Temperature Site', 'admin'),
|
||||||
|
('WT2 Temperature Site 3', 'Temperature Site', 'admin'),
|
||||||
|
('WT2 Temperature Site 4', 'Temperature Site', 'admin'),
|
||||||
|
('WT2 Temperature Site 5', 'Temperature Site', 'admin'),
|
||||||
|
('WT2 Temperature Site 6', 'Temperature Site', 'admin'),
|
||||||
|
('WT2 Temperature Site 7', 'Temperature Site', 'admin'),
|
||||||
|
('WT2 Temperature Site 8', 'Temperature Site', 'admin'),
|
||||||
|
('WT2 Temperature Site 9', 'Temperature Site', 'admin'),
|
||||||
|
('WT2 Temperature Site 10', 'Temperature Site', 'admin'),
|
||||||
|
('Cour des Miracles', 'Site', 'admin'),
|
||||||
|
('CDM Garden', 'Windrow', 'admin'),
|
||||||
|
('CDM House', 'Windrow', 'admin'),
|
||||||
|
('CDMG Compost', 'Temperature Site', 'admin'),
|
||||||
|
('CDMG Wall', 'Temperature Site', 'admin'),
|
||||||
|
('CDMG Back Roof', 'Temperature Site', 'admin'),
|
||||||
|
('CDMG Middle Roof', 'Temperature Site', 'admin'),
|
||||||
|
('CDMH Middle House', 'Temperature Site', 'admin'),
|
||||||
|
('CDMH Couch', 'Temperature Site', 'admin'),
|
||||||
|
('CDMH Floor', 'Temperature Site', 'admin'),
|
||||||
|
('CDMH Table', 'Temperature Site', 'admin'),
|
||||||
|
('CDMH Window', 'Temperature Site', 'admin'),
|
||||||
|
('Chez Mari', 'Site', 'admin'),
|
||||||
|
('Chez Mari Row 1', 'Windrow', 'admin'),
|
||||||
|
('Chez Mari Row 2', 'Windrow', 'admin'),
|
||||||
|
('Chez Mari Row 3', 'Windrow', 'admin'),
|
||||||
|
('CM1 Basestation Site', 'Temperature Site', 'admin'),
|
||||||
|
('CM1 Temperature Site 1', 'Temperature Site', 'admin'),
|
||||||
|
('CM1 Temperature Site 2', 'Temperature Site', 'admin'),
|
||||||
|
('CM1 Temperature Site 3', 'Temperature Site', 'admin'),
|
||||||
|
('CM1 Temperature Site 4', 'Temperature Site', 'admin'),
|
||||||
|
('CM1 Temperature Site 5', 'Temperature Site', 'admin'),
|
||||||
|
('CM1 Temperature Site 6', 'Temperature Site', 'admin'),
|
||||||
|
('CM1 Temperature Site 7', 'Temperature Site', 'admin'),
|
||||||
|
('CM1 Temperature Site 8', 'Temperature Site', 'admin'),
|
||||||
|
('CM1 Temperature Site 9', 'Temperature Site', 'admin'),
|
||||||
|
('CM1 Temperature Site 10', 'Temperature Site', 'admin'),
|
||||||
|
('CM2 Temperature Site 1', 'Temperature Site', 'admin'),
|
||||||
|
('CM2 Temperature Site 2', 'Temperature Site', 'admin'),
|
||||||
|
('CM2 Temperature Site 3', 'Temperature Site', 'admin'),
|
||||||
|
('CM2 Temperature Site 4', 'Temperature Site', 'admin'),
|
||||||
|
('CM2 Temperature Site 5', 'Temperature Site', 'admin'),
|
||||||
|
('CM2 Temperature Site 6', 'Temperature Site', 'admin'),
|
||||||
|
('CM2 Temperature Site 7', 'Temperature Site', 'admin'),
|
||||||
|
('CM2 Temperature Site 8', 'Temperature Site', 'admin'),
|
||||||
|
('CM2 Temperature Site 9', 'Temperature Site', 'admin'),
|
||||||
|
('CM2 Temperature Site 10', 'Temperature Site', 'admin'),
|
||||||
|
('CM3 Temperature Site 1', 'Temperature Site', 'admin'),
|
||||||
|
('CM3 Temperature Site 2', 'Temperature Site', 'admin'),
|
||||||
|
('J4 House', 'Site', 'admin'),
|
||||||
|
('J4 House Windrow 1', 'Windrow', 'admin'),
|
||||||
|
('J4 W1 Basestation Site', 'Temperature Site', 'admin'),
|
||||||
|
('J4 W1 Temperature Site 1', 'Temperature Site', 'admin'),
|
||||||
|
('J4 W1 Temperature Site 2', 'Temperature Site', 'admin'),
|
||||||
|
('J4 W1 Temperature Site 3', 'Temperature Site', 'admin'),
|
||||||
|
('J4 W1 Temperature Site 4', 'Temperature Site', 'admin'),
|
||||||
|
('J4 W1 Temperature Site 5', 'Temperature Site', 'admin'),
|
||||||
|
('J4 W1 Temperature Site 6', 'Temperature Site', 'admin'),
|
||||||
|
('J4 W1 Temperature Site 7', 'Temperature Site', 'admin'),
|
||||||
|
('J4 W1 Temperature Site 8', 'Temperature Site', 'admin'),
|
||||||
|
('J4 W1 Temperature Site 9', 'Temperature Site', 'admin'),
|
||||||
|
('J4 W1 Temperature Site 10', 'Temperature Site', 'admin'),
|
||||||
|
('The Void', 'Site', 'admin'),
|
||||||
|
('Where Mortals Fear to Tread', 'Windrow', 'admin'),
|
||||||
|
('Unknowable 1', 'Temperature Site', 'admin'),
|
||||||
|
('Unknowable 2', 'Temperature Site', 'admin'),
|
||||||
|
('Unknowable 3', 'Temperature Site', 'admin'),
|
||||||
|
('Unknowable 4', 'Temperature Site', 'admin'),
|
||||||
|
('Unknowable 5', 'Temperature Site', 'admin'),
|
||||||
|
('Unknowable 6', 'Temperature Site', 'admin'),
|
||||||
|
('Unknowable 7', 'Temperature Site', 'admin'),
|
||||||
|
('Unknowable 8', 'Temperature Site', 'admin'),
|
||||||
|
('Unknowable 9', 'Temperature Site', 'admin'),
|
||||||
|
('Unknowable 10', 'Temperature Site', 'admin');
|
||||||
|
|
||||||
|
-- add group parents
|
||||||
|
INSERT IGNORE INTO GroupParent (child, parent, author) VALUES
|
||||||
|
('WT Windrow 1', 'Wisconsin Turkey', 'admin'),
|
||||||
|
('WT Windrow 2', 'Wisconsin Turkey', 'admin'),
|
||||||
|
('WT1 Temperature Site 1', 'WT Windrow 1', 'admin'),
|
||||||
|
('WT1 Temperature Site 2', 'WT Windrow 1', 'admin'),
|
||||||
|
('WT1 Temperature Site 3', 'WT Windrow 1', 'admin'),
|
||||||
|
('WT1 Temperature Site 4', 'WT Windrow 1', 'admin'),
|
||||||
|
('WT1 Temperature Site 5', 'WT Windrow 1', 'admin'),
|
||||||
|
('WT1 Temperature Site 6', 'WT Windrow 1', 'admin'),
|
||||||
|
('WT1 Temperature Site 7', 'WT Windrow 1', 'admin'),
|
||||||
|
('WT1 Temperature Site 8', 'WT Windrow 1', 'admin'),
|
||||||
|
('WT1 Temperature Site 9', 'WT Windrow 1', 'admin'),
|
||||||
|
('WT1 Temperature Site 10', 'WT Windrow 1', 'admin'),
|
||||||
|
('WT2 Temperature Site 1', 'WT Windrow 2', 'admin'),
|
||||||
|
('WT2 Temperature Site 2', 'WT Windrow 2', 'admin'),
|
||||||
|
('WT2 Temperature Site 3', 'WT Windrow 2', 'admin'),
|
||||||
|
('WT2 Temperature Site 4', 'WT Windrow 2', 'admin'),
|
||||||
|
('WT2 Temperature Site 5', 'WT Windrow 2', 'admin'),
|
||||||
|
('WT2 Temperature Site 6', 'WT Windrow 2', 'admin'),
|
||||||
|
('WT2 Temperature Site 7', 'WT Windrow 2', 'admin'),
|
||||||
|
('WT2 Temperature Site 8', 'WT Windrow 2', 'admin'),
|
||||||
|
('WT2 Temperature Site 9', 'WT Windrow 2', 'admin'),
|
||||||
|
('WT2 Temperature Site 10', 'WT Windrow 2', 'admin'),
|
||||||
|
('CDM Garden', 'Cour des Miracles', 'admin'),
|
||||||
|
('CDM House', 'Cour des Miracles', 'admin'),
|
||||||
|
('CDMG Compost', 'CDM Garden', 'admin'),
|
||||||
|
('CDMG Wall', 'CDM Garden', 'admin'),
|
||||||
|
('CDMG Back Roof', 'CDM Garden', 'admin'),
|
||||||
|
('CDMG Middle Roof', 'CDM Garden', 'admin'),
|
||||||
|
('CDMH Middle House', 'CDM House', 'admin'),
|
||||||
|
('CDMH Couch', 'CDM House', 'admin'),
|
||||||
|
('CDMH Floor', 'CDM House', 'admin'),
|
||||||
|
('CDMH Table', 'CDM House', 'admin'),
|
||||||
|
('CDMH Window', 'CDM House', 'admin'),
|
||||||
|
('Chez Mari Row 1', 'Chez Mari', 'admin'),
|
||||||
|
('Chez Mari Row 2', 'Chez Mari', 'admin'),
|
||||||
|
('Chez Mari Row 3', 'Chez Mari', 'admin'),
|
||||||
|
('CM1 Basestation Site', 'Chez Mari Row 1', 'admin'),
|
||||||
|
('CM1 Temperature Site 1', 'Chez Mari Row 1', 'admin'),
|
||||||
|
('CM1 Temperature Site 2', 'Chez Mari Row 1', 'admin'),
|
||||||
|
('CM1 Temperature Site 3', 'Chez Mari Row 1', 'admin'),
|
||||||
|
('CM1 Temperature Site 4', 'Chez Mari Row 1', 'admin'),
|
||||||
|
('CM1 Temperature Site 5', 'Chez Mari Row 1', 'admin'),
|
||||||
|
('CM1 Temperature Site 6', 'Chez Mari Row 1', 'admin'),
|
||||||
|
('CM1 Temperature Site 7', 'Chez Mari Row 1', 'admin'),
|
||||||
|
('CM1 Temperature Site 8', 'Chez Mari Row 1', 'admin'),
|
||||||
|
('CM1 Temperature Site 9', 'Chez Mari Row 1', 'admin'),
|
||||||
|
('CM1 Temperature Site 10', 'Chez Mari Row 1', 'admin'),
|
||||||
|
('CM2 Temperature Site 1', 'Chez Mari Row 2', 'admin'),
|
||||||
|
('CM2 Temperature Site 2', 'Chez Mari Row 2', 'admin'),
|
||||||
|
('CM2 Temperature Site 3', 'Chez Mari Row 2', 'admin'),
|
||||||
|
('CM2 Temperature Site 4', 'Chez Mari Row 2', 'admin'),
|
||||||
|
('CM2 Temperature Site 5', 'Chez Mari Row 2', 'admin'),
|
||||||
|
('CM2 Temperature Site 6', 'Chez Mari Row 2', 'admin'),
|
||||||
|
('CM2 Temperature Site 7', 'Chez Mari Row 2', 'admin'),
|
||||||
|
('CM2 Temperature Site 8', 'Chez Mari Row 2', 'admin'),
|
||||||
|
('CM2 Temperature Site 9', 'Chez Mari Row 2', 'admin'),
|
||||||
|
('CM2 Temperature Site 10', 'Chez Mari Row 2', 'admin'),
|
||||||
|
('CM3 Temperature Site 1', 'Chez Mari Row 3', 'admin'),
|
||||||
|
('CM3 Temperature Site 2', 'Chez Mari Row 3', 'admin'),
|
||||||
|
('J4 House Windrow 1', 'J4 House', 'admin'),
|
||||||
|
('J4 W1 Basestation Site', 'J4 House Windrow 1', 'admin'),
|
||||||
|
('J4 W1 Temperature Site 1', 'J4 House Windrow 1', 'admin'),
|
||||||
|
('J4 W1 Temperature Site 2', 'J4 House Windrow 1', 'admin'),
|
||||||
|
('J4 W1 Temperature Site 3', 'J4 House Windrow 1', 'admin'),
|
||||||
|
('J4 W1 Temperature Site 4', 'J4 House Windrow 1', 'admin'),
|
||||||
|
('J4 W1 Temperature Site 5', 'J4 House Windrow 1', 'admin'),
|
||||||
|
('J4 W1 Temperature Site 6', 'J4 House Windrow 1', 'admin'),
|
||||||
|
('J4 W1 Temperature Site 7', 'J4 House Windrow 1', 'admin'),
|
||||||
|
('J4 W1 Temperature Site 8', 'J4 House Windrow 1', 'admin'),
|
||||||
|
('J4 W1 Temperature Site 9', 'J4 House Windrow 1', 'admin'),
|
||||||
|
('J4 W1 Temperature Site 10', 'J4 House Windrow 1', 'admin'),
|
||||||
|
('Where Mortals Fear to Tread', 'The Void', 'admin'),
|
||||||
|
('Unknowable 1', 'Where Mortals Fear to Tread', 'admin'),
|
||||||
|
('Unknowable 2', 'Where Mortals Fear to Tread', 'admin'),
|
||||||
|
('Unknowable 3', 'Where Mortals Fear to Tread', 'admin'),
|
||||||
|
('Unknowable 4', 'Where Mortals Fear to Tread', 'admin'),
|
||||||
|
('Unknowable 5', 'Where Mortals Fear to Tread', 'admin'),
|
||||||
|
('Unknowable 6', 'Where Mortals Fear to Tread', 'admin'),
|
||||||
|
('Unknowable 7', 'Where Mortals Fear to Tread', 'admin'),
|
||||||
|
('Unknowable 8', 'Where Mortals Fear to Tread', 'admin'),
|
||||||
|
('Unknowable 9', 'Where Mortals Fear to Tread', 'admin'),
|
||||||
|
('Unknowable 10', 'Where Mortals Fear to Tread', 'admin');
|
||||||
|
|
||||||
|
-- add node2group
|
||||||
|
INSERT IGNORE INTO Node2Group (node, node_group, author) VALUES
|
||||||
|
('34cdb084cac8','Unknowable 9','admin'),
|
||||||
|
('34cdb08f9ddc','Unknowable 8','admin'),
|
||||||
|
('34cdb08ff64c','Unknowable 7','admin'),
|
||||||
|
('34cdb0900390','CM3 Temperature Site 1','admin'),
|
||||||
|
('34cdb08ff358','J4 W1 Temperature Site 7','admin'),
|
||||||
|
('34cdb0926c80','CM3 Temperature Site 1','admin'),
|
||||||
|
('34cdb084cad8','CM2 Temperature Site 10','admin'),
|
||||||
|
('34cdb0901f10','CM2 Temperature Site 9','admin'),
|
||||||
|
('34cdb090250c','Unknowable 6','admin'),
|
||||||
|
('34cdb084cae8','CM2 Temperature Site 8','admin'),
|
||||||
|
('34cdb084caf4','CM2 Temperature Site 7','admin'),
|
||||||
|
('34cdb084cb00','CDMG Wall','admin'),
|
||||||
|
('34cdb0902b60','CM2 Temperature Site 6','admin'),
|
||||||
|
('34cdb08ff5c8','CM2 Temperature Site 5','admin'),
|
||||||
|
('34cdb0901ee4','CM2 Temperature Site 4','admin'),
|
||||||
|
('34cdb091f940','Unknowable 5','admin'),
|
||||||
|
('34cdb084cadc','CM2 Temperature Site 3','admin'),
|
||||||
|
('34cdb08fffd8','J4 W1 Temperature Site 6','admin'),
|
||||||
|
('34cdb084cb10','CM2 Temperature Site 2','admin'),
|
||||||
|
('34cdb0901180','CM2 Temperature Site 1','admin'),
|
||||||
|
('34cdb084cb14','CM1 Temperature Site 10','admin'),
|
||||||
|
('34cdb084cb24','CM1 Temperature Site 9','admin'),
|
||||||
|
('34cdb0902bb4','CM1 Temperature Site 8','admin'),
|
||||||
|
('34cdb084cb20','J4 W1 Temperature Site 5','admin'),
|
||||||
|
('34cdb09001ac','CM1 Temperature Site 7','admin'),
|
||||||
|
('34cdb0910dc8','CM1 Temperature Site 6','admin'),
|
||||||
|
('34cdb0902b08','J4 W1 Temperature Site 4','admin'),
|
||||||
|
('34cdb0902c78','CM1 Temperature Site 5','admin'),
|
||||||
|
('34cdb0901ba0','CM1 Temperature Site 4','admin'),
|
||||||
|
('34cdb08ffd14','Unknowable 4','admin'),
|
||||||
|
('34cdb084cb40','CM1 Temperature Site 3','admin'),
|
||||||
|
('34cdb08ffdb8','Unknowable 3','admin'),
|
||||||
|
('34cdb0901d58','Unknowable 2','admin'),
|
||||||
|
('34cdb084cb48','J4 W1 Temperature Site 3','admin'),
|
||||||
|
('34cdb0902ff4','CM1 Temperature Site 2','admin'),
|
||||||
|
('34cdb084cb28','CM1 Temperature Site 1','admin'),
|
||||||
|
('34cdb08fffb4','CDMG Back Roof','admin'),
|
||||||
|
('34cdb0901d18','J4 W1 Temperature Site 2','admin'),
|
||||||
|
('34cdb0900d90','J4 W1 Temperature Site 1','admin'),
|
||||||
|
('34cdb08ffcb0','CDMG Compost','admin'),
|
||||||
|
('34cdb09022a4','CDMH Floor','admin'),
|
||||||
|
('34cdb0900434','CDMH Window','admin'),
|
||||||
|
('34cdb091a93c','CDMH Middle House','admin'),
|
||||||
|
('cc8da22c9090','J4 W1 Basestation Site','admin'),
|
||||||
|
('cc8da22b4600','CM1 Basestation Site','admin'),
|
||||||
|
('4827e28abc94','Unknowable 1','admin'),
|
||||||
|
('48ca430f8d5c','CDMH Couch','admin'),
|
||||||
|
('48ca430f9834','CDMG Middle Roof','admin'),
|
||||||
|
('cc8da22c9090', 'Basestation', 'admin'),
|
||||||
|
('cc8da22b4600', 'Basestation', 'admin'),
|
||||||
|
('4827e28abc94', 'Basestation', 'admin'),
|
||||||
|
('48ca430f8d5c', 'Basestation', 'admin'),
|
||||||
|
('48ca430f9834', 'Basestation', 'admin'),
|
||||||
|
('34cdb084cac8','Probe','admin'),
|
||||||
|
('34cdb08f9ddc','Probe','admin'),
|
||||||
|
('34cdb08ff64c','Probe','admin'),
|
||||||
|
('34cdb0900390','Probe','admin'),
|
||||||
|
('34cdb08ff358','Probe','admin'),
|
||||||
|
('34cdb0926c80','Probe','admin'),
|
||||||
|
('34cdb084cad8','Probe','admin'),
|
||||||
|
('34cdb0901f10','Probe','admin'),
|
||||||
|
('34cdb090250c','Probe','admin'),
|
||||||
|
('34cdb084cae8','Probe','admin'),
|
||||||
|
('34cdb084caf4','Probe','admin'),
|
||||||
|
('34cdb084cb00','Probe','admin'),
|
||||||
|
('34cdb0902b60','Probe','admin'),
|
||||||
|
('34cdb08ff5c8','Probe','admin'),
|
||||||
|
('34cdb0901ee4','Probe','admin'),
|
||||||
|
('34cdb091f940','Probe','admin'),
|
||||||
|
('34cdb084cadc','Probe','admin'),
|
||||||
|
('34cdb08fffd8','Probe','admin'),
|
||||||
|
('34cdb084cb10','Probe','admin'),
|
||||||
|
('34cdb0901180','Probe','admin'),
|
||||||
|
('34cdb084cb14','Probe','admin'),
|
||||||
|
('34cdb084cb24','Probe','admin'),
|
||||||
|
('34cdb0902bb4','Probe','admin'),
|
||||||
|
('34cdb084cb20','Probe','admin'),
|
||||||
|
('34cdb09001ac','Probe','admin'),
|
||||||
|
('34cdb0910dc8','Probe','admin'),
|
||||||
|
('34cdb0902b08','Probe','admin'),
|
||||||
|
('34cdb0902c78','Probe','admin'),
|
||||||
|
('34cdb0901ba0','Probe','admin'),
|
||||||
|
('34cdb08ffd14','Probe','admin'),
|
||||||
|
('34cdb084cb40','Probe','admin'),
|
||||||
|
('34cdb08ffdb8','Probe','admin'),
|
||||||
|
('34cdb0901d58','Probe','admin'),
|
||||||
|
('34cdb084cb48','Probe','admin'),
|
||||||
|
('34cdb0902ff4','Probe','admin'),
|
||||||
|
('34cdb084cb28','Probe','admin'),
|
||||||
|
('34cdb08fffb4','Probe','admin'),
|
||||||
|
('34cdb0901d18','Probe','admin'),
|
||||||
|
('34cdb0900d90','Probe','admin'),
|
||||||
|
('34cdb08ffcb0','Probe','admin'),
|
||||||
|
('34cdb09022a4','Probe','admin'),
|
||||||
|
('34cdb0900434','Probe','admin'),
|
||||||
|
('34cdb091a93c','Probe','admin');
|
||||||
|
|
||||||
|
-- add node configuration
|
||||||
|
INSERT IGNORE INTO NodeConfiguration (node, author, measurement_interval_minutes, offset_from_midnight_minutes, wake_window_length_minutes) VALUES
|
||||||
|
('cc8da22c9090','admin',360,0,4),
|
||||||
|
('cc8da22b4600','admin',360,0,4),
|
||||||
|
('4827e28abc94','admin',360,0,4),
|
||||||
|
('48ca430f8d5c','admin',360,0,4),
|
||||||
|
('48ca430f9834','admin',360,0,4),
|
||||||
|
('34cdb084cac8','admin',360,0,4),
|
||||||
|
('34cdb08f9ddc','admin',360,0,4),
|
||||||
|
('34cdb08ff64c','admin',360,0,4),
|
||||||
|
('34cdb0900390','admin',360,0,4),
|
||||||
|
('34cdb08ff358','admin',360,0,4),
|
||||||
|
('34cdb0926c80','admin',360,0,4),
|
||||||
|
('34cdb084cad8','admin',360,0,4),
|
||||||
|
('34cdb0901f10','admin',360,0,4),
|
||||||
|
('34cdb090250c','admin',360,0,4),
|
||||||
|
('34cdb084cae8','admin',360,0,4),
|
||||||
|
('34cdb084caf4','admin',360,0,4),
|
||||||
|
('34cdb084cb00','admin',360,0,4),
|
||||||
|
('34cdb0902b60','admin',360,0,4),
|
||||||
|
('34cdb08ff5c8','admin',360,0,4),
|
||||||
|
('34cdb0901ee4','admin',360,0,4),
|
||||||
|
('34cdb091f940','admin',360,0,4),
|
||||||
|
('34cdb084cadc','admin',360,0,4),
|
||||||
|
('34cdb08fffd8','admin',360,0,4),
|
||||||
|
('34cdb084cb10','admin',360,0,4),
|
||||||
|
('34cdb0901180','admin',360,0,4),
|
||||||
|
('34cdb084cb14','admin',360,0,4),
|
||||||
|
('34cdb084cb24','admin',360,0,4),
|
||||||
|
('34cdb0902bb4','admin',360,0,4),
|
||||||
|
('34cdb084cb20','admin',360,0,4),
|
||||||
|
('34cdb09001ac','admin',360,0,4),
|
||||||
|
('34cdb0910dc8','admin',360,0,4),
|
||||||
|
('34cdb0902b08','admin',360,0,4),
|
||||||
|
('34cdb0902c78','admin',360,0,4),
|
||||||
|
('34cdb0901ba0','admin',360,0,4),
|
||||||
|
('34cdb08ffd14','admin',360,0,4),
|
||||||
|
('34cdb084cb40','admin',360,0,4),
|
||||||
|
('34cdb08ffdb8','admin',360,0,4),
|
||||||
|
('34cdb0901d58','admin',360,0,4),
|
||||||
|
('34cdb084cb48','admin',360,0,4),
|
||||||
|
('34cdb0902ff4','admin',360,0,4),
|
||||||
|
('34cdb084cb28','admin',360,0,4),
|
||||||
|
('34cdb08fffb4','admin',360,0,4),
|
||||||
|
('34cdb0901d18','admin',360,0,4),
|
||||||
|
('34cdb0900d90','admin',360,0,4),
|
||||||
|
('34cdb08ffcb0','admin',360,0,4),
|
||||||
|
('34cdb09022a4','admin',360,0,4),
|
||||||
|
('34cdb0900434','admin',360,0,4),
|
||||||
|
('34cdb091a93c','admin',360,0,4);
|
||||||
|
|
@ -0,0 +1,440 @@
|
||||||
|
const dgram = require('dgram')
|
||||||
|
const fs = require('fs')
|
||||||
|
const path = require('path')
|
||||||
|
const mysql = require("mysql2");
|
||||||
|
const cellularChunking = require('./cellularChunking')
|
||||||
|
const message_parser = require('./messages')
|
||||||
|
|
||||||
|
let thisDate = new Date().toISOString().slice(0,10)
|
||||||
|
const filePath = './records'
|
||||||
|
let message_id = 0
|
||||||
|
|
||||||
|
let sentFiles = true
|
||||||
|
|
||||||
|
let dataBuffer = []
|
||||||
|
const response_record = {}
|
||||||
|
const settings = {}
|
||||||
|
|
||||||
|
const pool = createPool()
|
||||||
|
|
||||||
|
if(!(fs.existsSync(filePath) && fs.lstatSync(filePath).isDirectory())) {
|
||||||
|
fs.mkdirSync(filePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
function createPool() {
|
||||||
|
const pool = mysql.createPool({
|
||||||
|
host: process.env.db_host,
|
||||||
|
user: process.env.db_user,
|
||||||
|
password: process.env.db_password,
|
||||||
|
database: process.env.db_database,
|
||||||
|
port: process.env.db_port,
|
||||||
|
timezone: '+00:00'
|
||||||
|
});
|
||||||
|
pool.getConnection(function(err,connection) {
|
||||||
|
if (err) {
|
||||||
|
console.log('error setting the database')
|
||||||
|
} else {
|
||||||
|
connection.execute(
|
||||||
|
"USE Chickens;",
|
||||||
|
[],
|
||||||
|
function(err2, results) {
|
||||||
|
if(err2) {
|
||||||
|
console.log(`Some other error setting database: ${err2}`)
|
||||||
|
connection.release()
|
||||||
|
} else {
|
||||||
|
connection.execute(
|
||||||
|
"SELECT Node2Group.node,Node2Group.node_group,NodeConfiguration.measurement_interval_minutes,NodeConfiguration.offset_from_midnight_minutes,NodeConfiguration.wake_window_length_minutes FROM GroupType JOIN Group2Type ON GroupType.name=Group2Type.type JOIN Node2Group ON Group2Type.group_name=Node2Group.node_group JOIN NodeConfiguration ON NodeConfiguration.node=Node2Group.node WHERE Group2Type.type = 'Temperature Site';",
|
||||||
|
[],
|
||||||
|
function (err2, results) {
|
||||||
|
if (err2) {
|
||||||
|
console.log(`Some other error getting node settings: ${err2}`)
|
||||||
|
connection.release()
|
||||||
|
} else {
|
||||||
|
results.forEach(function(thisRow) {
|
||||||
|
settings[thisRow.node] = {
|
||||||
|
measurement_interval: thisRow.measurement_interval_minutes,
|
||||||
|
measurement_offset: thisRow.offset_from_midnight_minutes,
|
||||||
|
wake_window_length: thisRow.wake_window_length_minutes,
|
||||||
|
temperature_site: thisRow.node_group
|
||||||
|
}
|
||||||
|
})
|
||||||
|
connection.release()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return pool
|
||||||
|
}
|
||||||
|
|
||||||
|
const socket = dgram.createSocket('udp4');
|
||||||
|
let stream = fs.createWriteStream(`${filePath}/${thisDate}-log.dat`, {flags: 'a+'})
|
||||||
|
|
||||||
|
function make_string_string_message(msg_type, version, source, target, key, value, this_message_id) {
|
||||||
|
theHeader = make_message_header(msg_type, version, source, target, this_message_id)
|
||||||
|
console.log('message header: ', theHeader)
|
||||||
|
theData = Buffer.alloc(key.length + value.length + 3)
|
||||||
|
theData.write(key,0)
|
||||||
|
theData.writeUInt8(255, key.length)
|
||||||
|
theData.write(value, key.length+1)
|
||||||
|
theData.writeUInt8(255, key.length+value.length+2)
|
||||||
|
return Buffer.concat([theHeader, theData, Buffer.from([0])])
|
||||||
|
}
|
||||||
|
|
||||||
|
function make_message_header(msg_type, version, source, target, this_message_id) {
|
||||||
|
target = ('0000000000000000' + target).slice(-16)
|
||||||
|
target_buf = Buffer.from(target, 'hex')
|
||||||
|
source = (source = '0000000000000000' + source).slice(-16)
|
||||||
|
source_buf = Buffer.from(source, 'hex')
|
||||||
|
timestamp = Math.floor(Date.now() / 1000) - 946684800 // timestamp in the format that the ESP32 expects it, seconds since 2000-01-01
|
||||||
|
timestamp_buf = Buffer.alloc(4)
|
||||||
|
timestamp_buf.writeUInt32BE(timestamp)
|
||||||
|
buf = Buffer.alloc(4)
|
||||||
|
buf.writeUInt16BE(this_message_id, 0)
|
||||||
|
buf.writeUInt8(msg_type, 2)
|
||||||
|
buf.writeUInt8(version, 3)
|
||||||
|
header = Buffer.concat([buf, source_buf, target_buf, timestamp_buf])
|
||||||
|
return header
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendFile(socket, rinfo, thisFilePath, fileList) {
|
||||||
|
theFilePath = path.join(thisFilePath, fileList[0])
|
||||||
|
fs.readFile(theFilePath, 'utf-8', (err,data) => {
|
||||||
|
console.log('have read file')
|
||||||
|
if(err) {
|
||||||
|
console.log(err)
|
||||||
|
} else {
|
||||||
|
console.log('make the message to send')
|
||||||
|
const theMsg = Buffer.from(make_string_string_message(8, 1, '00', '4827e28f7778', fileList[0], data, 537)).toString('hex')
|
||||||
|
console.log('chunk the message')
|
||||||
|
const theRespChunks = cellularChunking.chunk_message(theMsg, message_id)
|
||||||
|
console.log('have chunks')
|
||||||
|
thisSend(theRespChunks, socket, rinfo, thisFilePath, fileList)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleMessage(message, rinfo) {
|
||||||
|
console.log('store message: ', message)
|
||||||
|
|
||||||
|
// this is going to be for storing the new format for the data
|
||||||
|
// this needs to be set up to handle different message types differently, or it could be done when the databuffer is storedu
|
||||||
|
console.log(new Date().toISOString(), ' store data 2 route ', Buffer.from(message, 'hex').toString('hex'))
|
||||||
|
const parsed_data = message_parser.parse_messages({data: message})
|
||||||
|
let settingsString = Buffer.from([])
|
||||||
|
//const seenIDs = []
|
||||||
|
parsed_data.forEach(function(thisData) {
|
||||||
|
if(thisData.source.replaceAll('0', '') === '' || thisData.source.startsWith('f') || thisData.source.startsWith('8')) {
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
console.log('parsed data: ', thisData)
|
||||||
|
}
|
||||||
|
dataBuffer.push(thisData)
|
||||||
|
if((!response_record[thisData.source]) || response_record[thisData.source] + 1000 * 600 < Date.now()) { // respond at moch once every 600 seconds to any single node
|
||||||
|
console.log("respond with settings")
|
||||||
|
//settingsString = Buffer.concat([settingsString, makeSettingsString(thisData)])
|
||||||
|
response_record[thisData.source] = Date.now()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if(settingsString.length > 0) {
|
||||||
|
console.log('settingsString: ', settingsString.toString('hex'))
|
||||||
|
message_id = message_id+1
|
||||||
|
stream.write('< ' + (new Date().toISOString()) + ' ' + settingsString.toString('hex') + '\n')
|
||||||
|
// breaking in here so we can test sending a file
|
||||||
|
const theRespChunks = cellularChunking.chunk_message(settingsString, message_id)
|
||||||
|
console.log('msg chunks: ', theRespChunks)
|
||||||
|
theRespChunks.forEach(function(thisChunk) {
|
||||||
|
console.log('thisChunk: ', thisChunk)
|
||||||
|
socket.send(thisChunk, rinfo.port, rinfo.address, (err) => {
|
||||||
|
if(err) {
|
||||||
|
console.log(err)
|
||||||
|
} else {
|
||||||
|
console.log('sent message on')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if(!sentFiles) {
|
||||||
|
console.log('send files')
|
||||||
|
sentFiles = true
|
||||||
|
const thiFilePath = './firmware/Components'
|
||||||
|
fs.readdir(thiFilePath, (err, theFileList) => {
|
||||||
|
console.log(theFileList)
|
||||||
|
if(err) {
|
||||||
|
console.log(err)
|
||||||
|
} else {
|
||||||
|
sendFile(socket, rinfo, thiFilePath, theFileList)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeSettingsString(msgData) {
|
||||||
|
const device_id = msgData.device_id
|
||||||
|
msgData.version = msgData.version || 1
|
||||||
|
// make a message with all the info in it
|
||||||
|
const location_id = status.devices[device_id]?.location_id
|
||||||
|
const site_id = status.locations[location_id]?.site_id || '7d931bd4-bf0d-459f-8864-6d6405908b9e'
|
||||||
|
const timezoneOffset = getOffset(status.sites[site_id]?.timezone || 'UTC')
|
||||||
|
const measurementInterval = status.settings[status.settings_groups[device_id]]?.sleep_interval || '00:30:00'
|
||||||
|
const wakeWindowLength = status.settings[status.settings_groups[device_id]]?.window_length || '00:06:00'
|
||||||
|
const measurementOffset = status.settings[status.settings_groups[device_id]]?.start_time || '00:00:00'
|
||||||
|
msg = Buffer.from([])
|
||||||
|
settings = {
|
||||||
|
'\x4B': [timezoneOffset < 0 ? 256 - timezoneOffset : timezoneOffset],
|
||||||
|
}
|
||||||
|
|
||||||
|
msg = message_parser.make_two_byte_data_message(3, msgData.version, '00', device_id, settings)
|
||||||
|
settings2 = {
|
||||||
|
'\x01': status.sites[site_id]?.name || 'Unregistered',
|
||||||
|
'\x02': status.devices[device_id]?.name || 'Unregistered'
|
||||||
|
}
|
||||||
|
msg = Buffer.concat([msg, message_parser.make_byte_string_message(7, msgData.version, '00', device_id, settings2)])
|
||||||
|
settings3 = {
|
||||||
|
'\x03': [sqlTimeToNumber(measurementInterval)],
|
||||||
|
'\x04': [sqlTimeToNumber(measurementOffset)],
|
||||||
|
'\x05': [sqlTimeToNumber(wakeWindowLength)],
|
||||||
|
}
|
||||||
|
msg = Buffer.concat([msg, message_parser.make_four_byte_data_message(4, msgData.version, '00', device_id, settings3)])
|
||||||
|
return msg
|
||||||
|
}
|
||||||
|
|
||||||
|
function thisSend(theChunks, socket, rinfo, thisFilePath, fileList) {
|
||||||
|
socket.send(theChunks[0], rinfo.port, rinfo.address, (err) => {
|
||||||
|
if(err) {
|
||||||
|
console.log(err)
|
||||||
|
} else {
|
||||||
|
console.log("send chunk")
|
||||||
|
if (theChunks.length > 1) {
|
||||||
|
setTimeout(thisSend, 1000, theChunks.slice(1), socket, rinfo, thisFilePath, fileList)
|
||||||
|
} else if (fileList.length > 1) {
|
||||||
|
sendFile(socket, rinfo, thisFilePath, fileList.slice(1))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function sqlTimeToNumber(theTime) {
|
||||||
|
try {
|
||||||
|
const parts = theTime.split(':')
|
||||||
|
timeOut = Number(parts[0]) * 60 * 60 + Number(parts[1]) * 60 + Number(parts[2])
|
||||||
|
if(timeOut > 2**16) {
|
||||||
|
timeOut = 2**16-1
|
||||||
|
}
|
||||||
|
return timeOut
|
||||||
|
} catch (e) {
|
||||||
|
if(theTime !== null) {
|
||||||
|
console.log('sqlTimeToNumber error: ', e)
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function storeType1Messages(msgs) { // 2 byte measurement data
|
||||||
|
try {
|
||||||
|
if(msgs.length === 0) {
|
||||||
|
return
|
||||||
|
} else if(msgs.length > 2500) { // you can't do more than 65k entries in a paramaterized sql statement, there are 17 things per message, 65000 / 17 = 3823.529..., use 2500 to have a big margin
|
||||||
|
// you could have thousands of measurement messages if a bunch of probes are sending back data and there is a large deployment. It is 20 nodes over about a month.
|
||||||
|
const num_batches = Math.floor(msgs.length / 2500)
|
||||||
|
for (let i = 0; i < num_batches; i++) {
|
||||||
|
storeType1Messages(msgs.slice(i * 2500, (i+1) * 2500))
|
||||||
|
}
|
||||||
|
storeType1Messages(msgs.slice(-1 * (msgs.length % 2500)))
|
||||||
|
} else {
|
||||||
|
let theseParams = []
|
||||||
|
msgs.forEach(function(msg) {
|
||||||
|
theseParams.push(msg.source.slice(4),msg.reporting_node || "000000000000",settings[msg.source.slice(4)].temperature_site,msg.timestamp,msg.data["18_inch_temperature"] || 500000,msg.data["36_inch_temperature"] || 500000,msg.data.device_temperature || 500000,msg.data.ambient_temperature || 500000,msg.data.relative_humidity || 500000,msg.data.barometric_pressure || 500000,msg.data.accelerometer_x || 500000,msg.data.accelerometer_y || 500000,msg.data.accelerometer_z || 500000,msg.data.battery_charge_percent || 500000,msg.data.battery_voltage || 500000,msg.data.remaining_charge_capacity || 500000)
|
||||||
|
})
|
||||||
|
const thisSQL = "INSERT IGNORE INTO Measurement (source_node, reporting_node, associated_group, collection_time, temperature_18_inch, temperature_36_inch, device_temperature, ambient_temperature, relative_humidity, barometric_pressure, accelerometer_x, accelerometer_y, accelerometer_z, battery_charge_percent, battery_voltage, remaining_battery_capacity) VALUES " + "(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?),".repeat(msgs.length).slice(0,-1) + ";"
|
||||||
|
pool.getConnection(
|
||||||
|
function(err, connection) {
|
||||||
|
if(err) {
|
||||||
|
console.log(`Error storing type 1 message: ${err}`)
|
||||||
|
} else {
|
||||||
|
connection.execute(
|
||||||
|
thisSQL,
|
||||||
|
theseParams,
|
||||||
|
function(err2, results) {
|
||||||
|
if(err2) {
|
||||||
|
console.log(`Some other error storing type 1 message: ${err2}`)
|
||||||
|
connection.release()
|
||||||
|
} else {
|
||||||
|
connection.release()
|
||||||
|
// TODO: anything here?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.log(`Some outer error storing type 1 message: ${e}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function storeType6Messages(msgs) { // four byte device status
|
||||||
|
try {
|
||||||
|
let theseParams = []
|
||||||
|
let n = msgs.length
|
||||||
|
msgs.forEach(function(msg) {
|
||||||
|
theseParams.push(msg.source.slice(4),msg.timestamp,msg.data.measurement_interval || 360,msg.data.wake_window_length || 4,msg.data.measurement_offset || 0,msg.data.sleep_duration || 0,0,new Date(msg.data.previous_update_time * 1000 || 0 + 946684800000).toISOString().slice(0,-5))
|
||||||
|
})
|
||||||
|
const thisSQL = "INSERT IGNORE INTO NodeReportedStatus (node,collection_time,measurement_interval_minutes,wake_window_length_minutes,offset_from_midnight_minutes,sleep_duration_minutes,number_saved_measurements,when_time_was_last_updated) VALUES " + "(?,?,?,?,?,?,?,?),".repeat(n).slice(0,-1) + ";"
|
||||||
|
pool.getConnection(
|
||||||
|
function(err, connection) {
|
||||||
|
if(err) {
|
||||||
|
console.log(`Error storing type 6 message: ${err}`)
|
||||||
|
} else {
|
||||||
|
connection.execute(
|
||||||
|
thisSQL,
|
||||||
|
theseParams,
|
||||||
|
function(err2, results) {
|
||||||
|
if(err2) {
|
||||||
|
console.log(`Some other error storing type 6 message: ${err2}`)
|
||||||
|
connection.release()
|
||||||
|
} else {
|
||||||
|
connection.release()
|
||||||
|
// TODO: anything here?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
} catch (e) {
|
||||||
|
console.log(`Some outer error storing type 6 message: ${e}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function storeType13Messages(msgs) { // file version manifest
|
||||||
|
try {
|
||||||
|
let theseParams = []
|
||||||
|
let n = 0
|
||||||
|
msgs.forEach(function(msg) {
|
||||||
|
Object.keys(msg.data).forEach(function(thisFileId) {
|
||||||
|
n += 1
|
||||||
|
theseParams.push(msg.source.slice(4),msg.timestamp,thisFileId,msg.data[thisFileId] || 0)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
const thisSQL = "INSERT INTO NodeFileManifest (node,collection_time,program_id,program_version) VALUES " + "(?,?,?,?),".repeat(n).slice(0,-1) + ";"
|
||||||
|
pool.getConnection(
|
||||||
|
function(err, connection) {
|
||||||
|
if(err) {
|
||||||
|
console.log(`Error storing type 13 message: ${err}`)
|
||||||
|
} else {
|
||||||
|
connection.execute(
|
||||||
|
thisSQL,
|
||||||
|
theseParams,
|
||||||
|
function(err2, results) {
|
||||||
|
if(err2) {
|
||||||
|
console.log(`Some other error storing type 13 message: ${err}`)
|
||||||
|
connection.release()
|
||||||
|
} else {
|
||||||
|
connection.release()
|
||||||
|
// TODO: anything here?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
} catch (e) {
|
||||||
|
console.log(`Some outer error storing type 13 message: ${e}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function storeType17Messages(msgs) { // rssi data
|
||||||
|
try {
|
||||||
|
let theseParams = []
|
||||||
|
let n = 0
|
||||||
|
msgs.forEach(function(msg) {
|
||||||
|
Object.keys(msg.data).forEach(function(thisId) {
|
||||||
|
n += 1
|
||||||
|
theseParams.push(msg.source.slice(4),thisId,msg.timestamp, msg.data[thisId] || 0)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
const thisSQL = "INSERT IGNORE INTO NodeRssiRecord (node,neighbor,collection_time,rssi) VALUES " + "(?,?,?,?),".repeat(n).slice(0,-1) + ";"
|
||||||
|
pool.getConnection(
|
||||||
|
function(err, connection) {
|
||||||
|
if(err) {
|
||||||
|
console.log(`Error storing type 17 message: ${err}`)
|
||||||
|
} else {
|
||||||
|
connection.execute(
|
||||||
|
thisSQL,
|
||||||
|
theseParams,
|
||||||
|
function(err2, results) {
|
||||||
|
if(err2) {
|
||||||
|
console.log(`Some other error storing type 17 message: ${err2}`)
|
||||||
|
connection.release()
|
||||||
|
} else {
|
||||||
|
connection.release()
|
||||||
|
// TODO: anything here?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
} catch (e) {
|
||||||
|
console.log(`Some outer error storing type 17 message: ${e}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function storeMessages() {
|
||||||
|
console.log('store messages!!')
|
||||||
|
try {
|
||||||
|
const type1Messages = dataBuffer.filter(function (thisMsg) { return thisMsg.msg_type == 1 })
|
||||||
|
const type6Messages = dataBuffer.filter(function (thisMsg) { return thisMsg.msg_type == 6 })
|
||||||
|
const type13Messages = dataBuffer.filter(function (thisMsg) { return thisMsg.msg_type == 13 })
|
||||||
|
const type17Messages = dataBuffer.filter(function (thisMsg) { return thisMsg.msg_type == 17 })
|
||||||
|
// store the data in the database
|
||||||
|
// check message type, then store it based on that
|
||||||
|
if (type1Messages.length > 0) {
|
||||||
|
console.log("type 1 messages!")
|
||||||
|
storeType1Messages(type1Messages)
|
||||||
|
}
|
||||||
|
if (type6Messages.length > 0) {
|
||||||
|
console.log("type 6 messages!")
|
||||||
|
storeType6Messages(type6Messages)
|
||||||
|
}
|
||||||
|
if (type13Messages.length > 0) {
|
||||||
|
console.log("type 13 messages!")
|
||||||
|
storeType13Messages(type13Messages)
|
||||||
|
}
|
||||||
|
if (type17Messages.length > 0) {
|
||||||
|
console.log("type 17 messages!")
|
||||||
|
storeType17Messages(type17Messages)
|
||||||
|
}
|
||||||
|
dataBuffer = []
|
||||||
|
} catch (e) {
|
||||||
|
console.log(`Some error storing messages: ${e}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setInterval(storeMessages, 10000) // store messages every minute
|
||||||
|
|
||||||
|
socket.on('listening', () => {
|
||||||
|
let addr = socket.address();
|
||||||
|
console.log(`Listening for UDP packets at ${addr.address}:${addr.port}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('error', (err) => {
|
||||||
|
console.error(`UDP error: ${err.stack}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('message', (msg, rinfo) => {
|
||||||
|
if(thisDate !== new Date().toISOString().slice(0,10)) {
|
||||||
|
thisDate = new Date().toISOString().slice(0,10)
|
||||||
|
stream.close()
|
||||||
|
stream = fs.createWriteStream(`${filePath}/${thisDate}-log.dat`, {flags: 'a+'})
|
||||||
|
}
|
||||||
|
stream.write('> ' + (new Date().toISOString()) + ' ' + msg.toString('hex') + '\n')
|
||||||
|
cellularChunking.receive_chunk(msg, handleMessage, rinfo)
|
||||||
|
})
|
||||||
|
socket.bind(57321);
|
||||||
|
|
@ -0,0 +1,773 @@
|
||||||
|
(function() {
|
||||||
|
|
||||||
|
let message_id = 0
|
||||||
|
|
||||||
|
function increment_message_id() {
|
||||||
|
message_id = message_id + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
const twoByteDataUnits = {
|
||||||
|
0: "",
|
||||||
|
1: "°C",
|
||||||
|
2: "°C",
|
||||||
|
3: "°C",
|
||||||
|
4: "hPa",
|
||||||
|
5: "%rh",
|
||||||
|
6: "",
|
||||||
|
7: "lux",
|
||||||
|
8: "",
|
||||||
|
9: "",
|
||||||
|
10: "mg",
|
||||||
|
11: "mg",
|
||||||
|
12: "mg",
|
||||||
|
13: "°/s",
|
||||||
|
14: "°/s",
|
||||||
|
15: "°/s",
|
||||||
|
16: "nT",
|
||||||
|
17: "nT",
|
||||||
|
18: "nT",
|
||||||
|
19: "nT",
|
||||||
|
20: "%",
|
||||||
|
21: "V",
|
||||||
|
22: "mA",
|
||||||
|
23: "m/s",
|
||||||
|
24: "",
|
||||||
|
25: "mm",
|
||||||
|
26: "mm/h",
|
||||||
|
27: "",
|
||||||
|
28: "",
|
||||||
|
29: "",
|
||||||
|
30: "",
|
||||||
|
31: "clicks",
|
||||||
|
32: "",
|
||||||
|
33: "",
|
||||||
|
34: "",
|
||||||
|
35: "",
|
||||||
|
36: "°",
|
||||||
|
37: "°",
|
||||||
|
38: "m",
|
||||||
|
39: "m/s",
|
||||||
|
40: "°",
|
||||||
|
41: "°",
|
||||||
|
42: "°",
|
||||||
|
43: "ppm",
|
||||||
|
44: "ppm",
|
||||||
|
45: "ppm",
|
||||||
|
46: "ppm",
|
||||||
|
47: "ppm",
|
||||||
|
48: "mAh",
|
||||||
|
49: "mA",
|
||||||
|
50: "mA",
|
||||||
|
51: "mA",
|
||||||
|
52: "mW",
|
||||||
|
53: "mAh",
|
||||||
|
54: "",
|
||||||
|
55: "",
|
||||||
|
56: "",
|
||||||
|
57: "",
|
||||||
|
58: "",
|
||||||
|
59: "",
|
||||||
|
60: "",
|
||||||
|
61: "s",
|
||||||
|
62: "",
|
||||||
|
63: "",
|
||||||
|
64: "",
|
||||||
|
65: "",
|
||||||
|
66: "",
|
||||||
|
67: "",
|
||||||
|
68: "",
|
||||||
|
69: "",
|
||||||
|
70: "",
|
||||||
|
71: "",
|
||||||
|
72: "",
|
||||||
|
73: "V",
|
||||||
|
74: "",
|
||||||
|
75: "h",
|
||||||
|
76: "",
|
||||||
|
77: "",
|
||||||
|
78: "s",
|
||||||
|
79: "",
|
||||||
|
80: "°C",
|
||||||
|
81: "°C",
|
||||||
|
82: "",
|
||||||
|
83: " nodes",
|
||||||
|
84: " nodes",
|
||||||
|
85: " nodes",
|
||||||
|
}
|
||||||
|
|
||||||
|
const twoByteDataTypesDict = {
|
||||||
|
0: "end", // end of measurement data
|
||||||
|
1: "probe_temperature", // temperature probe temperature
|
||||||
|
2: "ambient_temperature", // ambient air temperature
|
||||||
|
3: "device_temperature", // device controller temperature
|
||||||
|
4: "barometric_pressure", // barometric pressure
|
||||||
|
5: "relative_humidity", //relative humidity of the air
|
||||||
|
6: "soil_moisture", // soil moisture sensor
|
||||||
|
7: "lux", // ambient light intensity (measured in lux)
|
||||||
|
8: "visible", // visible light measurement (meassured in ??)
|
||||||
|
9: "ir", // ir light measurement (same as visible light, but ir)
|
||||||
|
10: "accelerometer_x",//accelerometer x-axis data
|
||||||
|
11: "accelerometer_y",//accelerometer y-axis data
|
||||||
|
12: "accelerometer_z", //accelerometer z-axis data
|
||||||
|
13: "gyroscope_x",//gyroscope x-axis data
|
||||||
|
14: "gyroscope_y",//gyroscope y-axis data
|
||||||
|
15: "gyroscope_z",//gyroscope z-axis data
|
||||||
|
16: "magnetometer_x",//magnetometer x-axis data
|
||||||
|
17: "magnetometer_y",//magnetometer y-axis data
|
||||||
|
18: "magnetometer_z",//magnetometer z-axis data
|
||||||
|
19: "magnetic_intensiy", //hall effect intensity data
|
||||||
|
20: "battery_charge_percent", //battery charge level (%)
|
||||||
|
21: "battery_voltage", //voltage
|
||||||
|
22: "current", //current
|
||||||
|
23: "wind_speed", //wind speed
|
||||||
|
24: "wind_direction", //wind direction
|
||||||
|
25: "absolute_rainfall", //absolute rainfall (i.e. XX inches)
|
||||||
|
26: "rainfall_rate", //rate of rainfall (i.e. XX inches/minute)
|
||||||
|
27: "rssi", //RSSI (wifi signal strength)
|
||||||
|
28: "number_of_peer", //number of known peers
|
||||||
|
29: "number_of_saved_measurements", //number of saved measurements
|
||||||
|
30: "rtc_data_used", //amount of RTC data storage used
|
||||||
|
31: "clicks", //click counter (so how many times a discrete on/off sensor has been triggered, like a hall effect switch)
|
||||||
|
32: "rot_m1", //motor1 rotation
|
||||||
|
33: "rot_m2", //motor2 rotation
|
||||||
|
34: "rot_m3", //motor3 rotation
|
||||||
|
35: "rot_m4", //motor4 rotation
|
||||||
|
36: "lat", //latitude
|
||||||
|
37: "lon", //longitude
|
||||||
|
38: "ele", //elevation
|
||||||
|
39: "speed", //speed
|
||||||
|
40: "heading", //tilt-adjusted compass heading (yaw)
|
||||||
|
41: "inclination",//inclination (pitch)
|
||||||
|
42: "roll",//roll (roll)
|
||||||
|
43: "ammonia",//ammonia/nh3
|
||||||
|
44: "co", //CO
|
||||||
|
45: "co2",//CO2
|
||||||
|
46: "h2s",//H2S
|
||||||
|
47: "ch4",//CH4
|
||||||
|
48: "remaining_battery_capacity", // Remaining Battery Capacity (mAh
|
||||||
|
49: "average_current", // Average Current
|
||||||
|
50: "standby_current", // Standby Current
|
||||||
|
51: "max_current", // Max Current
|
||||||
|
52: "average_power", // Average Power
|
||||||
|
53: "full_available_capacity", // Full Available Capacity
|
||||||
|
54: "boot_ver", // the verion of boot.py
|
||||||
|
55: "program_ver", // the program.py version
|
||||||
|
56: "epaper_ver", // the epaper version
|
||||||
|
57: "mesh_ver", // the mesh code version
|
||||||
|
58: "bq27441_ver", // the bq27441 code version
|
||||||
|
59: "rtc_mem_ver", // the RTCMem.py version
|
||||||
|
60: "request_type", // for control messages, things that want a response from the base station, or a forwarded message
|
||||||
|
61: "measurement_interval", // non-battery measured voltage,
|
||||||
|
62: "measurement_alignment",
|
||||||
|
63: "scheduled_time_1",
|
||||||
|
64: "scheduled_time_2",
|
||||||
|
65: "scheduled_time_3",
|
||||||
|
66: "scheduled_time_4",
|
||||||
|
67: "scheduled_time_5",
|
||||||
|
68: "scheduled_time_6",
|
||||||
|
69: "scheduled_time_7",
|
||||||
|
70: "scheduled_time_8",
|
||||||
|
71: "scheduled_time_9",
|
||||||
|
72: "scheduled_time_10",
|
||||||
|
73: "voltage",
|
||||||
|
74: "measurement_version",
|
||||||
|
75: "time_zone_offset",
|
||||||
|
76: "message_parser_version",
|
||||||
|
77: "settings_type",
|
||||||
|
78: "wake_window_length",
|
||||||
|
79: "hop_distance", // the number of hops between the node and the base station
|
||||||
|
80: "18_inch_temperature", // a temperature probe at 18 inch depth
|
||||||
|
81: "36_inch_temperature", // a temperature probe at 36 inch depth
|
||||||
|
82: "forwarder", // if the node is a forwarder during this interval
|
||||||
|
83: "closer_nodes",
|
||||||
|
84: "same_nodes",
|
||||||
|
85: "farther_nodes",
|
||||||
|
}
|
||||||
|
|
||||||
|
// handler funcitons for each type of data
|
||||||
|
const twoByteDataConversionFunctions = {
|
||||||
|
0: noop, //"end", // end of measurement data
|
||||||
|
1: arrayToSignedDividedDecimal, //"probe_temp", // temperature probe temperature
|
||||||
|
2: arrayToSignedDividedDecimal, //"ambient_temp", // ambient air temperature
|
||||||
|
3: arrayToSignedDividedDecimal, //"device_temp", // device controller temperature
|
||||||
|
4: arrayToDividedDecimal, //"bara_pressure", // barometric pressure
|
||||||
|
5: arrayToDividedDecimal, //"rh", //relative humidity of the air
|
||||||
|
6: arrayToDividedDecimal, //"soil_moisture", // soil moisture sensor
|
||||||
|
7: arrayToDecimal, //"lux", // ambient light intensity (measured in lux)
|
||||||
|
8: arrayToDecimal, //"visible", // visible light measurement (meassured in ??)
|
||||||
|
9: arrayToDecimal, //"ir", // ir light measurement (same as visible light, but ir)
|
||||||
|
10: arrayToDecimal, //"acc_x",//accelerometer x-axis data
|
||||||
|
11: arrayToDecimal, //"acc_y",//accelerometer y-axis data
|
||||||
|
12: arrayToDecimal, //"acc_z", //accelerometer z-axis data
|
||||||
|
13: arrayToDecimal, //"gyro_x",//gyroscope x-axis data
|
||||||
|
14: arrayToDecimal, //"gyro_y",//gyroscope y-axis data
|
||||||
|
15: arrayToDecimal, //"gyro_z",//gyroscope z-axis data
|
||||||
|
16: arrayToDecimal, //"mag_x",//magnetometer x-axis data
|
||||||
|
17: arrayToDecimal, //"mag_y",//magnetometer y-axis data
|
||||||
|
18: arrayToDecimal, //"mag_z",//magnetometer z-axis data
|
||||||
|
19: arrayToDecimal, //"hall", //hall effect intensity data
|
||||||
|
20: arrayToDecimal, //"batt", //battery charge level (%)
|
||||||
|
21: arrayToDividedDecimal, //"batt_volt", //voltage
|
||||||
|
22: arrayToDecimal, //"amps", //current
|
||||||
|
23: arrayToDividedDecimal, //"wind_s", //wind speed
|
||||||
|
24: arrayToDividedDecimal, //"wind_d", //wind direction
|
||||||
|
25: arrayToDividedDecimal, //"rain", //absolute rainfall (i.e. XX inches)
|
||||||
|
26: arrayToDividedDecimal, //"rain_rate", //rate of rainfall (i.e. XX inches/minute)
|
||||||
|
27: arrayToDividedDecimal, //"rssi", //RSSI (wifi signal strength)
|
||||||
|
28: arrayToDividedDecimal, //"num_peer", //number of known peers
|
||||||
|
29: arrayToDecimal, //"num_meas", //number of saved measurements
|
||||||
|
30: arrayToDecimal, //"rtc_dat", //amount of RTC data storage used
|
||||||
|
31: arrayToDecimal, //"clicks", //click counter (so how many times a discrete on/off sensor has been triggered, like a hall effect switch)
|
||||||
|
32: arrayToDecimal, //"rot_m1", //motor1 rotation
|
||||||
|
33: arrayToDecimal, //"rot_m2", //motor2 rotation
|
||||||
|
34: arrayToDecimal, //"rot_m3", //motor3 rotation
|
||||||
|
35: arrayToDecimal, //"rot_m4", //motor4 rotation
|
||||||
|
36: arrayToSignedDecimal, //"lat", //latitude
|
||||||
|
37: arrayToSignedDecimal, //"lon", //longitude
|
||||||
|
38: arrayToDividedDecimal, //"ele", //elevation
|
||||||
|
39: arrayToDividedDecimal, //"speed", //speed
|
||||||
|
40: arrayToDividedDecimal, //"heading", //tilt-adjusted compass heading (yaw)
|
||||||
|
41: arrayToDividedDecimal, //"pitch",//inclination (pitch)
|
||||||
|
42: arrayToDividedDecimal, //"roll",//roll (roll)
|
||||||
|
43: arrayToDividedDecimal, //"nh3",//ammonia
|
||||||
|
44: arrayToDividedDecimal, //"co", //CO
|
||||||
|
45: arrayToDividedDecimal, //"co2",//CO2
|
||||||
|
46: arrayToDividedDecimal, //"h2s",//H2S
|
||||||
|
47: arrayToDividedDecimal, //"ch4",//CH4
|
||||||
|
48: arrayToDecimal, //"remCap", // Remaining Battery Capacity (mAh
|
||||||
|
49: arrayToDividedDecimal, //"avgCur", // Average Current
|
||||||
|
50: arrayToDividedDecimal, //"sbc", // Standby Current
|
||||||
|
51: arrayToDividedDecimal, //"mc", // Max Current
|
||||||
|
52: arrayToDividedDecimal, //"ap", // Average Power
|
||||||
|
53: arrayToDividedDecimal, //"fac", // Full Available Capacity
|
||||||
|
54: arrayToDecimal, //"boot_ver", // the verion of boot.py
|
||||||
|
55: arrayToDecimal, //"prog_ver", // the program.py version
|
||||||
|
56: arrayToDecimal, //"ep_ver", // the epaper version
|
||||||
|
57: arrayToDecimal, //"mesh_ver", // the mesh code version
|
||||||
|
58: arrayToDecimal, //"bq_ver", // the bq27441 code version
|
||||||
|
59: arrayToDecimal, //"rtc_mem_ver", // the RTCMem.py version
|
||||||
|
60: noop, //"request_type", // for control messages, things that want a response from the base station, or a forwarded message
|
||||||
|
61: arrayToDecimal, //"measurement_interval" // time between measurements
|
||||||
|
62: arrayToTime, // measurement time alignment
|
||||||
|
63: arrayToTime, // measurement time 1
|
||||||
|
64: arrayToTime, // measurement time 2
|
||||||
|
65: arrayToTime, // measurement time 3
|
||||||
|
66: arrayToTime, // measurement time 4
|
||||||
|
67: arrayToTime, // measurement time 5
|
||||||
|
68: arrayToTime, // measurement time 6
|
||||||
|
69: arrayToTime, // measurement time 7
|
||||||
|
70: arrayToTime, // measurement time 8
|
||||||
|
71: arrayToTime, // measurement time 9
|
||||||
|
72: arrayToTime, // measurement time 10
|
||||||
|
73: arrayToDividedDecimal, // measured voltage
|
||||||
|
74: noop, // version (deprecated)
|
||||||
|
75: arrayToSignedDecimal, // time zone offset
|
||||||
|
76: arrayToDecimal, // message parser version
|
||||||
|
77: arrayToDecimal, // settings type
|
||||||
|
78: arrayToDecimal, // wake window length
|
||||||
|
79: arrayToDecimal, // distance in hops from node to base station
|
||||||
|
80: arrayToDividedDecimal, // 18 inch probe temperature
|
||||||
|
81: arrayToDividedDecimal, // 36 inch probe temperature
|
||||||
|
82: toBoolean, // forwarder node
|
||||||
|
83: arrayToDecimal, // closer nodes
|
||||||
|
84: arrayToDecimal, // same nodes
|
||||||
|
85: arrayToDecimal, // father nodes
|
||||||
|
}
|
||||||
|
|
||||||
|
const fourByteDataTypesDict = {
|
||||||
|
1: "time_drift", // the time error for the node
|
||||||
|
2: "sleep_duration",
|
||||||
|
3: "measurement_interval",
|
||||||
|
4: "measurement_offset",
|
||||||
|
5: "wake_window_length",
|
||||||
|
6: "previous_update_time", // the most recent time the device set its time
|
||||||
|
7: "base_station_rssi", // the RSSI at the skewer for a message from the base station
|
||||||
|
8: "last_successful_report", // the last time that the server received data from the base station or probe
|
||||||
|
}
|
||||||
|
|
||||||
|
const fourByteDataConversionFunctions = {
|
||||||
|
1: arrayToSignedDecimal,
|
||||||
|
2: arrayToDecimal,
|
||||||
|
3: arrayToDecimal,
|
||||||
|
4: arrayToDecimal,
|
||||||
|
5: arrayToDecimal,
|
||||||
|
6: arrayToDecimal,
|
||||||
|
7: arrayToDecimal,
|
||||||
|
8: arrayToSQLTimestamp,
|
||||||
|
}
|
||||||
|
|
||||||
|
const stringDataTypesDict = {
|
||||||
|
1: "site_name", // the human readable site name for the node
|
||||||
|
2: "node_name" // the human readable name for the node
|
||||||
|
}
|
||||||
|
|
||||||
|
const stringDataConversionFunctions = {
|
||||||
|
1: toTrimmedString,
|
||||||
|
2: toTrimmedString
|
||||||
|
}
|
||||||
|
|
||||||
|
const fileVersionManifestTypesDict = {
|
||||||
|
1: "boot_Version",
|
||||||
|
2: "program_Version",
|
||||||
|
3: "epaper_Version",
|
||||||
|
4: "mesh_version",
|
||||||
|
5: "bq27441_version",
|
||||||
|
6: "rtcmem_version",
|
||||||
|
7: "screen_version",
|
||||||
|
8: "message_parser_version",
|
||||||
|
9: "sht40_version",
|
||||||
|
10: "ssd1306_version",
|
||||||
|
11: "tmpxx_version",
|
||||||
|
12: "uqr_version",
|
||||||
|
13: "main_version",
|
||||||
|
14: "test_version",
|
||||||
|
15: "mc3470_version",
|
||||||
|
16: "settings_version",
|
||||||
|
17: "file_name_map_version",
|
||||||
|
18: "_walter_version",
|
||||||
|
19: "cellular_chunking_version",
|
||||||
|
20: "ds3231_version",
|
||||||
|
21: "walter_version",
|
||||||
|
}
|
||||||
|
|
||||||
|
const fileVersionManifestConversionFunctions = {
|
||||||
|
1: arrayToDecimal,
|
||||||
|
2: arrayToDecimal,
|
||||||
|
3: arrayToDecimal,
|
||||||
|
4: arrayToDecimal,
|
||||||
|
5: arrayToDecimal,
|
||||||
|
6: arrayToDecimal,
|
||||||
|
7: arrayToDecimal,
|
||||||
|
8: arrayToDecimal,
|
||||||
|
9: arrayToDecimal,
|
||||||
|
10: arrayToDecimal,
|
||||||
|
11: arrayToDecimal,
|
||||||
|
12: arrayToDecimal,
|
||||||
|
13: arrayToDecimal,
|
||||||
|
14: arrayToDecimal,
|
||||||
|
15: arrayToDecimal,
|
||||||
|
16: arrayToDecimal,
|
||||||
|
17: arrayToDecimal,
|
||||||
|
18: arrayToDecimal,
|
||||||
|
19: arrayToDecimal,
|
||||||
|
20: arrayToDecimal,
|
||||||
|
21: arrayToDecimal,
|
||||||
|
}
|
||||||
|
|
||||||
|
const rssiDataTypesDict = {
|
||||||
|
1: "rssi",
|
||||||
|
}
|
||||||
|
|
||||||
|
const rssiConversionFunctions = {
|
||||||
|
1: arrayToDecimal,
|
||||||
|
}
|
||||||
|
|
||||||
|
function noop(input) {
|
||||||
|
return input
|
||||||
|
}
|
||||||
|
|
||||||
|
function arrayToSQLTimestamp(input) {
|
||||||
|
const l = input.length
|
||||||
|
let out = 0
|
||||||
|
for(let i = 0; i < input.length; i++) {
|
||||||
|
out += input[i] * (2**(8*(l-i-1)))
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const theTimestamp = new Date(out*1000 + 946684800000).toISOString().slice(0, 19).replace('T', ' ')
|
||||||
|
return theTimestamp
|
||||||
|
} catch (e) {
|
||||||
|
console.log(out)
|
||||||
|
console.log(e)
|
||||||
|
return '00:00:00'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// input is a 2 byte array representing minutes since midnight, convert it into hh:mm:ss time
|
||||||
|
function arrayToTime(input) {
|
||||||
|
const minutesSinceMidnight = (input[0] << 8) + input[1]
|
||||||
|
minutes = minutesSinceMidnight % 60
|
||||||
|
hours = (Math.floor(minutesSinceMidnight - minutes) / 60)
|
||||||
|
|
||||||
|
while (hours > 23) {
|
||||||
|
hours = hours - 24
|
||||||
|
}
|
||||||
|
return hours + ":" + minutes + ":00"
|
||||||
|
}
|
||||||
|
|
||||||
|
function toBoolean(input) {
|
||||||
|
let tmp = 0
|
||||||
|
const l = input.length
|
||||||
|
for(let i = 0; i < input.length; i++) {
|
||||||
|
tmp += input[i] << (8*(l-i-1))
|
||||||
|
}
|
||||||
|
let out = false
|
||||||
|
if(tmp === 0) {
|
||||||
|
out = false
|
||||||
|
} else {
|
||||||
|
out = true
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// input is an array of bytes, trim off any leading 0x00 values and convert it to a string
|
||||||
|
function toTrimmedString(input) {
|
||||||
|
return input.toString().replaceAll('\x00', '')
|
||||||
|
}
|
||||||
|
|
||||||
|
function arrayToDividedDecimal(input) {
|
||||||
|
const l = input.length
|
||||||
|
let out = 0
|
||||||
|
for(let i = 0; i < input.length; i++) {
|
||||||
|
out += input[i] << (8*(l-i-1))
|
||||||
|
}
|
||||||
|
return out / 100
|
||||||
|
}
|
||||||
|
|
||||||
|
function arrayToHex(input) {
|
||||||
|
return input.map(x => ('00'+ x.toString(16)).slice(-2)).join('')
|
||||||
|
}
|
||||||
|
|
||||||
|
function arrayToDecimal(input) {
|
||||||
|
const l = input.length
|
||||||
|
let out = 0
|
||||||
|
for(let i = 0; i < input.length; i++) {
|
||||||
|
out += input[i] << (8*(l-i-1))
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
function arrayToSignedDecimal(input) {
|
||||||
|
const l = input.length
|
||||||
|
let out = 0
|
||||||
|
for(let i = 0; i < input.length; i++) {
|
||||||
|
out += input[i] << (8*(l-i-1))
|
||||||
|
}
|
||||||
|
if (out > 2**(8*l-1)) {
|
||||||
|
out = out - 2**(8*l)
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
function arrayToSignedDividedDecimal(input) {
|
||||||
|
const l = input.length
|
||||||
|
let out = 0
|
||||||
|
for(let i = 0; i < input.length; i++) {
|
||||||
|
out += input[i] << (8*(l-i-1))
|
||||||
|
}
|
||||||
|
if (out > 2**(8*l-1)) {
|
||||||
|
out = out - 2**(8*l)
|
||||||
|
}
|
||||||
|
return out / 100
|
||||||
|
}
|
||||||
|
|
||||||
|
function parse_message_header(theDataIn) {
|
||||||
|
const msg_id = arrayToHex([...theDataIn.slice(0,2)])
|
||||||
|
const msg_type = arrayToDecimal([...theDataIn.slice(2,3)])
|
||||||
|
const version = arrayToDecimal([...theDataIn.slice(3,4)])
|
||||||
|
if (version === 1) {
|
||||||
|
const source = arrayToHex([...theDataIn.slice(4,12)])
|
||||||
|
const target = arrayToHex([...theDataIn.slice(12,20)])
|
||||||
|
const timestamp = arrayToSQLTimestamp([...theDataIn.slice(20,24)])
|
||||||
|
return {
|
||||||
|
msg_id,
|
||||||
|
msg_type,
|
||||||
|
version,
|
||||||
|
source,
|
||||||
|
target,
|
||||||
|
timestamp,
|
||||||
|
device_id: source.slice(4),
|
||||||
|
header_length: 24
|
||||||
|
}
|
||||||
|
} else if (version === 2) {
|
||||||
|
const target_distance = arrayToDecimal([...theDataIn.slice(4,5)])
|
||||||
|
const source = arrayToHex([...theDataIn.slice(5,13)])
|
||||||
|
const target = arrayToHex([...theDataIn.slice(13,21)])
|
||||||
|
const timestamp = arrayToSQLTimestamp([...theDataIn.slice(21,25)])
|
||||||
|
return {
|
||||||
|
msg_id,
|
||||||
|
msg_type,
|
||||||
|
target_distance,
|
||||||
|
source,
|
||||||
|
target,
|
||||||
|
timestamp,
|
||||||
|
device_id: source.slice(4),
|
||||||
|
header_length: 25
|
||||||
|
}
|
||||||
|
} else { //else if (version === 3) {
|
||||||
|
const target_distance = arrayToDecimal([...theDataIn.slice(4,5)])
|
||||||
|
const source = arrayToHex([...theDataIn.slice(5,13)])
|
||||||
|
const target = arrayToHex([...theDataIn.slice(13,21)])
|
||||||
|
const t18 = arrayToDividedDecimal([...theDataIn.slice(21,23)])
|
||||||
|
const t36 = arrayToDividedDecimal([...theDataIn.slice(23,25)])
|
||||||
|
const timestamp = arrayToSQLTimestamp([...theDataIn.slice(25,29)])
|
||||||
|
return {
|
||||||
|
msg_id,
|
||||||
|
msg_type,
|
||||||
|
target_distance,
|
||||||
|
source,
|
||||||
|
target,
|
||||||
|
timestamp,
|
||||||
|
device_id: source.slice(4),
|
||||||
|
t18,
|
||||||
|
t36,
|
||||||
|
header_length: 29
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const messageTypes = {
|
||||||
|
1: {
|
||||||
|
types: twoByteDataTypesDict,
|
||||||
|
fns: twoByteDataConversionFunctions
|
||||||
|
},
|
||||||
|
2: {
|
||||||
|
types: fourByteDataTypesDict,
|
||||||
|
fns: fourByteDataConversionFunctions
|
||||||
|
},
|
||||||
|
3: {
|
||||||
|
types: twoByteDataTypesDict,
|
||||||
|
fns: twoByteDataConversionFunctions
|
||||||
|
},
|
||||||
|
4: {
|
||||||
|
types: fourByteDataTypesDict,
|
||||||
|
fns: fourByteDataConversionFunctions
|
||||||
|
},
|
||||||
|
5: {
|
||||||
|
types: twoByteDataTypesDict,
|
||||||
|
fns: twoByteDataConversionFunctions
|
||||||
|
},
|
||||||
|
6: {
|
||||||
|
types: fourByteDataTypesDict,
|
||||||
|
fns: fourByteDataConversionFunctions
|
||||||
|
},
|
||||||
|
7: {
|
||||||
|
types: stringDataTypesDict,
|
||||||
|
fns: stringDataConversionFunctions
|
||||||
|
},
|
||||||
|
8: {
|
||||||
|
// I don't think we need anything here
|
||||||
|
},
|
||||||
|
9: {
|
||||||
|
// do we need anything here?
|
||||||
|
},
|
||||||
|
10: {
|
||||||
|
// I need to figure out what goes here
|
||||||
|
},
|
||||||
|
11: {
|
||||||
|
// TODO
|
||||||
|
},
|
||||||
|
12: {
|
||||||
|
// TODO
|
||||||
|
},
|
||||||
|
13: {
|
||||||
|
types: fileVersionManifestTypesDict,
|
||||||
|
fns: fileVersionManifestConversionFunctions
|
||||||
|
},
|
||||||
|
14: {
|
||||||
|
// nothing here
|
||||||
|
},
|
||||||
|
15: {
|
||||||
|
// nothing here
|
||||||
|
},
|
||||||
|
16: {
|
||||||
|
// nothing here
|
||||||
|
},
|
||||||
|
17: {
|
||||||
|
types: rssiDataTypesDict,
|
||||||
|
fns: rssiConversionFunctions
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function get_type_and_fn(msg_type_id) {
|
||||||
|
msg_type = parsed["msg_type"]
|
||||||
|
theFn = noop
|
||||||
|
if (Object.keys(messageTypes).indexOf(String(parsed["msg_type"])) > -1) {
|
||||||
|
if( Object.keys(messageTypes[String(parsed["msg_type"])].types).indexOf(String(msg[ind])) > -1) {
|
||||||
|
msg_type = messageTypes[String(parsed["msg_type"])].types[String(msg[ind])]
|
||||||
|
}
|
||||||
|
if(Object.keys(messageTypes[String(parsed["msg_type"])].fns).indexOf(String(msg[ind])) > -1) {
|
||||||
|
theFn = messageTypes[String(parsed["msg_type"])].fns[String(msg[ind])]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {type: msg_type, fn: theFn}
|
||||||
|
}
|
||||||
|
|
||||||
|
function parse_messages(full_msg) {
|
||||||
|
ind = -1
|
||||||
|
out = []
|
||||||
|
msg = Buffer.from(full_msg.data, 'hex')
|
||||||
|
while (ind < msg.length) {
|
||||||
|
if (ind == -1 || msg[ind] == 0) {
|
||||||
|
ind = ind + 1
|
||||||
|
if (msg.length > ind + 24) {
|
||||||
|
parsed = parse_message_header(msg.slice(ind))
|
||||||
|
parsed["data"] = {}
|
||||||
|
out.push(parsed)
|
||||||
|
ind = ind + parsed['header_length']
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} else if ([1,3,5,13].indexOf(parsed["msg_type"]) > -1) { // data with 2 byte values
|
||||||
|
if (msg.length > ind + 3) {
|
||||||
|
type_info = get_type_and_fn(msg[ind])
|
||||||
|
parsed["data"][type_info.type] = type_info.fn(msg.slice(ind+1,ind+3))
|
||||||
|
ind = ind + 3
|
||||||
|
} else {
|
||||||
|
ind = ind + 1
|
||||||
|
}
|
||||||
|
} else if ([2,4,6].indexOf(parsed["msg_type"]) > -1) { // data with 4 byte values
|
||||||
|
if (msg.length > ind + 5) {
|
||||||
|
type_info = get_type_and_fn(msg[ind])
|
||||||
|
parsed["data"][type_info.type] = type_info.fn(msg.slice(ind+1,ind+5))
|
||||||
|
ind = ind + 5
|
||||||
|
} else {
|
||||||
|
ind = ind + 1
|
||||||
|
}
|
||||||
|
} else if (parsed["msg_type"] == 7) { // key-value pairs with byte keys and string values
|
||||||
|
types_value = msg[ind]
|
||||||
|
type_info = get_type_and_fn(msg[ind])
|
||||||
|
theKey = type_info.type
|
||||||
|
ind = ind + 1
|
||||||
|
valueEnd = 0
|
||||||
|
for (let i = ind; i < msg.length; i++) {
|
||||||
|
if (msg[i] == 255) {
|
||||||
|
valueEnd = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!valueEnd) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
parsed["data"][theKey] = type_info.fn(msg.slice(ind,valueEnd))
|
||||||
|
ind = valueEnd + 1
|
||||||
|
} else if (parsed["msg_type"] == 8) {// # key-value pair with both key and value as strings
|
||||||
|
// read until the first 0xFF for the name
|
||||||
|
keyEnd = 0
|
||||||
|
for (let i = ind; i < msg.length; i++) {
|
||||||
|
if (msg[i] == 255) {
|
||||||
|
keyEnd = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!keyEnd) {
|
||||||
|
// there is no terminator, the message isn't correctly formatted so we drop it
|
||||||
|
// if the first chararacter is the terminator it is also an error
|
||||||
|
break
|
||||||
|
}
|
||||||
|
theName = msg.slice(ind,keyEnd)
|
||||||
|
ind = keyEnd + 1
|
||||||
|
// read until the next 0xFF for the walue
|
||||||
|
valueEnd = 0
|
||||||
|
for (let i = ind; i < msg.length; i++) {
|
||||||
|
if (msg[i] == 255) {
|
||||||
|
valueEnd = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!valueEnd) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
value = msg.slice(ind,valueEnd)
|
||||||
|
parsed["data"]["name"] = toTrimmedString(theName)
|
||||||
|
parsed["data"]["value"] = toTrimmedString(value)
|
||||||
|
ind = valueEnd + 1
|
||||||
|
} else if (parsed["msg_type"] == 10) {
|
||||||
|
parsed["data"]["distance"] = msg[ind]
|
||||||
|
ind = ind + 1
|
||||||
|
} else if (parsed["msg_type"] == 17) {
|
||||||
|
thisMac = msg.slice(ind,ind+6).toString('hex')
|
||||||
|
parsed["data"][thisMac] = msg[ind+6]
|
||||||
|
ind = ind + 7
|
||||||
|
} else {
|
||||||
|
// if we hit a message we don't know how to deal with return
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
function make_message_header(msg_type, version, source, target, this_message_id) {
|
||||||
|
target = ('0000000000000000' + target).slice(-16)
|
||||||
|
target_buf = Buffer.from(target, 'hex')
|
||||||
|
source = (source = '0000000000000000' + source).slice(-16)
|
||||||
|
source_buf = Buffer.from(source, 'hex')
|
||||||
|
if (!this_message_id) {
|
||||||
|
this_message_id = message_id
|
||||||
|
increment_message_id()
|
||||||
|
}
|
||||||
|
timestamp = Math.floor(Date.now() / 1000) - 946684800 // timestamp in the format that the ESP32 expects it, seconds since 2000-01-01
|
||||||
|
timestamp_buf = Buffer.alloc(4)
|
||||||
|
timestamp_buf.writeUInt32BE(timestamp)
|
||||||
|
buf = Buffer.alloc(4)
|
||||||
|
buf.writeUInt16BE(this_message_id, 0)
|
||||||
|
buf.writeUInt8(msg_type, 2)
|
||||||
|
buf.writeUInt8(version || 1, 3)
|
||||||
|
header = Buffer.concat([buf, source_buf, target_buf, timestamp_buf])
|
||||||
|
return header
|
||||||
|
}
|
||||||
|
|
||||||
|
function make_two_byte_data_message(msg_type, version, source, target, data, this_message_id) {
|
||||||
|
theHeader = make_message_header(msg_type, version, source, target, this_message_id)
|
||||||
|
theData = Buffer.from([])
|
||||||
|
Object.keys(data).forEach(function(thisKey) {
|
||||||
|
if(thisKey > 255) {
|
||||||
|
// if the key won't fit into 1 byte or the value won't fit into 2 bytes something is wrong so we skip this one
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// make sure that thhe vaule is always 2 bytes long, zero pad if needed
|
||||||
|
const tmpBuffer = Buffer.alloc(2)
|
||||||
|
tmpBuffer.writeUInt16BE(data[thisKey])
|
||||||
|
this_buf = Buffer.concat([Buffer.from(thisKey), tmpBuffer])
|
||||||
|
theData = Buffer.concat([theData, this_buf])
|
||||||
|
})
|
||||||
|
return Buffer.concat([theHeader,theData,Buffer.from([0x00])])
|
||||||
|
}
|
||||||
|
|
||||||
|
function make_four_byte_data_message(msg_type, version, source, target, data, this_message_id) {
|
||||||
|
theHeader = make_message_header(msg_type, version, source, target, this_message_id)
|
||||||
|
theData = Buffer.from([])
|
||||||
|
Object.keys(data).forEach(function(thisKey) {
|
||||||
|
if(thisKey <= 255) {
|
||||||
|
// if the key won't fit into 1 byte or the value won't fit into 2 bytes something is wrong so we skip this one
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const tmpBuffer = Buffer.alloc(4)
|
||||||
|
tmpBuffer.writeUInt32BE(data[thisKey])
|
||||||
|
this_buf = Buffer.concat([Buffer.from(thisKey), tmpBuffer])
|
||||||
|
theData = Buffer.concat([theData, this_buf])
|
||||||
|
})
|
||||||
|
return Buffer.concat([theHeader,theData,Buffer.from([0x00])])
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function make_byte_string_message(msg_type, version, source, target, data, this_message_id) {
|
||||||
|
theHeader = make_message_header(msg_type, version, source, target, this_message_id)
|
||||||
|
theData = Buffer.from([])
|
||||||
|
Object.keys(data).forEach(function(thisKey) {
|
||||||
|
this_buf = Buffer.alloc(thisKey.length + data[thisKey].length)
|
||||||
|
this_buf.write(thisKey)
|
||||||
|
this_buf.write(data[thisKey],1)
|
||||||
|
theData = Buffer.concat([theData,this_buf, Buffer.from([255])])
|
||||||
|
})
|
||||||
|
return Buffer.concat([theHeader, theData, Buffer.from([0])])
|
||||||
|
}
|
||||||
|
|
||||||
|
function make_string_string_message(msg_type, version, source, target, key, value, this_message_id) {
|
||||||
|
theHeader = make_message_header(msg_type, version, source, target, this_message_id)
|
||||||
|
theData = Buffer.alloc(key.length + value.length + 3)
|
||||||
|
theData.write(key,0)
|
||||||
|
theData.writeUInt8(255, key.length)
|
||||||
|
theData.write(value, key.length+1)
|
||||||
|
theData.writeUInt8(255, key.length+value.length+2)
|
||||||
|
return Buffer.concat([theHeader, theData, Buffer.from([0])])
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
parse_messages,
|
||||||
|
make_two_byte_data_message,
|
||||||
|
make_four_byte_data_message,
|
||||||
|
make_byte_string_message,
|
||||||
|
make_string_string_message
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
|
@ -0,0 +1,138 @@
|
||||||
|
{
|
||||||
|
"name": "node-roost",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"": {
|
||||||
|
"name": "node-roost",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"mysql2": "^3.16.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/aws-ssl-profiles": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/denque": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/generate-function": {
|
||||||
|
"version": "2.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz",
|
||||||
|
"integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"is-property": "^1.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/iconv-lite": {
|
||||||
|
"version": "0.7.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz",
|
||||||
|
"integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/express"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/is-property": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/long": {
|
||||||
|
"version": "5.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz",
|
||||||
|
"integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==",
|
||||||
|
"license": "Apache-2.0"
|
||||||
|
},
|
||||||
|
"node_modules/lru.min": {
|
||||||
|
"version": "1.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/lru.min/-/lru.min-1.1.3.tgz",
|
||||||
|
"integrity": "sha512-Lkk/vx6ak3rYkRR0Nhu4lFUT2VDnQSxBe8Hbl7f36358p6ow8Bnvr8lrLt98H8J1aGxfhbX4Fs5tYg2+FTwr5Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"bun": ">=1.0.0",
|
||||||
|
"deno": ">=1.30.0",
|
||||||
|
"node": ">=8.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/wellwelwel"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/mysql2": {
|
||||||
|
"version": "3.16.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.16.0.tgz",
|
||||||
|
"integrity": "sha512-AEGW7QLLSuSnjCS4pk3EIqOmogegmze9h8EyrndavUQnIUcfkVal/sK7QznE+a3bc6rzPbAiui9Jcb+96tPwYA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"aws-ssl-profiles": "^1.1.1",
|
||||||
|
"denque": "^2.1.0",
|
||||||
|
"generate-function": "^2.3.1",
|
||||||
|
"iconv-lite": "^0.7.0",
|
||||||
|
"long": "^5.2.1",
|
||||||
|
"lru.min": "^1.0.0",
|
||||||
|
"named-placeholders": "^1.1.3",
|
||||||
|
"seq-queue": "^0.0.5",
|
||||||
|
"sqlstring": "^2.3.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/named-placeholders": {
|
||||||
|
"version": "1.1.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.6.tgz",
|
||||||
|
"integrity": "sha512-Tz09sEL2EEuv5fFowm419c1+a/jSMiBjI9gHxVLrVdbUkkNUUfjsVYs9pVZu5oCon/kmRh9TfLEObFtkVxmY0w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"lru.min": "^1.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/safer-buffer": {
|
||||||
|
"version": "2.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||||
|
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/seq-queue": {
|
||||||
|
"version": "0.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/seq-queue/-/seq-queue-0.0.5.tgz",
|
||||||
|
"integrity": "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q=="
|
||||||
|
},
|
||||||
|
"node_modules/sqlstring": {
|
||||||
|
"version": "2.3.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz",
|
||||||
|
"integrity": "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
{
|
||||||
|
"name": "node-roost",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"description": "",
|
||||||
|
"dependencies": {
|
||||||
|
"mysql2": "^3.16.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
// this takes the files is the records folder and sends the messages to the server one by one, this is for recovery when the server crashes
|
||||||
|
|
||||||
|
const dgram = require('dgram')
|
||||||
|
const fs = require('fs')
|
||||||
|
const cellularChunking = require('./cellularChunking')
|
||||||
|
|
||||||
|
function handleMessage(message, rinfo) {
|
||||||
|
console.log('store message: ', message)
|
||||||
|
fetch('http://localhost:8085/api/data/store2', {
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify({"data":message}),
|
||||||
|
headers: {"Content-Type": "application/json",'Connection': 'Close', 'x-authentication': '40ab7a89-dad8-45ac-a68e-4f64b3df6373'}
|
||||||
|
}).then(x => x.text()).then(theResp => {
|
||||||
|
console.log('got a response: ', theResp)
|
||||||
|
}).catch(e => console.log)
|
||||||
|
}
|
||||||
|
|
||||||
|
let lineInd = 0
|
||||||
|
let theLines = []
|
||||||
|
|
||||||
|
function storeNextLine() {
|
||||||
|
if(theLines[lineInd].startsWith('>')) {
|
||||||
|
const tmp = theLines[lineInd].split(' ')
|
||||||
|
const theMessage = Buffer.from(tmp[tmp.length-1], 'hex')
|
||||||
|
lineInd = lineInd + 1
|
||||||
|
cellularChunking.receive_chunk(theMessage, handleMessage, undefined)
|
||||||
|
} else {
|
||||||
|
lineInd = lineInd + 1
|
||||||
|
}
|
||||||
|
setTimeout(storeNextLine, 100)
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!process.argv[2]) {
|
||||||
|
console.log("usage: node ./storeRecords.js <filename.dat>")
|
||||||
|
} else {
|
||||||
|
let fileData = fs.readFileSync(`./records/${process.argv[2]}`, {encoding: 'utf-8'})
|
||||||
|
theLines = fileData.split('\n')
|
||||||
|
storeNextLine()
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,28 @@
|
||||||
|
const dgram = require('dgram')
|
||||||
|
const fs = require('fs')
|
||||||
|
const path = require('path')
|
||||||
|
const cellularChunking = require('./../cellularChunking')
|
||||||
|
const messageParser = require('./../messages')
|
||||||
|
|
||||||
|
const socket = dgram.createSocket('udp4');
|
||||||
|
|
||||||
|
function send_next_line(i, theLines) {
|
||||||
|
if (theLines.length > i) {
|
||||||
|
socket.send(Buffer.from(theLines[i], 'hex'), 57321, '127.0.0.1', function (err) {
|
||||||
|
setTimeout(send_next_line, 100, i+1, theLines)
|
||||||
|
//send_next_line(i + 1, theLines)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
process.exit()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// open the file with the messages and then send them
|
||||||
|
try {
|
||||||
|
const fileData = fs.readFileSync('./data/test_data.txt', 'utf8')
|
||||||
|
const theLines = fileData.split('\n')
|
||||||
|
let i = 0
|
||||||
|
send_next_line(i,theLines)
|
||||||
|
} catch (e) {
|
||||||
|
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue