/* * Karinto Library Project * * This software is distributed under a zlib-style license. * See license.txt for more information. */ using System; using System.Collections.Generic; using System.Text; using System.Text.RegularExpressions; using System.IO; using Karinto.Xml; using Settings = Karinto.Xml.Hioki.HicorderSettingsRow; using AnalogChannel = Karinto.Xml.Hioki.HicorderAnalogChannelRow; namespace Karinto { public class HiokiHicorderDataReader { private class BaHeader { public int Channel; public int SampleNumber; public int Offset; } private HiokiHicorderData temp; private BaHeader[] baHeaders; private const int HeaderSize = 0x200; private int sizeOfHeaderChunk; private int sizeOfDataChunk; public HiokiHicorderData Read(FilePath path) { HiokiHicorderData hicorderData = null; try { using (FileStream fs = new FileStream( path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) { if (fs.Length < 512 || fs.Length > 0x7FFFFFFF) { return null; } temp = new HiokiHicorderData(); ParseHeaders(fs); sizeOfDataChunk = (int)fs.Length - sizeOfHeaderChunk; byte[] data = new byte[sizeOfDataChunk]; fs.Read(data, 0, data.Length); if (baHeaders == null) { ReadDataChunkWithoutBHeaders(data); } else { ReadDataChunkWithBHeaders(data); } } hicorderData = temp; } catch (Exception e) { throw e; } finally { temp = null; } return hicorderData; } private static string[] ReadHeader(byte[] data, int offset) { const int elementNum = 42; const int elementSize = 12; string[] header = new string[elementNum]; for (int i = 0; i < elementNum; ++i) { string element = Encoding.ASCII.GetString(data, offset, 12); header[i] = element.TrimEnd(new char[] { '\0' }); offset += elementSize; } return header; } private void ParseHeaders(FileStream fs) { byte[] data = new byte[HeaderSize]; fs.Read(data, 0, HeaderSize); int offset = 0; string[] header = ReadHeader(data, offset); temp.Settings = ParseWHeader(header); int numberOfHeaders = Int32.Parse(header[1]); sizeOfHeaderChunk = numberOfHeaders * HeaderSize; data = new byte[sizeOfHeaderChunk - HeaderSize]; int readBytes = fs.Read(data, 0, data.Length); if (readBytes < data.Length) { throw new Exception("Invalid Headers"); } while (offset < data.Length) { header = ReadHeader(data, offset); offset += 0x200; switch (header[0]) { case "HBA": baHeaders = ParseBAHeader(header); break; case "HC": AnalogChannel c = ParseCHeader(header); temp.ChannelData[c.Channel] = c; break; default: break; } } if (offset != data.Length) { throw new Exception("Invalid Headers"); } } private static Settings ParseWHeader(string[] header) { if (header[0] != "HW") return null; Xml.Hioki.HicorderSettingsDataTable table = new Xml.Hioki.HicorderSettingsDataTable(); Settings settings = table.NewHicorderSettingsRow(); settings.Model = header[2]; settings.RomVersion = header[3]; settings.Function = header[4]; settings.Shot = header[5]; settings.SaveDataNumber = Int32.Parse(header[6]); Match m = new Regex(@"(\d\d)-(\d\d)-(\d\d)").Match(header[7]); int year = Int32.Parse(m.Groups[1].Value); year += (year > 70) ? 1900 : 2000; int month = Int32.Parse(m.Groups[2].Value); int date = Int32.Parse(m.Groups[3].Value); m = new Regex(@"(\d\d):(\d\d):(\d\d(?:\.\d*)?)").Match(header[8]); int hour = Int32.Parse(m.Groups[1].Value); int minute = Int32.Parse(m.Groups[2].Value); double second = Double.Parse(m.Groups[3].Value); int iSecond = (int)Math.Floor(second); int mSecond = (int)Math.Floor((second - iSecond) * 1000); settings.TrigDate = new DateTime(year, month, date, hour, minute, iSecond, mSecond, DateTimeKind.Local); m = new Regex(@"(\d+(?:\.\d*)?)(.*)s").Match(header[11]); decimal scale = (decimal)Unit.SiPrefix[m.Groups[2].Value]; settings.TimePerDiv = decimal.Parse(m.Groups[1].Value) * scale; settings.TitleSetting = header[28]; settings.Title = header[29] + header[30] + header[31] + header[32]; settings.Title = settings.Title.TrimEnd(); settings.CommentSetting = header[33]; settings.UseChannel = 0; settings.SavedAnalogChannel = 0; int cursor = 1; foreach (char saved in header[34] + header[35]) { if (saved == '1') { settings.UseChannel++; settings.SavedAnalogChannel += cursor; } cursor += cursor; } settings.SavedLogicUnit = 0; cursor = 1; foreach (char saved in header[39]) { if (saved == '1') { settings.SavedLogicUnit += cursor; } cursor += cursor; } return settings; } private static BaHeader[] ParseBAHeader(string[] header) { if (header[0] != "HBA") return null; BaHeader[] headers = new BaHeader[16 + 1]; int offset = 0; for (int i = 0; i < 16; ++i) { if (header[offset + 1] == "") { break; } BaHeader ba = new BaHeader(); ba.Channel = Int32.Parse(header[offset + 1]); ba.SampleNumber = Int32.Parse(header[offset + 2]); ba.Offset = Int32.Parse(header[offset + 3]); headers[ba.Channel] = ba; offset += 4; } return headers; } private AnalogChannel ParseCHeader(string[] header) { if (header[0] != "HC") return null; Hioki.HicorderAnalogChannelDataTable table = new Hioki.HicorderAnalogChannelDataTable(); AnalogChannel ch = table.NewHicorderAnalogChannelRow(); ch.Channel = Int32.Parse(header[1]); ch.Unit = header[2]; // switch (temp.Settings.Model) { case "8855": if (!Regex.IsMatch(ch.Unit, "8951")) { break; } string romVer = temp.Settings.RomVersion; Match d = RegexSet.DecimalFloat.Match(romVer); Decimal v = Decimal.Parse(d.Groups[0].Value); if (v < 2.55m || (5.00m <= v && v < 5.55m)) { // cf. http://hioki.jp/support/recorder/8855.pdf throw new NotSupportedException(); } break; } ch.Resolution = Int32.Parse(header[3]); ch.Range = header[4]; ch.Position = Int32.Parse(header[5]); ch.LPF = header[6]; ch.AAF = header[7]; ch.Coupling = header[8]; ch.Trig = header[9]; ch.Mode = header[16]; ch.Comment = header[17] + header[18] + header[19] + header[20]; ch.Comment = ch.Comment.TrimEnd(); ch.Scaling = header[21] == "ON"; ch.ScalingEu = header[22]; ch.ScalingEuPerV = Double.Parse(header[23]); ch.ScalingOffset = Double.Parse(header[24]); ch.Variable = header[25] == "ON"; ch.VariableUpper = Double.Parse(header[26]); ch.VariableLower = Double.Parse(header[27]); Match m = new Regex(@"(\d+(?:\.\d*)?)%").Match(header[28]); ch.Vernier = Double.Parse(m.Groups[1].Value); ch.Calcurate = header[32]; if (header[34].Length < 2) { ch.MeasurementScale = Double.Parse(header[33] + header[34]); ch.MeasurementOffset = Double.Parse(header[35] + header[36]); } else { ch.MeasurementScale = Double.Parse(header[33]); ch.MeasurementOffset = Double.Parse(header[34]); } return ch; } private void ReadDataChunkWithoutBHeaders(byte[] data) { int numberOfAnalogCh = 0; int numberOfLogicCh = 0; List analogChMap = new List(); for (int i = 0; i < 16; ++i) { if ((temp.Settings.SavedAnalogChannel & (1 << i)) != 0) { numberOfAnalogCh++; analogChMap.Add(i + 1); } } int n = temp.Settings.SaveDataNumber; short[][] analog = new short[numberOfAnalogCh][]; byte[][] logic = new byte[numberOfLogicCh][]; for (int i = 0; i < analog.Length; ++i) { analog[i] = new short[n]; } for (int i = 0; i < logic.Length; ++i) { logic[i] = new byte[n]; } switch (temp.Settings.Function) { case "MEM": ReadMemDataChunkWithoutBHeaders(data, n, analog, logic); break; case "REC": throw new NotSupportedException(); break; default: throw new NotSupportedException(); } for (int i = 0; i < analog.Length; ++i) { int ch = analogChMap[i]; temp.ChannelData[ch].SetRawData(analog[i]); temp.ChannelData[ch].SetConverter(true); } } private static void ReadMemDataChunkWithoutBHeaders( byte[] data, int n, short[][] analog, byte[][] logic) { int length = n + n; int offset = 0; for (int i = 0; i < n; ++i) { for (int ch = 0; ch < analog.Length; ++ch) { analog[ch][i] = (short)((data[offset] << 8 | data[offset + 1]) - 0x8000); offset += 2; } for (int ch = 0; ch < logic.Length; ++ch) { if ((ch & 1) == 1) { logic[ch][i] = (byte)(data[offset - 1] >> 4); } else { logic[ch][i] = (byte)(data[offset] & 0xF); offset += 1; } } } } private void ReadDataChunkWithBHeaders(byte[] data) { for (int i = 1; i <= 16; ++i) { if (temp.ChannelData[i] != null) { temp.ChannelData[i].SetRawData(ReadAnalogChannel(data, i)); temp.ChannelData[i].SetConverter(false); } } } private short[] ReadAnalogChannel(byte[] data, int channel) { int n = baHeaders[channel].SampleNumber; short[] values = new short[n]; int offset = baHeaders[channel].Offset * HeaderSize; offset -= sizeOfHeaderChunk; for (int i = 0; i < n; ++i) { values[i] = (short)(data[offset] << 8 | data[offset + 1]); offset += 2; } return values; } } }